[分享]iOS开发-Objective C运行时(runtime)技术总结,好强大的runtime

前言:html

Objective C的runtime技术功能很是强大,可以在运行时获取并修改类的各类信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增长方法,属性等等,本文对相关的几个要点作了一个小结。windows

目录:数组

  • (1)使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增长新函数数据结构

  • (2)重载forwardingTargetForSelector,将没法处理的selector转发给其余对象函数

  • (3)重载resolveInstanceMethod,从而在没法处理某个selector时,动态添加一个selector测试

  • (4)使用class_copyPropertyList及property_getName获取类的属性列表及每一个属性的名称编码

  • (5) 使用class_copyMethodList获取类的全部方法列表指针

  • (6) 总结code


(1)在运行时对函数进行动态替换 : class_replaceMethodorm

使用该函数能够在运行时动态替换某个类的函数实现,这样作有什么用呢?最起码,能够实现相似windows上hook效果,即截获系统类的某个实例函数,而后塞一些本身的东西进去,好比打个log什么的。

示例代码:

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

forwardingTargetForSelector是NSObject的函数,用户能够在派生类中对其重载,从而将没法处理的selector转 发给另外一个对象。仍是以上面的uppercaseString为例,若是用户本身定义的CA类的对象a,没有uppercaseString这样一个实例 函数,那么在不调用respondSelector的状况下,直接执行[a performSelector:@selector"uppercaseString"],那么执行时必定会crash,此时,若是CA实现了 forwardingTargetForSelector函数,并返回一个NSString对象,那么就相对于对该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

ps: 这里有个问题,CA类的对象不能接收@selector(uppercaseString),那么若是我在 forwardingTargetForSelector函数中用class_addMethod给CA类增长一个uppercaseString函数, 而后返回self,可行吗?通过试验,这样会crash,此时CA类其实已经有了uppercaseString函数,可是不知道为何不能调用,若是此 时new一个CA类的对象,并返回,是能够成功的。


(3)当某个对象不能接受某个selector时,向对象所属的类动态添加所需的selector:

+(BOOL) resolveInstanceMethod:(SEL)aSEL

这个函数与forwardingTargetForSelector相似,都会在对象不能接受某个selector时触发,执行起来略有差异。前者的目 的主要在于给客户一个机会来向该对象添加所需的selector,后者的目的在于容许用户将selector转发给另外一个对象。另外触发时机也不彻底一 样,该函数是个类函数,在程序刚启动,界面还没有显示出时,就会被调用。

在类不能处理某个selector的状况下,若是类重载了该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector 就不会被调用,若是在该函数中没有添加相应的selector,那么无论返回什么,后面都会继续调用 forwardingTargetForSelector,若是在forwardingTargetForSelector并未返回能接受该 selector的对象,那么resolveInstanceMethod会再次被触发,这一次,若是仍然不添加selector,程序就会报异常
代码示例一:

@implementation CA
 void dynamicMethodIMP(id self, SEL _cmd)
 {   
      printf("SEL %s did not exist\n",sel_getName(_cmd));
 }

 +(BOOL) resolveInstanceMethod:(SEL)aSEL
 {
     if (aSEL == @selector(t))
     {
         class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:");
         return YES;
     }
     return [superresolveInstanceMethod:aSEL];
 }

 @end

测试代码:

CA * ca = [CA new]
 [ca performSelector:@selector(t)];

执行结果
SEL t did not exist

示例代码二:

@implementation CA
 
void 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没有uppercaseString函数,所以会触发resolveInstanceMethod,可是因为该函数并无添加selector,所以运行时发现找不到该函数,会触发

forwardingTargetForSelector 函数,在forwardingTargetForSelector函数中,返回了一个NSString "hello world",所以会由该string来执行uppercaseString函数,最终返回大写的hello world。

示例代码三:

@implementation CA
 
+(BOOL) resolveInstanceMethod:(SEL)aSEL
{
    return  YES;
}
-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}

测试代码:

a = [[CA alloc]init];
NSLog(@"%@",[a performSelector:@selector(uppercaseString)];

这段代码的执行顺序为:

(1):首先在程序刚执行,AppDelegate都尚未出来时,resolveInstanceMethod就被触发,

(2):等测试代码执行时,forwardingTargetForSelector被调用

(3):因为forwardingTargetForSelector返回了nil,所以运行时仍是找不到uppercaseString selector,这时又会触发resolveInstanceMethod,因为仍是没有加入selector,因而会crash。


(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);
}

以上代码获取了UIView的全部属性并打印属性名称, 输出结果为:

skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
tag
layer
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags


(5) 使用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);
}

代码执行后将输出UIView全部函数的名称,具体结果略。
其余一些相关函数:
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) 为该方法设置一个新的实现


(6)总结

总而言之,使用runtime技术能作些什么事情呢?

能够在运行时,在不继承也不category的状况下,为各类类(包括系统的类)作不少操做,具体包括:

  • 增长

增长函数:class_addMethod
增长实例变量:class_addIvar
增长属性:@dynamic标签,或者class_addMethod,由于属性其实就是由getter和setter函数组成
增长Protocol:class_addProtocol (说实话我真不知道动态增长一个protocol有什么用,-_-!!)

  • 获取

获取函数列表及每一个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...
获取属性列表及每一个属性的信息:class_copyPropertyList property_getName
获取类自己的信息,如类名等:class_getName class_getInstanceSize
获取变量列表及变量信息:class_copyIvarList
获取变量的值

  • 替换

将实例替换成另外一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
直接经过char *格式的名称来修改变量的值,而不是经过变量


分享来源:
http://www.cnblogs.com/yswdar...