4.runtime的使用

2019-01-15  本文已影响14人  你weixiao的时候很美
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。

上一篇下一篇

猜你喜欢

热点阅读