Method class_getInstanceMethod(Class cls, SEL name)
IMP class_getMethodImplementation(Class cls, SEL name)
class_addMethod(Class cls, SEL name, IMP imp, const char * types)
class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)
method_exchangeImplementations(Method m1, Method m2)
method_setImplementation(Method method, IMP imp)
解決方案通常是在 `+(void)load`方法中處理;也能夠加鎖;還能夠在`+(void)initialize`中去作,可是必定要注意繼承的問題。
好比你可能會只想修改一個實例的方法,但實際上你修改了全部的實例方法。好比你交換的方法真實的實現是在父類中的,你的修改會影響全部的父類派生出來的類。 例如,直接使用 `method_exchangeImplementations` 方法,考慮下這種狀況
@ B - (void)case1 { NSLog(@"case 1 B"); } @end @interface C: B @property (nonatomic, copy) NSString *x; @end @implementation C - (void)case2 { NSLog(@"case2 C %@-%@",[self class],self.x); } @end - (void)someMethod { Method a1 = class_getInstanceMethod([C class], @selector(case1)); Method a2 = class_getInstanceMethod([C class], @selector(case2)); method_exchangeImplementations(a1, a2); B *b = [[B alloc] init]; [b case1]; }
會發生什麼呢?會 crash ,由於 C 做爲 B 的子類並無實現 case1
方法,方法交換會把 B 的case1
替換成 C 的 case2
,後面 [b case1]
其實會執行 void _.._case2(C * self, SEL _cmd)
這個函數,裏面調用 x 屬性,因此 crash。git
爲了不這個錯誤,通常的作法有,先用 class_addMethod
判斷可否添加將要替換的方法,若是能夠添加,說明子類原先沒有實現此方法,這個方法是在父類中實現的。具體能夠看參考1。github
RSSwizzle
和 jrswizzle
都避免了這個問題。objective-c
好比你交換的方法極可能在別的地方(好比類別裏)已經有一樣命名的存在了。此時的避免方法能夠是直接去替換 Method 裏的函數指針,保存原有的函數指針來調用:
typedef IMP *IMPPointer; 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); } @implementation NSObject (FRRuntimeAdditions) + (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store { return class_swizzleMethodAndStore(self, original, replacement, store); } @end
好比一樣調用原來的函數時,`_cmd`已經不同了,解決方案能夠和上面一致。
對於 Objective-C 中的一些類簇類,好比 NSNumber、NSArray和NSMutableArray 等,由於這些並非一個具體的類,而是一個抽象類,若是直接在這些類的內部寫個方法經過self class等方式來獲取 Class 並作方法交換的話,由於並不能得到其真實的類名,因此會達不到想要的效果。安全
好比,咱們能夠經過如下代碼來獲得NSMutableArray的真實類型:函數
object_getClass([[NSMutableArray alloc] init]); objc_getClass("__NSArrayM");
上面代碼中__NSArrayM
是NSMutableArray
的真實類名;atom
好比下面的例子就會發生循環調用。
@ A - (void)log { NSLog(@"i am a"); } - (void)print { [self print]; } @end @ B - (void)log { NSLog(@"i am b"); [super log]; } - (void)print { [self print]; } @end
下面作一下方法交換,並執行子類的方法。線程
- (void)test { Method a1 = class_getInstanceMethod([A class], @selector(log)); Method a2 = class_getInstanceMethod([A class], @selector(print)); method_exchangeImplementations(a1, a2); Method a3 = class_getInstanceMethod([B class], @selector(log)); Method a4 = class_getInstanceMethod([B class], @selector(print)); method_exchangeImplementations(a3, a4); B *b = [[B alloc] init]; [b print]; }
方法的調用流程(用imp來表示)指針
B.log - A.print - B.log....
從而造成了循環的引用。code
參考2中提到的,利用 (1、1)中的方法,額外提供一個變量來存儲原始的函數指針,若是須要調用原始方法,就用這個變量來主動設置 sel 參數來防止原始函數用到了_cmd
的狀況blog
主要用到了 method_exchangeImplementations
方法,魯棒性上作的工做就是先作了 class_addMethod
操做。簡單是很簡單,然而上面所說的大部分問題他都不能避免。
主要用到了 class_replaceMethod
方法,避免了子類的替換影響了父類。並且對方法交換加了鎖,加強了線程安全。有更多的替換選項。而且,他經過block
引入了兩個方法互相調用或者子類父類同時交換致使的循環問題。上面的問題幾乎均可以免。
問題是:OSSpinLock
不被建議使用了。
官方文檔說他解決了method_exchangeImplementations
的限制:
_cmd
參數 (經過 RSSwizzleInfo
結構,保存原始的 selector)參考:
1.http://nshipster.cn/method-swizzling/
2.https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
3.http://yulingtianxia.com/blog/2017/04/17/Objective-C-Method-Swizzling/
4.https://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c