runtime运行时
来源:互联网 发布:考试出题软件app 编辑:程序博客网 时间:2024/06/10 07:10
oc 的runtime技术功能非常强大,能够在运行时获取各种信息,例如,获取方法列表,属性列表,变量列表,修改方法,属性,增加方法,属性等等,我们也可以引入库#include<objc/runtime.h>
进入头文件进行查看
那OC是怎么实现动态调用的呢?下面我们来看看OC通过发送消息来达到动态调用的秘密。假如在OC中写了这样的一个代码:
[obj makeText];其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成objc_msgSend(obj,@selector(makeText));首先我们来看看obj这个对象,iOS中的obj都继承于NSObject。@interface NSObject <nsobject> { Class isa OBJC_ISA_AVAILABILITY;}</nsobject>在NSObjcet中存在一个Class的isa指针。然后我们看看Class:typedef struct objc_class *Class;struct objc_class { Class isa; // 指向metaclass Class super_class ; // 指向其父类 const char *name ; // 类名 long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取 long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法; long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量); struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址 struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法; struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率; struct objc_protocol_list *protocols; // 存储该类遵守的协议 }Class isa :指向metaclass,也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对 象方法(“-”开头的方法).普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方 法)。
(1)在运行时进行对函数动态替换 class_replaceMethod
使用该函数可以在运行时动态的替换某个类的函数实现,这样做有何作用,可以实现类似windows上的hook效果,即获取系统类的某个实例函数,然后塞进一些自己的东西
IMP orginIMP;NSString * MyUppercaseString(id SELF, SEL _cmd){ NSLog(@"begin uppercaseString"); NSString *str = orginIMP (SELF, _cmd);(3) NSLog(@"end uppercaseString"); return str;}-(void)testReplaceMethod{ Class strcls = [NSString class]; SEL oriUppercaseString = @selector(uppercaseString); orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString]; (1) IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2) NSString *s = "hello world"; NSLog(@"%@",[s uppercaseString]];}
执行结果为:
begin uppercaseString
end uppercaseString
HELLO WORLD
这段代码的作用就是
(1)得到uppercaseString这个函数的函数指针存到变量orginIMP中
(2)将NSString类中的uppercaseString函数的实现替换为自己定义的MyUppercaseString
(3)在MyUppercaseString中,先执行了自己的log代码,然后再调用之前保存的uppercaseString的系统实现,这样就在系统函数执行之前加入自己的东西,后面每次对NSString调用uppercaseString的时候,都会打印出log来
与class_replaceMethod相仿,class_addMethod可以在运行时为类增加一个函数。
(2)当某个类不能接受某个selector是,对该selector的调用转发给另一个对象 - (id)forwardingTargetForSelector:(SEL)aSelector
forwardingTagetForSelector是NSObject的函数,用户可以在派生类中对其重载,从而将无法处理的selector转发给另一个对象。还是以上面的uppercaseString为例,如果用户自己定义的CA类的对象a,没有uppercaseString这样的一个实例函数,那么在不调用respondSelector的情况下,直接执行[a perforSelector:@selector”uppercaseString”]那么执行时一定会crash,此时如果CA实现了forwardingTargetForSelector函数,并且返回一个NSSstring对象,那么就相对于该NSString对象执行了uppercaseString函数,此时就不会crash了,当然实现这个函数的目的并不仅仅是为了让程序不crash那么简单,在实现装饰模式是,也可以使用该函数进行消息转发
@interface CA :NSObject- (void)f;@end@implementation CA- (id)forwardingTargetForSelector:(SEL)aSelector{if (aSelector == @selector(uppercaseString)){ return @"hello world" }}测试代码CA *a = [CA new];NSString *s = [a performSelector:@selector(uppercaseString)];NSLog(@"%@",s);
测试代码的输出为:HELLO WORLD
这里有个问题,CA类的对象不能直接接收@selector(uppercaseString),那么如果我在forwardingTargetForSelector函数中调用class_addMethod给CA类添加一个uppercaseString函数,然后返回self,可行么,经过试验,这样程序会崩溃,此时CA类其实已经有了uppercaseString函数,但是不知道为什么不能调用,如果此时new一个CA类的对象,并返回,是可以成功的
(3)当某个对象不能接收某个selector时,向对象所属的类的动态添加所需要的selector:+(BOOL)resolveInstanceMethod:(SEL)aSEL
这个函数与forwardTargetForSelector类似,都会在对象不能接受某个selector转发给另一个对象,另外,触发时机也不完全一样,该函数是个类函数,在程序启动,界面尚未显示出时,就会被调用
在类不能处理某个selector的情况下,如果重载该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetFoeSelector就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用forwardingTargetForSelector,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用forwardingTargetForSelector,如果在forwardingTargetForSelector并返回能接收该selector的对象,那么resolveInstanceMethod会再次被触发,这一次,如果仍然不添加selector,程序就会crash
@implementation CAvoid dynamicMethodIMP(id self,SEL _cmd){printf("SEL %s did not\n",sel_getName(_cmd));}+ (BOOL)resolveInstanceMethod:(SEL)aSEL{if (aSEL == @selector(t)){class_addMethod([selfclass],aSEL,(IMP) dynamicMethodIMP,"v@:");return YES;其中types参数为"i@:@“,按顺序分别表示: i : 返回值类型int,若是v则表示void @ : 参数id(self) : : SEL(_cmd) @ : id(str) }}class_addMethod 方法中的参数 cls:被添加方法的类 name:可以理解为方法名 imp:实现这个方法的函数 types:一个定义该函数返回值类型和参数类型的字符串@end测试代码CA *ca = [CA new];[ca performSelector:@selector(t)];
执行结果
SEL t did not exist
这里写示例代码二:@implementation CAvoid dynamicMethodIMP(id self, SEL _cmd){ printf("SEL %s did not exist\n",sel_getName(_cmd));}+ (BOOL) resolveInstanceMethod:(SEL)aSEL{ return YES;}- (id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == @selector(uppercaseString)) { return @"hello world"; }}测试代码 : a = [[CA alloc]init]; NSLog(@"%@",[a performSelector:@selector(uppercaseString)];代码片
对于该测试代码的输出为:HELLO WORLD
对于该测试代码,由于a没有对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发
forwardingTargetForSelector函数,在forwardingTargetForSelector函数中,返回了一个NSString “hello world”,因此会由该string来执行uppercaseString函数,最终返回大写的hello world。
(4)使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称
u_int count;objc_property_t* properties = class_copyPropertyList([UIView class],&count);for (int i = 0; i < count; i++){const char*propertyName = property_getName(properties[i]);NSSTring *strName = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];NSLOG(@"%@",strName);}输出结果:skipsSubviewEnumerationviewTraversalMarkviewDelegatemonitorsSubtreebackgroundColorSystemColorNamegesturesEnableddeliversTouchesForGesturesToSuperviewuserInteractionEnabledtaglayer_boundsWidthVariable_boundsHeightVariable_minXVariable_minYVariable_internalConstraints_dependentConstraints_constraintsExceptingSubviewAutoresizingConstraints_shouldArchiveUIAppearanceTags
使用class_copyMethodList获取类的所有方法列表
获取到的数据是一个Method数组,Method数据结构中包含函数的名称、参数、返回值等信息,以下代码以获取名称为例:
u_int count;Method* methods= class_copyMethodList([UIView class], &count);for (int i = 0; i < count ; i++){ SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; NSLog(@"%@",strName);}
其他一些相关方法的用法:
1.SEL method_getName(Method m) 由Method得到SEL
2.IMP method_getImplementation(Method m) 由Method得到IMP函数指针
3.const char *method_getTypeEncoding(Method m) 由Method得到类型编码信息
4.unsigned int method_getNumberOfArguments(Method m)获取参数个数
5.char *method_copyReturnType(Method m) 得到返回值类型名称
6.IMP method_setImplementation(Method m, IMP imp) 为该方法设置一个新的实现
总结:
总而言之,使用runtime技术能做些什么事情呢?
可以在运行时,在不继承也不category的情况下,为各种类(包括系统的类)做很多操作,具体包括:
1、增加
增加函数:class_addMethod
增加实例变量:class_addIvar
增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
增加Protocol:class_addProtocol (说实话我真不知道动态增加一个protocol有什么用,-_-!!)
2、获取
获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName …
获取属性列表及每个属性的信息:class_copyPropertyList property_getName
获取类本身的信息,如类名等:class_getName class_getInstanceSize
获取变量列表及变量信息:class_copyIvarList
获取变量的值
替换
将实例替换成另一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
直接通过char *格式的名称来修改变量的值,而不是通过变量
- iOS_运行时runtime
- iOS运行时 runtime
- 浅析运行时Runtime
- Runtime 运行时定义
- 运行时Runtime 小结
- runtime运行时
- 运行时runtime
- runtime运行时
- runtime 运行时
- RunTime 运行时
- iOS runtime 运行时
- 运行时runtime
- 运行时runtime
- IOS runtime运行时
- Runtime运行时
- runtime运行时
- Runtime 运行时:知识点
- Runtime(运行时)
- Android基础之intent-filter、action、category标签使用
- 不再设防
- UIday1103:UITableView 3 cell高度自适应代码
- LeetCode_OJ【177】Nth Highest Salary
- SVN安装---本机安装---SVN服务注册为Windows服务(4)
- runtime运行时
- sql语句优化
- js验证电话,身份证和银行卡的有效性
- SOAP协议基础
- ZOJ 3724 Delivery(树状数组+离线处理)
- sublime text 注册码以及stone capture的注册码
- 神经猫项目
- Java_jdbc 基础笔记之九 数据库连接 (查询Customer对象的方法)
- 树和森林和二叉树