iOS底层-20:Method-Swizzling

相信大家对Method-Swizzling并不陌生,今天我们来聊一聊Method-Swizzling的一些坑点和优化。

简单使用
+ (void)lg_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);
}

接着我们在类中调用lg_methodSwizzlingWithClass交换方法即可

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}
  • 为避免交换类主动调用+ load,重复交换,使用单例设计
  • 重写了+ load方法,会消耗启动性能,加重启动负担,也可以在 + initialize方法中交换

代码:
LGPerson

@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person对象方法:%s",__func__);
    
}
+ (void)personClassMethod{
    NSLog(@"person类方法:%s",__func__);
}

@end

LGStudent

@interface LGStudent : LGPerson
- (void)helloword;
@end

@implementation LGStudent 

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}
- (void)lg_studentInstanceMethod{
    NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
    [self lg_studentInstanceMethod];
}
@end

viewDidLoad

- (void)viewDidLoad {
    [super viewDidLoad];

    LGStudent *s = [[LGStudent alloc] init];
    [s helloword];
 
}

问题一:lg_studentInstanceMethod里面调用lg_studentInstanceMethod会不会产生递归?
答案:不会,在+ load方法中,lg_studentInstanceMethod已经和helloword交换了imp,里面调用的其实是helloword方法。

问题二:helloword方法并没有实现,会出什么问题?


答案:会报错崩溃,找不到helloword方法。

下面我们查看一些底层源码,看看问题的产生原因:

  • class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    //查找一次imp,参数为LOOKUP_RESOLVER,尝试method resolver
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    //通过sel cls 拿到Method  这是主要方法
    return _class_getMethod(cls, sel);
}

  • _class_getMethod
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
  • getMethod_nolock
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;

    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    //递归父类查找
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}

方法慢速查找流程前面的文章详细分析过了。传送门
总结:
方法没有实现的时候,class_getInstanceMethod会返回空值。

下面看一下交换的方法

  • 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;
    //交换完成后,删除缓存中的数据
    flushCaches(nil);

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}

与之对应的还有一个class_replaceMethod

  • 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);
}

优化

在交换之前,要判断Method是否存在,做对应的处理

+ (void)lg_bestMethodSwizzlingWithClass:(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为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        class_addMethod(cls, oriSEL, imp_implementationWithBlock(^(id self, SEL _cmd){ NSLog(@"%@ has not implementation",_cmd); }), method_getTypeEncoding(oriMethod));
        oriMethod = class_getInstanceMethod(cls, oriSEL);
    }
    if (!swiMethod) {
        class_addMethod(cls, swizzledSEL, imp_implementationWithBlock(^(id self, SEL _cmd){ NSLog(@"%@ has not implementation",_cmd); }), method_getTypeEncoding(swiMethod));
        swiMethod = class_getInstanceMethod(cls, swizzledSEL);

    }
    method_exchangeImplementations(oriMethod, swiMethod);
}

判断oriMethodswiMethod是否存在,不存在的话给他们添加一个空的实现。然后再进行交换。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容