zoukankan      html  css  js  c++  java
  • iOS

    前言:

             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那么简单,在实现装饰者模式时,也 可以使用该函数进行消息转发。

    示例代码:

     @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
    ",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
    ",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的情况下,为各种类(包括系统的类)做很多操作,具体包括:

     

    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
    
    获取变量的值

     

    3.    替换

     

    将实例替换成另一个类:object_setClass
    
    将函数替换成一个函数实现:class_replaceMethod
    
    直接通过char *格式的名称来修改变量的值,而不是通过变量
  • 相关阅读:
    java设计模式简介
    java设计模式--单例模式
    判断整形回文数
    常用正则表达式 捕获组(分组)
    [转]十分钟搞定Vue搭建
    ActiveX界面已显示,调用方法报undefined的处理办法
    [转]纯js导出json到excel(支持chrome)
    webapi 开启gzip压缩
    webapi下载文件
    iis添加共享目录为虚拟目录
  • 原文地址:https://www.cnblogs.com/baitongtong/p/6051581.html
Copyright © 2011-2022 走看看