?。。。。。?!此帖子內(nèi)容有待更深的考究!?。。。。。。?!
ios開發(fā)中,在很多情形下,會用到 method swizzling 方式,即hook某一類(一般是不開源框架類)的某方法,在方法的實現(xiàn)中添加自定義的邏輯。
比如,對于NSMutableDictionary類的setValue:forKey:方法(此方法實現(xiàn)在 NSMutableDictionary針對kvc支持的拓展中),添加key是否為空的判斷,以避免key為nil導(dǎo)致的崩潰等情況。
此時,需在NSMutableDictionary類的拓展類中添加重載load類方法的代碼,代碼套路如下:
+ (void)load {
Class class = [self class]; //objc_getClass("__NSDictionaryM") // line 1
SEL selector_origin = @selector(setValue:forKey:); //line 2
SEL selector_new = @selector(XXSetValue:forKey:); //line 3
//XXSetValue:forKey: 方法為自定義方法,略
Method method_origin = class_getInstanceMethod(class, selector_origin); // line 4
Method method_new = class_getInstanceMethod(class, selector_new); //line 5
method_exchangeImplementations(method_origin, method_new); //line 6
}
此時,當(dāng)在程序中其他地方,包含了此部分代碼的頭文件時,正常調(diào)用setValue: forKey:時,實際上已經(jīng)調(diào)用了自定義的方法 XXSetValue:forKey: 。以上代碼的作用就是將兩方法的實現(xiàn)代碼互換(method_exchangeImplementations方法)。
廢話說了這么多,重點在這,下圖為替換 setObject:forKey:方法的實現(xiàn)代碼:
Class class = [self class]; //objc_getClass("__NSDictionaryM"); //line 0
SEL selector_origin = @selector(setObject:forKey:); //line 1
SEL selector_new = @selector(XsetObj:forKey:); //line 2
Method method_origin = class_getInstanceMethod(class, selector_origin); //line 3
Method method_new = class_getInstanceMethod(class, selector_new); //line 4
method_exchangeImplementations(method_origin, method_new); //line 5
注意,重點:在這里的hook實際上事很特殊的,因為如果此時swizzling的方法不是setValue:forKey:而是setObject:forKey:的話(代碼見以上),結(jié)果會大不相同(實際結(jié)果是后者的method swizzling不會成功);而如果想使setObject:forKey:的swizzling成功的話,需要在代碼 line 0 處,換成注釋的部分。(--而 對setValue方法時,使用objc_getClass("__NSDictionaryM")或者[self class]都是成功的)
問題來了:
1、為什么都是類的實例方法的 setobject和setvalue,實現(xiàn)method swizzling 的方式并不完全一樣呢??
2、 class_getInstanceMethod() 函數(shù)的第一參數(shù)的意義是什么??
/由于作者水平限制,以下解釋中,類簇的相關(guān)知識并不詳細(xì)且嚴(yán)謹(jǐn)。。/
首先,method_exchangeImplementations()函數(shù)的implementation交換是在類對象中完成,即問題2 中的函數(shù)參數(shù),所以問題2 的答案應(yīng)該是 第一參數(shù)是方法所在的類對象,或父類的類對象(稍后會解釋);當(dāng)selector為類方法而不是實例方法時,應(yīng)該使用class_getClassMethod()函數(shù),對應(yīng)的,問題2 的參數(shù)應(yīng)該是當(dāng)前類的元類對象,或者根元類(NSObject)的元類對象。
ps: 拓展一個知識點, [self class]無論在類方法中還是實例方法中,返回都是類對象。而object_getClass(self)函數(shù),當(dāng)在類方法中時,返回的是類對象的isa指針指向的類,即元類對象;在實例方法中,返回的才是類對象。-------因此要避免使用不合適的類對象做參數(shù)
其次,為什么setobject和setvalue方法的method swizzling 實現(xiàn)不一樣呢?首先,需要明白類簇的原理,(以下關(guān)于類簇的知識只是總結(jié)了一些碎塊化的知識點,然后瞎逼逼的。)簡單理解的話,類簇即根據(jù)不同的初始化方法,選擇多套子類代碼中某個作為當(dāng)前類,進(jìn)行實例的初始化,如[[NSNumber alloc] initWithFloat: 1.2f]和[[NSNumber alloc] initWithDouble: 1.3]會產(chǎn)生不同的類,所以可以理解為不同初始化方式得到的類可能不是同一個類,不同用途的類也可能不是同一個類----這里的setvalue對應(yīng)的類,即Method結(jié)構(gòu) method_origin的類對象參數(shù),和setobject對應(yīng)的類不是同一個個類----雖然都屬于NSMutableDictionary類(繼承自公共基類"__NSDictionaryM")。
想要實現(xiàn)正確的method swizzling,需要自當(dāng)前類對象,沿著繼承關(guān)系,依次向上替換method(考慮繼承類中有方法的重載現(xiàn)象--例如自定義UIViewController類重寫viewDidLoad方法);在不考慮繼承類重寫方法的時候,使用類簇的根基類(即implementation了當(dāng)前方法的&&最上層的類):
這里引用牛逼的人 的一個方法,獲取某類的所有直接子類列表:
//打印出 某類的所有直接子類
+ (NSArray *)findAllOf:(Class)defaultClass {
int count = objc_getClassList(NULL, 0);
if (count <= 0) {
@throw@"Couldn't retrieve Obj-C class-list";
return @[defaultClass];
}
NSMutableArray * output = @[].mutableCopy;
Class * classes = (Class *) malloc(sizeof(Class) * count);
objc_getClassList(classes, count);
for (int i = 0; i < count; ++i) {
if (defaultClass == class_getSuperclass(classes[i]))//子類
{
[output addObject: classes[i]];
}
}
free(classes);
return output.copy;
}
正確的方法替換如下:
while (class_getInstanceMethod(currentClass, @selector(setObject:forKey:))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(setObject:forKey:)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(setObject:forKey:)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleMethodForClass:currentClass];
// swizzleMethodForClass 方法是 完成方法交換
}
currentClass = [currentClass superclass];
}
最終通過setObject和setValue的例子,也可以得出牛逼的人 一篇帖子中的一個說法,子類若未重載基類的方法,在class_getInstanceMethod調(diào)用后,會copy一份基類的方法到自己的方法列表中;而實際進(jìn)行方法交換的method就是這一份拷貝。因此,就會導(dǎo)致在類簇類進(jìn)行method swizzling時,class參數(shù)的選取不當(dāng),導(dǎo)致swizzling失敗。