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

2020-10-26  本文已影响0人  _zhang__

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.

上一篇下一篇

猜你喜欢

热点阅读