iOS-Swizzle
最后更新:2017-06-21
一、先说结论
void swizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod =
class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
二、代码分析
2.1 class_getInstanceMethod()
获取某个类实例的方法, 如果该类实例没有此方法, 则返回NULL
Method swizzleMethod = class_getInstanceMethod([Person class], @selector(run));
if (swizzleMethod == NULL) {
NSLog(@"NULL");
}
参数解释
class_getInstanceMethod(Class cls, SEL name)
cls: 获取方法的类
name: 方法的名称
2.2 class_addMethod()
参数解释
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
cls: 获取方法的类
name: 添加的方法方法的名称
imp: 方法的实现,也就一个指向方法的指针
const char *types: 定义了返回值类型和参数类型的字符串(下面会提到)
返回值
YES: 增加方法成功
NO: 增加方法失败,例如 (如果目标类(Person) 实现了该方法,那么会返回 NO)
注意点
- class_addMethod 能够覆盖父类的实现的;
如果目标类有实现了该方法,class_addMethod就会失败
class_addMethod will add an override of a superclass's implementation
-
处理警告问题
参考: https://stackoverflow.com/questions/6224976/how-to-get-rid-of-the-undeclared-selector-warningvoid sayHello(id self, SEL _cmd, NSString *word) { NSLog(@"%@", word); } - (void)viewDidLoad { [super viewDidLoad]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" class_addMethod([Person class], @selector(resolveThisMethodDynamically:), (IMP)sayHello, "v@:@"); Person *p = [[Person alloc] init]; [p performSelector:@selector(resolveThisMethodDynamically:) withObject:@"hello"]; #pragma clang diagnostic pop }
-
需要动态调用,因为通过运行时添加的方法,直接调用 编译不过的
正确做法: [p performSelector:@selector(resolveThisMethodDynamically:) withObject:@"hello"]; 错误做法,编译不过 [p resolveThisMethodDynamically:@"hello"];
-
参 imp 默认自带两个参数, id类型 以及 SEL 类型
void sayHello(id self, SEL _cmd, ...)
2.3 参数 const char *types 解释
v 表示的是void 类型
i 表示整数类型
@ 表示一个对象
: 表示一个方法
v@: 表示的是返回值类型是void, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd)
v@😡 表示的是返回值类型是void, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd), 还有一个参数是对象(NSString *word)
i@: 表示范围值是 int, 一个参数是对象(id self),另一个参数为方法 (SEL _cmd)
可以按照下表来查
2.4 言归正传 Swizzle 的讨论
简单的,交换方法,我们仅仅需要如下操作
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
但是, 我们仅仅解决了一种情况
周全起见,有两种情况要考虑一下。
第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。
第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。
(译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。)
对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。
因此,上面简单的交换方法,仅仅能处理第二种 已经存在于目标类的方式
完整的解决方式如下
void swizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod =
class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}