iOS-Swizzle

最后更新:2017-06-21

一、先说结论

void swizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
    
    BOOL didAddMethod =
    class_addMethod(cls,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

二、代码分析

2.1 class_getInstanceMethod()

获取某个类实例的方法, 如果该类实例没有此方法, 则返回NULL

Method swizzleMethod = class_getInstanceMethod([Person class], @selector(run));
if (swizzleMethod == NULL) {
    NSLog(@"NULL");
}

参数解释

class_getInstanceMethod(Class cls, SEL name)
cls: 获取方法的类
name: 方法的名称

2.2 class_addMethod()

image_1bj4peu581pm5t5r1k24uu21e8l13.png-49.9kB

参数解释

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

cls: 获取方法的类
name: 添加的方法方法的名称
imp: 方法的实现,也就一个指向方法的指针
const char *types: 定义了返回值类型和参数类型的字符串(下面会提到)

返回值

YES: 增加方法成功
NO: 增加方法失败,例如 (如果目标类(Person) 实现了该方法,那么会返回 NO)

注意点

  • class_addMethod 能够覆盖父类的实现的;
    如果目标类有实现了该方法,class_addMethod就会失败

class_addMethod will add an override of a superclass's implementation

  • 处理警告问题
    参考: https://stackoverflow.com/questions/6224976/how-to-get-rid-of-the-undeclared-selector-warning

    void sayHello(id self, SEL _cmd, NSString *word)
    {
        NSLog(@"%@", word);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        class_addMethod([Person class], @selector(resolveThisMethodDynamically:), (IMP)sayHello, "v@:@");
        Person *p = [[Person alloc] init];
        [p performSelector:@selector(resolveThisMethodDynamically:) withObject:@"hello"];
    #pragma clang diagnostic pop
    
    }
    
    
  • 需要动态调用,因为通过运行时添加的方法,直接调用 编译不过的

    正确做法:
    [p performSelector:@selector(resolveThisMethodDynamically:) withObject:@"hello"];
    
    错误做法,编译不过
    [p resolveThisMethodDynamically:@"hello"];
    
  • imp 默认自带两个参数, id类型 以及 SEL 类型

    void sayHello(id self, SEL _cmd, ...)
    

2.3 参数 const char *types 解释

v 表示的是void 类型
i 表示整数类型
@ 表示一个对象
: 表示一个方法

v@: 表示的是返回值类型是void, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd)

v@😡 表示的是返回值类型是void, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd), 还有一个参数是对象(NSString *word)

i@: 表示范围值是 int, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd)

可以按照下表来查

image_1bj4qlcd318r318i01an07qh1ieg1t.png-75kB

参考: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100

2.4 言归正传 Swizzle 的讨论

简单的,交换方法,我们仅仅需要如下操作

Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);

method_exchangeImplementations(originalMethod, swizzledMethod);
    

但是, 我们仅仅解决了一种情况

周全起见,有两种情况要考虑一下。
第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。
第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。

(译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。)

对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。

摘自: http://blog.csdn.net/horkychen/article/details/8532087

因此,上面简单的交换方法,仅仅能处理第二种 已经存在于目标类的方式

完整的解决方式如下

void swizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
    
    BOOL didAddMethod =
    class_addMethod(cls,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
posted @ 2017-06-21 21:10  洒水先生  阅读(495)  评论(0编辑  收藏  举报