iOS - Runtime Method Swizzling(上)

简介: Runtime合集iOS - Runtime基础

个人写的一段代码,建议在了解Swizzling时,查看Runtime 以下方法源码


我为你准备好了


class_getInstanceMethod

class_addMethod

class_replaceMethod

method_exchangeImplementations

+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Class msclass = cls;
    // 获取当前类中的Org SEL (当前方法不存在,class_getInstanceMethod会去superClass查找)
    Method originalMethod = class_getInstanceMethod(msclass, originalSelector);
    // 获取当前类中的New SEL
    Method swizzledMethod = class_getInstanceMethod(msclass, swizzledSelector);
    // 尝试将Org SEL 添加到Class 中,IMP使用New SEL 的IMP,添加成功返回true,否则false(当前方法存在)
    // class_addMethod 如果父类存在Org SEL,则会添加新方法,相当于重写父类Org SEL
    BOOL didAddMethod =
    class_addMethod(msclass,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {·//添加成功,之前不存在 Org SEL
        // 将New SEL的方法实现IMP 替换成 Org
        class_replaceMethod(msclass,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        //直接交换IMP的方法,如果父类存在Org,子类不存在,则会替换父类的Org SEL
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}


1. Method Swizzling是什么


通常我们叫它方法交换方法欺骗


Method Swizzling用于改变一个已经存在的selector实现,在程序运行时,通过改变selector和所在类的methodLists的映射从而改变方法的调用。其实质就是交换两个方法的IMP(方法实现)


上篇文章我们知道:Method(方法)对应的是objc_Method结构体,而objc_method结构体中包含了SEL method_name(方法名)IMP(方法实现)

struct objc_method {
    SEL _Nonnull method_name;
    char * _Nullable method_types;
    IMP _Nonnull method_imp;
};


Method(方法)SEL(方法名)IMP(方法实现)三者的关系可以这样来表示:

在运行时,Class(类)维护了一个method list(方法列表)来确定消息的正确发送。method list(方法列表)存放的元素就是Method(方法)。而Method(方法)中映射了一对键值对:SEL(方法名):IMP(方法实现)


Method Swizzling修改了method list(方法列表),使得不同Method(方法)中的键值对发生了交换。比如交换前两个键值对分别为SEL A :IMP ASEL B :IMP B,交换之后就变成了SEL A :IMP BSEL B : IMP A

如图所示:


image.png

Swizzling 交换示意图


2. Method Swizzling 底层源码


(Runtime源码下载)[https://opensource.apple.com/tarballs/objc4/]


源码位于objc-class-old.m

void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
    IMP m1_imp;
    old_method *m1 = oldmethod(m1_gen);
    old_method *m2 = oldmethod(m2_gen);
    if (!m1  ||  !m2) return;
    impLock.lock();
    m1_imp = m1->method_imp;
    m1->method_imp = m2->method_imp;
    m2->method_imp = m1_imp;
    impLock.unlock();
}


objc-runtime-new.m

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;
    mutex_locker_t lock(runtimeLock);
    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();
    m1->setImp(imp2);
    m2->setImp(imp1);
    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?
    flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });
    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}


3. Method Swizzling使用方法


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


4. Method Swizzling 使用注意事项


  1. 应该只在 +load 中执行 Method Swizzling
    程序在启动的时候,会先加载所有的类,这时会调用每个类的 +load 方法。而且在整个程序运行周期只会调用一次(不包括外部显示调用)。所以在 +load 方法进行 Method Swizzling 再好不过了。


而为什么不用+initialize 方法呢。


因为+initialize 方法的调用时机是在 第一次向该类发送第一个消息的时候才会被调用。如果该类只是引用,没有调用,则不会执行 +initialize 方法。


Method Swizzling 影响的是全局状态,+load方法能保证在加载类的时候就进行交换,保证交换结果。而使用 +initialize 方法则不能保证这一点,有可能在使用的时候起不到交换方法的作用。


  1. Method Swizzling 在 +load 中执行时,不要调用 [super load];。


上边我们说了,程序在启动的时候,会先加载所有的类。如果在 + (void)load方法中调用 [super load]方法,就会导致父类的Method Swizzling 被重复执行两次,而方法交换也被执行了两次,相当于互换了一次方法之后,第二次又换回去了,从而使得父类的Method Swizzling 失效。


  1. Method Swizzling 应该总是在 dispatch_once 中执行


Method Swizzling 不是原子操作,dispatch_once 可以保证即使在不同的线程中也能确保代码只执行一次。所以,我们应该总是在 dispatch_once 中执行 Method Swizzling 操作,保证方法替换只被执行一次。


相关文章
|
API iOS开发
iOS面试关于runtime
iOS面试关于runtime
112 0
15-iOS之Runtime常用API以及使用
15-iOS之Runtime常用API以及使用
93 0
|
编译器 iOS开发
iOS Runtime详细介绍及实战使用(二)
iOS Runtime详细介绍及实战使用
 iOS Runtime详细介绍及实战使用(二)
|
API iOS开发
iOS Runtime详细介绍及实战使用(一)
iOS Runtime详细介绍及实战使用
|
iOS开发
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
138 0
|
JSON 前端开发 JavaScript
iOS Principle:Runtime(下)
iOS Principle:Runtime(下)
94 0
iOS Principle:Runtime(下)
|
缓存 编译器 Swift
iOS Principle:Runtime(中)
iOS Principle:Runtime(中)
169 0
iOS Principle:Runtime(中)
|
存储 缓存 编译器
iOS Principle:Runtime(上)
iOS Principle:Runtime(上)
112 0
iOS Principle:Runtime(上)
|
iOS开发
iOS - Runtime 动态添加属性
我们在开发中常常使用类目Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法,而且相比类目更是具有增加属性的优势,但是继承毕竟是一个重量级的操作,添加不必要的继承关系无疑增加了代码的复杂度。