美文网首页
iOS中的runtime理解

iOS中的runtime理解

作者: Dottie22 | 来源:发表于2016-12-09 17:45 被阅读26次

    Runtime的使用:

    1.动态获取/创建类

    2.动态为一个类增加属性(关联对象)或方法

    3.在程序运行过程中遍历类中的成员变量, 属性和方法

    4.调换两个方法的逻辑实现(Method Swizzling)-->交叉方法

    如:
    //oc消息发送的底层实现.
    //1.导入#import <objc/message.h>.
    //2.到项目Build setting中搜索objc, 关闭 enable strict checking of objc_msgSend calls
    //Person类有一个属性age
    objc_msgSend(person, @selector(setAge:), 28);
    NSLog(@"person.age = %ld", person.age);
    

    1.关联对象

    给类添加属性:

    原理: 给一个类声明属性, 其实本质就是给这个类添加关联, 并不是直接把这个值的内存空前添加到类的存储空间.

    • 比如说现在系统的button类不能满足我的要求, 你需要添加一个额外的属性,
      • 一般我们的解决办法都是去继承一个类, 但是就为了一个属性而去继承一个类, 太不麻烦和不划算了,
      • 这个时候 runtime的关联属性就发挥有用处了
    @param object#> 给谁设置关联对象
    @param key#> 关联对象唯一的key, 获取时会用到.
    @param value#> 关联对象
    @param policy#> 关联策略
    @return void
             
    objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
    objc_getAssociatedObject(<#id object#>, <#const void *key#>)
    
    **示例代码**
    @interface UIButton (Mine)
    @property(nonatomic, strong) NSIndexPath *indexPath;
    @end
    
    #import "UIButton+Mine.h"
    #include <objc/runtime.h>
    @implementation UIButton (Mine)
    - (NSIndexPath *)indexPath {
        //这里的_cmd是OC中的一个关键字, 只能在方法中使用, 用来表示当前方法的地址, 等价于@selector(方法名称)的返回值.
        NSLog(@"_cmd = %p", _cmd);
        NSLog(@"indexPath = %p", @selector(indexPath));
        //
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (void)setIndexPath:(NSIndexPath *)indexPath {
        //这是我设置的关联对象的key值为indexpath方法地址.
        objc_setAssociatedObject(self, @selector(indexPath), indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    @end
    
    在其他地方引入该UIButton类扩展的头文件
    //这里使用了关联对象哦
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.indexPath = [NSIndexPath indexPathForRow:10 inSection:10];
    NSLog(@"indexpath = %ld", btn.indexPath.row);
    2016-12-09 17:01:03.093 Method_Swizzling01[56915:3702360] _cmd = 0x10b3ea0a4
    2016-12-09 17:01:03.093 Method_Swizzling01[56915:3702360] indexPath = 0x10b3ea0a4
    2016-12-09 17:01:03.095 Method_Swizzling01[56915:3702360] indexpath = 10
    

    2.动态获取类的信息

    #import <objc/runtime.h>
    #import <objc/message.h>
    #import "Person.h”
    void getDetailClass() {
        
        Person *person = [Person person];
        NSLog(@"[Person class] = %@", [person class]);
        
        Class personClass = [Person class];
        unsigned int outCount;
        //动态获取类中的成员变量
        Ivar *ivarList = class_copyIvarList(personClass, &outCount);
        for(int i = 0; i < outCount; i++) {
            Ivar ivar = ivarList[i];
            NSLog(@"成员变量的名称 = %s", ivar_getName(ivar));
        }
        
        //动态获取类中的属性
        objc_property_t *propertyList = class_copyPropertyList(personClass, &outCount);
        for(int i = 0; i < outCount; i++) {
            objc_property_t property = propertyList[i];
            NSLog(@"属性列表的名称 = %s", property_getName(property));
        }
        
        //动态获取方法列表
        Method *methodList = class_copyMethodList(personClass, &outCount);
        for(int i = 0; i < outCount; i++) {
            Method method = methodList[i];
            NSLog(@"方法列表的名称 = %@", NSStringFromSelector(method_getName(method)));
        }
    }
    

    3.交叉方法Method Swizzling

    +load在每个类被装载到Runtime的时候调用, 如import到当前执行文件中去的时候, 就会调用load方法.

    +initialize 在每个类第一次被发送消息的时候调用。只调用一次.

    dispatch_once是一次性执行代码

    + (void)load {
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            Method oldMethod = class_getInstanceMethod([self class], @selector(hasPrefix:));
            
            Method newMethod = class_getInstanceMethod([self class], @selector(dzHasPrefix:));
            
            method_exchangeImplementations(oldMethod, newMethod);
        });
    }
    

    4.动态添加方法 class_addMethod(self, SEL, IMP, 参数说明)

    //隐式调用方法, 
    [target performSelector:@selector(resolveAdd:) withObject:@"test"];
    //然后,在target对象内部重写拦截调用的方法,动态添加方法。
    void runAddMethod(id self, SEL _cmd, NSString *string){
        NSLog(@"add C IMP ", string);
    }
    //重写拦截调用的方法.进行动态添加方法.
    + (BOOL)resolveInstanceMethod:(SEL)sel{  
    //给本类动态添加一个方法
    if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
        //这里的”v@:*”, 表示的是 v=@encoding(void), @=encoding(id), :=encoding(SEL)
        //这里整体的”v@:*”就是表示方法的签名,代表有一个参数的意思.
         class_addMethod(self, sel, (IMP)runAddMethod, "v@:*”);
    }
    return YES;
    }
    

    相关文章

      网友评论

          本文标题:iOS中的runtime理解

          本文链接:https://www.haomeiwen.com/subject/wlfymttx.html