method_exchangeImplementations
方法交换的注意点:
- 如果是类簇,类名要正确
- 如果是类方法,要使用
object_getClass
获得元类
- 如果子类方法交换时该方法仅存在于父类,因为
class_getInstanceMethod
会搜索superclass
,直接执行method_exchangeImplementations
会使得父类的方法会替换,不符合预期,甚至会crash。
- 偶数次执行相同的
method_exchangeImplementations
导致交换无效,如需保证仅执行一次则使用dispatch_once
。
- 同样,可以使用
method_exchangeImplementations
恢复方法交换。
- 如果有多次不同的
selector
执行了method_exchangeImplementations
,则实际调用顺序与swizzle的顺序相反。
- 如果
swizzledMethod
写在分类里,请使用特定的前缀保证不会导致分类方法覆盖。
- 如果
originalMethod
和swizzledMethod
不在同一个originalClass
中,则需要先通过class_addMethod
将swizzledMethod
加到originalClass
中。
- 如非直接替换,则在
swizzledMethod
中应调用原生方法originalMethod
的实现。由于方法已替换,所以代码上调用的是swizzledSelector
。
- 方法交换适用于类方法或者所有实例的实例方法,如需仅对指定实例生效,可使用
Aspect
。
- 方法交换由于
selector
不同,会导致_cmd
不同。
class_addMethod与class_replaceMethod
关键代码如下。
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }
当子类方法交换时该方法仅存在于父类时,
class_addMethod
成功,didAddMethod
为YES
,则调用class_replaceMethod
,而不是method_exchangeImplementations
,避免父类被污染。JRSwizzle
JRSwizzle
主要实现了在Mac OS X, iOS, Objective-C不同平台不同版本不同架构的方法交换。而对于目前主流的iOS的objc2,关键代码如下。
class_addMethod(self, origSel_, class_getMethodImplementation(self, origSel_), method_getTypeEncoding(origMethod)); class_addMethod(self, altSel_, class_getMethodImplementation(self, altSel_), method_getTypeEncoding(altMethod)); method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
通过先对目标类添加一次原方法和替换方法,避免父类被污染的问题。
IMPPointer
为了避免使用
method_exchangeImplementations
出现的命名冲突和_cmd
变化的问题,可以使用IMP
和IMP
指针,而不是使用新的selector
。typedef IMP *IMPPointer; static void MethodSwizzle(id self, SEL _cmd, id arg1); static void (*MethodOriginal)(id self, SEL _cmd, id arg1); static void MethodSwizzle(id self, SEL _cmd, id arg1) { // do custom work MethodOriginal(self, _cmd, arg1); } BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) { IMP imp = NULL; Method method = class_getInstanceMethod(class, original); if (method) { const char *type = method_getTypeEncoding(method); imp = class_replaceMethod(class, original, replacement, type); if (!imp) { imp = method_getImplementation(method); } } if (imp && store) { *store = imp; } return (imp != NULL); }
RSSwizzle
RSSwizzle
提出了method_exchangeImplementations
的几个问题- 线程不安全
- 在
swizzle
的时候如果方法不存在,从父类复制实现到子类。会导致父类的方法替换无效。
- 方法交换导致
_cmd
变化
- 替换方法的
selector
存在命名冲突的可能
RSSwizzle
的实现只用到class_replaceMethod
,并没有用method_exchangeImplementations
The previous implementation of the method identified by name for the class identified by cls.
由于
class_replaceMethod
的返回值是替换前的方法,如果是NULL
,则表明该子类本身没有实现该方法,则在实际运行调用原方法时直接调用super
的方法。使得既不污染父类,也不改变子类调用父类的默认行为。如果父类也有方法替换,也可以同时生效。if (NULL == imp){ // If the class does not implement the method // we need to find an implementation in one of the superclasses. Class superclass = class_getSuperclass(classToSwizzle); imp = method_getImplementation(class_getInstanceMethod(superclass,selector)); }
整个过程只用了block和IMP的操作,没有新增SEL,所以不存在方法命名冲突和_cmd变化的问题。
缺点是需要自己额外声明方法签名(参数个数、参数类型和返回值类型),比较繁琐。
Loading Comments...