zoukankan      html  css  js  c++  java
  • Objective C运行时(runtime)技术的几个要点总结

    前言:

             Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增加方法,属性等等,本文对相关的几个要点做了一个小结。

    目录:

    (1)使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数

    (2)重载forwardingTargetForSelector,将无法处理的selector转发给其他对象

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

    (4)使用class_copyPropertyListproperty_getName获取类的属性列表及每个属性的名称

      (5) 使用class_copyMethodList获取类的所有方法列表

      (6) 总结

     

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

          使用该函数可以在运行时动态替换某个类的函数实现,这样做有什么用呢?最起码,可以实现类似windowshook效果,即截获系统类的某个实例函数,然后塞一些自己的东西进去,比如打个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那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。

    示例代码:

     1 @interface CA : NSObject
     3 -(void)f;
     4 
     5 @end
     6 
     7 @implementation CA
     8 
     9 - (id)forwardingTargetForSelector:(SEL)aSelector
    11 {
    13     if (aSelector == @selector(uppercaseString))
    15     {
    17         return@"hello world";
    19     }
    21 }

    测试代码:

    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,程序就会报异常

    代码示例一:
    
     1 @implementation CA
     3 void dynamicMethodIMP(id self, SEL _cmd)
     5 {    
     7      printf("SEL %s did not exist
    ",sel_getName(_cmd));
     9 }
    10 
    11 + (BOOL) resolveInstanceMethod:(SEL)aSEL
    13 {
    15     if (aSEL == @selector(t))
    17     {
    19         class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:");
    21         return YES;
    23     }
    25     return [superresolveInstanceMethod:aSEL];
    27 }
    28 
    29 @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
    ",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;
      }
     测试代码:
    
    1  a = [[CA alloc]init];
    2  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 *格式的名称来修改变量的值,而不是通过变量
    

      

        

       参考资料:(1)Objective-C Runtime Reference

                   (2)深入浅出Cocoa之消息   

                   (3)objective-c 编程总结(第六篇)运行时操作 - 方法交换

                    (4)Runtime of Objective-C

     

  • 相关阅读:
    Infopath Notify 弹出提示信息
    window.showModalDialog 返回值
    【转】获得正文内容中的所有img标签的图片路径
    Json Datable Convert
    Sharepoint 列表 附件 小功能
    Surgey 权限更改
    SQL 触发器用于IP记录转换
    Caml语句 查询分配给当前用户及当前组
    jquery 1.3.2 auto referenced when new web application in VSTS2010(DEV10)
    TFS diff/merge configuration
  • 原文地址:https://www.cnblogs.com/gugupluto/p/3159733.html
Copyright © 2011-2022 走看看