zoukankan      html  css  js  c++  java
  • runtime的几种技术

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

    目录:

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

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

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

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

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

      (6) 总结

     

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

          使用该函数可以在运行时动态替换某个类的函数实现,这样做有什么用呢?最起码,可以实现类似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那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。

    示例代码:

    复制代码
     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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    代码示例一:
     
     @implementation CA
     void dynamicMethodIMP(id selfSEL _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

     

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    示例代码二:
     
    @implementation CA
     
    void dynamicMethodIMP(id selfSEL _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。

     

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    示例代码三:
     
    @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 *格式的名称来修改变量的值,而不是通过变量

     

  • 相关阅读:
    LeetCode 242. Valid Anagram (验证变位词)
    LeetCode 205. Isomorphic Strings (同构字符串)
    LeetCode 204. Count Primes (质数的个数)
    LeetCode 202. Happy Number (快乐数字)
    LeetCode 170. Two Sum III
    LeetCode 136. Single Number (落单的数)
    LeetCode 697. Degree of an Array (数组的度)
    LeetCode 695. Max Area of Island (岛的最大区域)
    Spark中的键值对操作
    各种排序算法总结
  • 原文地址:https://www.cnblogs.com/yuwei0911/p/5442023.html
Copyright © 2011-2022 走看看