美文网首页
4.runtime的使用

4.runtime的使用

作者: 你weixiao的时候很美 | 来源:发表于2019-01-15 12:09 被阅读14次
    1. 一些类的含义:
    1. SEL:选择子,用于在运行时表示 方法的名称 ,是已经在Objective-C运行时注册(或“映射”)的C字符串。

    在使用选择器时,必须使用从sel_registerName或Objective-C编译器指令@selector()返回的值。您不能简单地将C字符串转换为SEL。

    typedef struct objc_selector *SEL;
    
    1. IMP:指向 方法实现 开始的指针
      函数有2个参数,第一个是指向self的指针,第二个是方法的选择子。

    2. Method:方法,表示类定义范畴下方法的定义。

    typedef struct objc_method *Method;
    
    1. NSMethodSignature :方法的返回值和参数的类型信息的记录

    使用NSObject methodSignatureForSelector: 来获取methodSignature。

    1. NSInvocation :作为对象呈现的Objective-C消息。

    An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value.
    包含消息的所有要输。

    2. runtime的使用
    1. 实现多继承,Multiple Inheritance
    2. Method Swizzling
    3. Aspect Oriented Programming
    4. isa Swizzling
    5. Associated Object 关联对象
    6. 动态的增加方法
    7. NSCoding的自动归档和解档
      8.字典模型相互转换
    一: 实现多继承 Multiple Inheritance

    OC程序中可以借用消息转发机制来实现多继承的功能。


    用 forwardingTargetForSelector: 和 forwardInvocation:方法。上图中,
    在图中,warrior实例转发了一个negotiate消息到Diplomat实例中,执行Diplomat中的negotiate方法,结果看起来像是warrior实例执行了一个和Diplomat实例一样的negotiate方法,其实执行者还是Diplomat实例。

    这使得不同继承体系分支下的两个类可以“继承”对方的方法,这样一个类可以响应自己继承分支里面的方法,同时也能响应其他不相干类发过来的消息。在上图中Warrior和Diplomat没有继承关系,但是Warrior将negotiate消息转发给了Diplomat后,就好似Diplomat是Warrior的超类一样。

    消息转发和多继承的不同:
    多继承:合并了不同的行为特征在一个单独的对象中,会得到一个重量级多层面的对象。
    消息转发:将各个功能分散到不同的对象中,得到的一些轻量级的对象,这些对象通过消息转发联合起来。

    即使我们利用转发消息来实现了“假”继承,但是NSObject类还是会将两者区分开。像respondsToSelector:和 isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链。

    二: Method Swizzling
    1.method swizzling 原理

    Method Swizzling是发生在运行时的,主要用于在运行时将两个Method进行交换,Method Swizzling本质上就是对IMP和SEL进行交换。

    2. method Swizzling的使用代码

    代码模板如下

     #import <objc/runtime.h>
    @implementation UIViewController (Swizzling)
    
     // 1.在load 方法中实现。
    + (void)load {     
          //2. 使用dispatch_once;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            // 3. 获取SEL
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(xxx_viewWillAppear:);
    
           // 4. 获取Method
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            // 5. 交换方法实现。
            BOOL didAddMethod = class_addMethod(class,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    #pragma mark - Method Swizzling
    - (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"viewWillAppear: %@", self);
    }
    @end
    
    
    3. method Swizzling 注意点
    1. 主要是用在分类中,交换类中的某个方法的实现, 在保证原来方法实现的基础上,添加一些自己的功能。而不用继承的方式。

    2. Objective-C在运行时会自动调用类的两个方法+load和+initialize。+load会在类初始加载时调用, +initialize方法是以懒加载的方式被调用的

    3.Swizzling应该总是在dispatch_once中执行
    Swizzling会改变全局状态,所以在运行时采取一些预防措施,使用dispatch_once就能够确保代码不管有多少线程都只被执行一次。这将成为Method Swizzling的最佳实践。

    4.method Swizzling 的使用场景。
    1. 实现aop

    2. 实现埋点统计。

    3. 实现异常保护,比如hook NSArray的objectAtIndex方法,对越界做处理。

    三。Aspect Oriented Programming

    AOP定义: 面向切片编程
    类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern),AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。

    我们使用消息转发机制 和 method Swizzling 来达到aop的目的。

    四: Isa Swizzling

    黑魔法Method Swizzling,本质上就是对IMP和SEL进行交换。Isa Swizzling本质上也是交换,不过交换的是Isa。

    苹果使用的 Key-Value Observing 即KVO也用到了Isa Swizzling。

    KVO是为了监听一个对象的某个属性值是否发生变化。在属性值发生变化的时候,肯定会调用其setter方法。所以KVO的本质就是监听对象有没有调用被监听属性对应的setter方法。具体实现应该是重写其setter方法即可。

    KVO在调用addObserver方法之后,苹果的做法是在执行完addObserver: forKeyPath: options: context: 方法之后,把isa指向到另外一个类去。NSKVONotifying_XXX。
    在这个新类里面重写被观察的对象四个方法。class,setter,dealloc,_isKVOA。

    1. 重写class方法:

    重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容。

    2.重写setter方法:

    在新的类中会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:

    - (void)willChangeValueForKey:(NSString *)key
    - (void)didChangeValueForKey:(NSString *)key
    

    在didChangeValueForKey:方法再调用:

    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context
    

    注意: 如果使用了点语法,会在setter中调用will/didChangeValueForKey:方法。

    如果没有使用点方法,而使用KVC,运行时会在setValue:forKey方法中调用will/didChangeValueForKey:方法。 也可以触发KVO。

    直接调用will/didChangeValueForKey:方法。也可以触发KVO。

    3. 重写dealloc方法

    销毁新生成的NSKVONotifying_类

    4.重写_isKVOA方法

    这个私有方法估计可能是用来标示该类是一个 KVO 机制声称的类。

    5.KVO的缺陷:

    主要问题在KVO的回调机制,不能传一个selector或者block作为回调,而必须重写-addObserver:forKeyPath:options:context:方法所引发的一系列问题。而且只监听一两个属性值还好,如果监听的属性多了, 或者监听了多个对象的属性, 那有点麻烦,需要在方法里面写很多的if-else的判断。

    五:Associated Object关联对象

    通过关联对象来实现在 Category 中添加属性的功能了。

    1.用法
    // NSObject+AssociatedObject.h
    @interface NSObject (AssociatedObject)
    @property (nonatomic, strong) id associatedObject;
    @end
    
    // NSObject+AssociatedObject.m
    @implementation NSObject (AssociatedObject)
    @dynamic associatedObject;
    
    - (void)setAssociatedObject:(id)object {
        objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (id)associatedObject {
        return objc_getAssociatedObject(self, @selector(associatedObject));
    }
    

    参数的含义:

    1. id object 设置关联对象的实例对象

    2. const void *key 区分不同的关联对象的 key。

    3.id value 关联的对象

    4.objc_AssociationPolicy policy 关联对象的存储策略,它是一个枚举,与property的attribute 相对应。

    注意1:标记成OBJC_ASSOCIATION_ASSIGN的关联对象和
    @property (weak) 是不一样的,上面表格中等价定义写的是 @property (unsafe_unretained),对象被销毁时,属性值仍然还在。

    注意2: objc_removeAssociatedObjects。这个方法是移除源对象中所有的关联对象,并不是其中之一。所以其方法参数中也没有传入指定的key。要删除指定的关联对象,使用 objc_setAssociatedObject 方法将对应的 key 设置成 nil 即可。

    2.关联对象的原理

    使用AssociationsManager 来管理2张哈希表:
    第一张AssociationsHashMap: key为object的地址的指针,value为一张ObjcAssociationMap。
    第二张ObjcAssociationMap表:key值是set方法里面传过来的形参const void *key,value为ObjcAssociation对象。
    ObjcAssociation对象中存储了set方法最后两个参数,policy和value。

    所以set方法的四个参数的值都存入了表中:


    所以set 和get 和remove 就是对这些的操作过程。

    六 动态的增加方法

    在消息发送时,如果在父类中也没有找到相应的IMP,就会执行resolveInstanceMethod方法,进行动态方法解析。在这个方法里面,我们可以动态的给类对象或者实例对象动态的增加方法。

    + (BOOL)resolveInstanceMethod:(SEL)sel {
        
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString isEqualToString:@"method1"]) {
            class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
        }
        
        return [super resolveInstanceMethod:sel];
    }
    
    七 : NSCoding 自动归档 和解档。

    用runtime实现的思路是:我们循环依次找到每个成员变量的名称,然后利用KVC读取和赋值就可以完成encodeWithCoder和initWithCoder了。

    八: 字典模型互换

    几个出名的开源库JSONModel、MJExtension等都是通过这种方式实现的。
    字典转模型:
    1.利用runtime的class_copyIvarList获取属性数组,
    2.遍历模型对象的所有成员属性,根据属性名找到字典中key值进行赋值,当然这种方法只能解决NSString、NSNumber等,
    3.如果含有NSArray或NSDictionary,还要进行第二步转换,如果是字典数组,需要遍历数组中的字典,利用objectWithDict方法将字典转化为模型,在将模型放到数组中,最后把这个模型数组赋值给之前的字典数组。

    模型转字典步骤差不多。

    9.runtime的缺点:

    Method swizzling不是原子性操作。如果在+load方法里面写,是没有问题的,但是如果写在+initialize方法中就会出现一些奇怪的问题。

    难以理解和debug。

    相关文章

      网友评论

          本文标题:4.runtime的使用

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