“这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战”编程
咱们都知道,类的最终父类是NSObject
,在程序编译后,在底层能够发现类就是一个结构体,每一个类都有一个 isa
指针,可以访问到结构体里面的数据。方法查找的是时候,是在类的方法列表里面,经过SEL
查找对应的IMP
。安全
你们或多或少听到过iOS黑魔法
,也就是方法交换
。同时苹果的运行时 runtime
也提供了一个很好的环境。利用OC
的Runtime
特性,动态改变SEL
(方法编号)和IMP
(方法实现)的对应关系,达到OC
方法调用流程改变的目的。主要用于OC
方法。下面用两个图例来展现下:性能优化
根据这两张图,咱们能稍微明白些这个交换的是怎么一回事。Runtime
提供了交换两个SEL
和IMP
对应关系的函数:markdown
OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
Runtime
机制对于AOP
面向切面编程提供良好的支持。在OC
中,可利用Method Swizzling
实现AOP
,其中AOP
(Aspect Oriented Programming
)是一种编程的思想,一样面向对象编程OOP
也一种编程的思想,可是AOP
和OOP
有本质的区别:函数
OOP
编程思想,他更加倾向于对业务模块的封装,同时也可以划分出更为清晰的业务逻辑单元;AOP
编程思想,是面向切面进行提取封装,提取各个模块中的公共部分,这样能提升模块的复用率,下降业务之间的耦合性;API
介绍SEL
获取方法Method
:获取实例方法
post
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码
获取类方法
性能
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
复制代码
IMP
的getter/setter
方法:获取某个方法的实现
优化
OBJC_EXPORT IMP _Nonnull
method_getImplementation(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
设置一个方法的实现
编码
OBJC_EXPORT IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
OBJC_EXPORT const char * _Nullable
method_getTypeEncoding(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
OBJC_EXPORT void
class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull) OBJC2_UNAVAILABLE;
复制代码
IMP
。OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
IMP
。OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码
建立一个类LGPerson
,而后建立LGTeacher
继承LGPerson
,使用以下代码:spa
// LGPerson .h
@interface LGPerson : NSObject
- (void)person_instanceMethod;
@end
// LGPerson.m
@implementation LGPerson
- (void)person_instanceMethod {
NSLog(@"\n 打印 person_instanceMethod: %s\n", __func__);
}
@end
// LGTeacher.h
@implementation LGTeacher
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ [LGRuntimeUtil
lg_methodSwizzlingWithClass:self
oriSEL:@selector(person_instanceMethod)
swizzledSEL:@selector(teacher_instanceMethod)];
});
}
- (void)teacher_instanceMethod {
NSLog(@"\n 打印 teacher_instanceMethod: %s\n", __func__);
}
@end
// 封装LGRuntimeUtil.m
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swizzleMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swizzleMethod);
}
复制代码
在 main
文件里面初始化这两个类,都调用 person_instanceMethod
方法:
LGPerson *person = [LGPerson alloc] init];
[person person_instanceMethod];
LGTeacher *teacher = [LGTeacher alloc] init];
[teacher person_instanceMethod];
复制代码
可是,打印出来的方法名,却都是 teacher_instanceMethod
。那么就说明替换成功了。
由于person_instanceMethod
的 SEL
找到的是teacher_instanceMethod
的IMP
,因此找到的就是teacher_instanceMethod
方法;
而teacher_instanceMethod
的 SEL
找到的倒是person_instanceMethod
的IMP
,但IMP
对应的是person_instanceMethod
方法,再继续根据person_instanceMethod
方法的 SEL
找到的是交换后的IMP
,因此找到了teacher_instanceMethod
方法。
就是在 teacher_instanceMethod
方法里面,再次调用 teacher_instanceMethod
,代码以下:
- (void)teacher_instanceMethod {
[self teacher_instanceMethod];
NSLog(@"\n 打印 teacher_instanceMethod: %s\n", __func__);
}
复制代码
运行以后,直接报错:
为何会这样了?
对于LGTeacher
而言调用person_instanceMethod
就是调用LGTeacher:teacher_instanceMethod-> LGPerson:person_instanceMethod
。
对于LGPerson
调用person_instanceMethod
是调用LGTeacher:teacher_instanceMethod -> LGPerson:teacher_instanceMethod
。而LGPerson
没有实现teacher_instanceMethod
,因此报错。
因此交换方法必定是去交换本身的方法。
由于有时候,在作一些处理的时候,须要保持原来的逻辑,因此须要再次调用本类。
能够经过class_addMethod
去尝试添加要交换的方法。
class_addMethod
方法的使用,咱们可使用这个方法来添加要交换的方法:
若是添加成功
,说明在本类中没有这个方法,可是能够经过class_replaceMethod
进行替换
,其内部会调用class_addMethod
进行添加的方法;
若是添加不成功,就说明类里面有这个方法,则经过method_exchangeImplementations
进行交换
代码以下:
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 添加要交换的方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {
// 添加成功 - 进行替换 - 没有父类进行处理 (重写一个)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
// 本身有的话就
method_exchangeImplementations(oriMethod, swiMethod);
}
}
复制代码
根据上面的使用案例,若是子类和父类都没有实现person_instanceMethod
这个方法,在子类里面调用[self teacher_instanceMethod]
时,就会产生递归,若是不处理,就回报错。
怎么解决了?若是该方法不存在,能够在添加方法后,再给此方法添加一个空的实现,也就是至关于增长一个不作任何事情的IMP
,代码以下:
+ (void)ssl_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, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ NSLog(@"来了一个空的 imp"); }));
}
// 尝试添加你要交换的方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {
// 添加成功说明本身没有 - 替换 - 父类重写一个
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
// 本身有 - 交换
method_exchangeImplementations(oriMethod, swiMethod);
}
}
复制代码