Objective-C Runtime 初识(三)

By | 2016-03-07

学习完 Runtime 的基本知识,接下来看一下 Runtime 在实际开发中的一些应用场景。

Method Swizzling

Runtime 中最常用的一个功能了吧,在运行时将两个 Method 替换,利用这个特性可以在 OC 开发中实现 AOP。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(_my_viewWillAppear:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        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);
        }
    });
}

- (void)_my_viewWillAppear:(BOOL)animated {
    [self _my_viewWillAppear:animated];
    NSLog(@"%@ %s", self, __func__);
}
注意
  • Swizzling 应该在 + (void)load 中执行,相比较于 + (void)initialize ,前者能保证在类初始加载时调用,而后者是懒加载的调用方式,只有第一次对类发送消息才会调用。
  • + (void)load 方法中不应该调用 [super load]
  • Swizzling 应该在 dispatch_once 中执行,保证不管有多少线程都只被执行一次。
  • 需要先判断被替换的方法有没有被当前类实现还是被当前类的父类实现的。

KVO

KVO 的本质就是替换了对象的 isa 指向,将对象的 isa 指向了动态创建的一个子类上,之后对象调用对象方法实质上是去查找这个动态创建的子类的方法列表,利用这一点 KVO 重写了需要实现监听属性的 set 方法,在 set 方法前后添加了下面的方法:

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

Associated Object

开发中经常用到分类,应该都遇到过需要给分类添加一个属性,但是正常情况下 OC 不允许在分类中添加成员变量,所以即使在分类中添加了 @property 也并不会正常的生成存取方法和成员变量,只能手动的去给它实现存取方法,并给它关联一个对象。

@interface NSObject (AssociatedObject)

@property (nonatomic, strong) id associcatedObject;

@end

@implementation NSObject (AssociatedObject)

- (id)associcatedObject {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setAssocicatedObject:(id)associcatedObject {
    objc_setAssociatedObject(self, @selector(associcatedObject), associcatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

代码中用到了 _cmd@selector(associcatedObject) 作为关联对象的 key ,其中 _cmd 是方法的隐藏参数,和 @selector(associcatedObject) 一样都是一个 SEL 类型,获取的是一个 C 语言字符串,所以用在这里可以更加简洁。

OBJC_ASSOCIATION_RETAIN_NONATOMIC 是关联对象的存储策略,相当于 @property(nonatomic, strong)

最后

Runtime 还有很多应用场景,需要学习的知识也很多,这次初步了解记录了目前能理解的,在今后知识面扩充之后有时间应该会再识 Runtime。

References: