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);(3NSLog(@"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);(2NSString *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 *格式的名称来修改变量的值,而不是通过变量

0 0