美文网首页
OC底层探索19、方法交换 swizzing

OC底层探索19、方法交换 swizzing

作者: _zhang__ | 来源:发表于2020-10-26 22:14 被阅读0次

    runtimemethod swizzing 其本质是方法交换,即 imp 的交换。但其使用过程中可能出现一些问题,本文对method swizzing进行简单的探究。

    一、方法交换的问题场景与处理方案

    1、+load方法中交换的问题

    任意创建一个iOS工程,代码准备. 子类MySubPerson添加分类,在分类中的+load调方法交换的方法:

    // 1、+load 中交换方法
    + (void)load {
        
        NSLog(@"%s",__func__);
        [My_RunTimeTool my_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceOne) swizzledSEL:@selector(my_subPersonInstanceOne)];
    }
    
    /// My_RunTimeTool 工具类
    // 1、
    + (void)my_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
        
        if (!cls) NSLog(@"传入的交换类不能为空");
    
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    

    ViewController:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        
        MySubPerson *subPerson = [[MySubPerson alloc] init];
        [subPerson personInstanceOne];
        [MySubPerson load];
        [subPerson personInstanceOne];
    
        MyPerson *person = [[MyPerson alloc] init];
        [person personInstanceOne];
    }
    

    运行工程,输出如下:

    Demo_appload[25656:2481009] +[ViewController load]
    Demo_appload[25656:2481009] +[MySubPerson(forSwizz) load]
    Demo_appload[57169:6022736] 来了 C++ : myFunc 
    Demo_appload[25656:2481009] main 函数进入
    Demo_appload[25656:2481009] -[MySubPerson personInstanceOne]
    Demo_appload[25656:2481009] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[25656:2481009] +[MySubPerson(forSwizz) load]
    Demo_appload[25656:2481009] -[MySubPerson personInstanceOne]
    Demo_appload[25656:2481009] -[MyPerson personInstanceOne]
    

    这里除了个问题,我们其实要实现MySubPerson执行方法:my_subPersonInstanceOne,但是load的多次调用,使方法多次交换,交换成了并非我们所需要的。so 要进行 once处理:

    + (void)load {
        
        NSLog(@"%s",__func__);
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [My_RunTimeTool my_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceOne) swizzledSEL:@selector(my_subPersonInstanceOne)];
        });
    }
    

    再次运行工程,MySubPerson每次都是执行my_subPersonInstanceOne,实现需求:

    Demo_appload[25656:2481009] -[MySubPerson personInstanceOne]
    Demo_appload[25656:2481009] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[25656:2481009] +[MySubPerson(forSwizz) load]
    Demo_appload[25656:2481009] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[25656:2481009] -[MyPerson personInstanceOne]
    

    但是,我们一直+load方法的实现,会使类提前加载,对启动造成慢。
    so 使用在+initialize而非+load进行一系列处理。

    + (void)initialize
    {
        if (self == [super class]) {
            NSLog(@"%s",__func__);
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                [My_RunTimeTool my_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceOne) swizzledSEL:@selector(my_subPersonInstanceOne)];
            });
        }
    }
    

    2、父类实现 - 子类中不实现oriMethod方法的场景

    运行工程,crash 了,信息如下:

    Demo_appload[26037:2504023] -[MyPerson personInstanceOne]
    Demo_appload[26037:2504023] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[25970:2499447] -[MyPerson my_subPersonInstanceOne]: unrecognized selector sent to instance 0x600002b3b6c0
    Demo_appload[25970:2499447] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyPerson my_subPersonInstanceOne]: unrecognized selector sent to instance 0x600002b3b6c0'
    

    这是什么原因呢?
    还记得方法查找流程吗?子类未实现便会一直找向父类,所以,方法交换时,父类MyPersonimp变了,它指向了my_subPersonInstanceOne,子类中有实现正常执行,但父类中并未实现my_subPersonInstanceOne,so 找不到方法-->崩溃。
    处理方案:

    /// My_RunTimeTool 工具类
    // 2、父类实现 子类未实现方法
    + (void)my_resolve_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
        
        if (!cls) NSLog(@"传入的交换类不能为空");
    
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        // 尝试添加我们要交换的方法 swiMethod: my_subPersonInstanceOne
        BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
        
        if (success) {// 加成功了,那么就是自己没有方法:swiMethod - 用父类的自己的方法替换
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{ // 自己有,正常交换
            method_exchangeImplementations(oriMethod, swiMethod);
        }
    }
    

    再次运行工程,成功执行:

    来了 C++ : myFunc 
    Demo_appload[26168:2512856] main 函数进入
    Demo_appload[26168:2512856] +[MySubPerson(forSwizz) initialize]
    Demo_appload[26168:2512856] -[MyPerson personInstanceOne]
    Demo_appload[26168:2512856] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[26168:2512856] -[MyPerson personInstanceOne]
    

    3、父类子类都未实现oriMethod

    运行工程,执行到下面位置:

    来了 C++ : myFunc 
    Demo_appload[26233:2520202] main 函数进入
    Demo_appload[26233:2520202] +[MySubPerson(forSwizz) initialize]
    

    过了一会儿崩溃了在了下图位置:

    image.png

    递归循环溢出了,但是为何上面2中场景未造成 crash 呢?
    --> 1、文章上面的场景 1、2中,my_subPersonInstanceOneimp是指向personInstanceOne的,方法查找时找的是personInstanceOne,找到与否都会抛出结果;
    --> 2、但场景3personInstanceOne并未实现,没有impmy_subPersonInstanceOne一直自己指自己,递归至溢出崩溃。
    如何处理这中场景呢?代码如下:

    // 3、父类子类未实现 oriSEL
    + (void)my_best_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
        
        if (!cls) NSLog(@"传入的交换类不能为空");
    
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        if (!oriMethod) {// 避免没有 oriMethod 导致无意义交换
            // 在 oriMethod 为 nil 时,替换后将 swizzledSEL 赋值一个不做任何事的空实现(这里做个打印),代码如下:
            class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
            method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
                NSLog(@"来了一个空的 imp");
            }));
        }
        
        // 尝试添加我们要交换的方法 swiMethod: my_subPersonInstanceOne
        BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
        
        if (success) {// 加成功了,那么就是自己没有方法:swiMethod - 用自己替换
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{ // 自己有,正常交换
            method_exchangeImplementations(oriMethod, swiMethod);
        }
    }
    

    判断一下oriMethod,若不存在,给swiMethod一个空的实现,再次运行结果如下,崩在MyPerson找不到personInstanceOne

    Demo_appload[26619:2549842] main 函数进入
    Demo_appload[26619:2549842] +[MySubPerson(forSwizz) initialize]
    Demo_appload[26619:2549842] -[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[26619:2549842] 来了一个空的 imp
    Demo_appload[26619:2549842] MySubPerson 分类添加的对象方法:-[MySubPerson(forSwizz) my_subPersonInstanceOne]
    Demo_appload[26619:2549842] -[MyPerson personInstanceOne]: unrecognized selector sent to instance 0x6000021843b0
    Demo_appload[26619:2549842] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyPerson personInstanceOne]: unrecognized selector sent to instance 0x6000021843b0'
    

    二、 api 源码

    1、method_exchangeImplementations()源码:

    void method_exchangeImplementations(Method m1, Method m2)
    {
        if (!m1  ||  !m2) return;
    
        mutex_locker_t lock(runtimeLock);
        // imp 交换
        IMP m1_imp = m1->imp;
        m1->imp = m2->imp;
        m2->imp = m1_imp;
    
    
        // 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);
    
        adjustCustomFlagsForMethodChange(nil, m1);
        adjustCustomFlagsForMethodChange(nil, m2);
    }
    

    2、class_replaceMethod()源码:

    IMP 
    class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    {
        if (!cls) return nil;
    
        mutex_locker_t lock(runtimeLock);
        // 添加 Method
        return addMethod(cls, name, imp, types ?: "", YES);
    }
    

    addMethod()

    /**********************************************************************
    * addMethod
    * fixme
    * Locking: runtimeLock must be held by the caller
    **********************************************************************/
    static IMP 
    addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
    {
        IMP result = nil;
    
        runtimeLock.assertLocked();
    
        checkIsKnownClass(cls);
        
        ASSERT(types);
        ASSERT(cls->isRealized());
    
        method_t *m;
        if ((m = getMethodNoSuper_nolock(cls, name))) {
            // already exists
            if (!replace) {
                result = m->imp;
            } else {
                // 已存在,要替换
                // m: my_subPersonInstanceOne
                // imp: personInstanceOne
                result = _method_setImplementation(cls, m, imp);
            }
        } else {
            auto rwe = cls->data()->extAllocIfNeeded();
    
            // fixme optimize
            method_list_t *newlist;
            newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
            newlist->entsizeAndFlags = 
                (uint32_t)sizeof(method_t) | fixed_up_method_list;
            newlist->count = 1;
            newlist->first.name = name;
            newlist->first.types = strdupIfMutable(types);
            newlist->first.imp = imp;
    
            prepareMethodLists(cls, &newlist, 1, NO, NO);
            rwe->methods.attachLists(&newlist, 1);
            flushCaches(cls);
    
            result = nil;
        }
    
        return result;
    }
    

    _method_setImplementation()

    /***********************************************************************
    * method_setImplementation
    * Sets this method's implementation to imp.
    * The previous implementation is returned.
    **********************************************************************/
    static IMP 
    _method_setImplementation(Class cls, method_t *m, IMP imp)
    {
        runtimeLock.assertLocked();
    
        if (!m) return nil;
        if (!imp) return nil;
    
        // m: my_subPersonInstanceOne
        // imp: personInstanceOne
        IMP old = m->imp;
        m->imp = imp;// 使 m 指向 personInstanceOne 的 imp
    
        // Cache updates are slow if cls is nil (i.e. unknown)
        // RR/AWZ updates are slow if cls is nil (i.e. unknown)
        // fixme build list of classes whose Methods are known externally?
    
        flushCaches(cls);
    
        adjustCustomFlagsForMethodChange(cls, m);
    
        return old;
    }
    

    class_replaceMethod执行流程:
    class_replaceMethod() --> addMethod() --> _method_setImplementation() - 使 swizzMethod 的 imp指向 personInstanceOne 的 imp.

    相关文章

      网友评论

          本文标题:OC底层探索19、方法交换 swizzing

          本文链接:https://www.haomeiwen.com/subject/ddhamktx.html