Swizzling方法

swizzling是一种能够改变已存在的selector的实现的方法,它通过改变selectors在类的映射表中的映射方式修改Objective-C的方法调用。

举个例子,如果我们想追踪各个viewController对用户展示了多少次,我们可以在每个viewController的viewDidAppear:中添加追踪代码,但是那样会产生大量的重复代码块。也可以通过继承的方式来实现,但是这种方式会要求继承UIViewController、UITableViewController、UINavigationController等其他任何的viewController类,这种方式也会产生重复代码。

幸运的是,我们可以在代理中添加swizzling方法来做到,而没有上述的问题:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dospatch_onec(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);   
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        /*
        * 当调整一个类方式的时候使用下面的实现方式:
        * Class class = object_getClass((id)self);
        * ...
        * Method originalMethod = class_getClassMethod(class, originalSelector);
        * Method swizzledMethod = class_getClassMethod(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);
        }
    });
}

#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

现在当任何UIViewController的实例或者子类调用viewWillAppear:都会打印出日志。
使用swizzling方法往视图控制器的生命周期、响应时间、视图绘画或者网络堆栈库里面注入行为具有很长好的效果。当然还有很多适用swizzling技术的场景。

+load vs. +initialize

Swizzling只能在+load方法中实现

在类中,只有两个方法会被运行时自动调用:
* 当类初始加载的时候会调用+load
* 当类或者对象被第一次使用时会调用+initialize
要实现Swizzling方法,+load和+initialize都是可选的,只要实现了方法,就会被执行。但是因为swizzling方式会影响全局的状态,所以尽量减少竟态条件是非常重要的。类在初始化的时候,+load是必然会被执行的,这在改变系统范围的行为的时候提供了一定的一致性。相较之下,+initalize就没有这方面的保证,它有可能永远不会被执行(只有被实例化的时候才会执行)。

dispatch_once

Swizzling应该在dispatch_once中实现

重复强调一下,因为swizzling会改变全局的状态,所以我们需要采取所有的可以采取的预防措施。原子操作就是一种预防措施,它确保了其中的代码只会被执行一次,即使在在不同的线程中。

Selectors,Methods,&Implementations

在Objective-C中,selectors,methods和implementations分别代表了runtime的几个方面,然而在消息发送的过程中,这些概念可以相互转换。
下面是Apple'sObjective-C运行时的官方描述:

Selector(typedef struct objc_selector *SEL):在runtime,Selectors代表方法的名称,selector方法是objective-C在运行时注册(或者映射)的一个C字符串,在类加载的时候,它由编辑器生成,并由runtime自动生成映射。

Method(typedef struct objc_method *Method):在类的定义中代表一个方法的未知类型。

Implementation(typedef id (*IMP)(id, SEL, ...)):这个数据类型是一个指向实现该方法的函数的开始地址,这个函数为当前的CPU架构使用标准的C语言协议。第一个参数是一个指向自身的指针(类的实例地址或者指向元类);第二个参数是selector方法。

理解这些概念的最好方式是:类维维护着一个消息分发列表,表中的入口是方法(Method),方法标记着selector的名称和IMP的指针。

swizzle方法是改变类的分发表中的已存在的selector和implementation的映射关系,使selector与implementation组成新的映射关系。

Invoking _cmd

下面代码可能会导致无限循环:

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillApear: %@", NSStringFromClass([self class]));
}

然而,并没有。在swizzling的实现过程中,xxx_viewWillAppear:已经被替换成了-viewWillAppear:,如果将[self xxx_viewWillAppear:animated]改成-viewWillAppear:则会发生死循环

记得给swizzled方法添加前缀是一个好的习惯。

总结

很多人认为Swizzling是一种不可靠的技术,容易产生不可预料的行为和后果。但是当做了如下处理后,它将变得非常可靠:
* 总是调用原生方法(除非你有更好的理由不去做):APIs提供了输入输出的协议,但是implementation却没有公开,是一个黑盒子。使用Swizzling方法后,而不是用原生的方法将会导致底层不可预知的崩溃。
* 避免冲突:分类方法使用前缀,确保其它代码库或者依赖库没有实现和你一样的功能。
* 理解原理
* 保持谨慎

Method Swizzling

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页