轉(zhuǎn)載:http://www.cocoachina.com/ios/20161102/17920.html
因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzling這個(gè)黑魔法解決了我們實(shí)際開(kāi)發(fā)中諸多常規(guī)手段所無(wú)法解決的問(wèn)題, 比如代碼的插樁,Hook,Patch等等. 我們首先看看常規(guī)的Method Swizzling是怎樣用的, NSHipster有一篇介紹基本用法的文章Method Swizzling, 我們就先以這篇文章中的示例開(kāi)始說(shuō)起吧:
#import?@implementation?UIViewController?(Tracking)
+?(void)load?{
staticdispatch_once_t?onceToken;
dispatch_once(&onceToken,?^{
Classclass=?[selfclass];
SEL?originalSelector?=?@selector(viewWillAppear:);
SEL?swizzledSelector?=?@selector(xxx_viewWillAppear:);
Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);
BOOLdidAddMethod?=
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);
}
});
}
#pragma?mark?-?Method?Swizzling
-?(void)xxx_viewWillAppear:(BOOL)animated?{
[self?xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear:?%@",?self);
}
@end
簡(jiǎn)要說(shuō)明一下以上代碼的幾個(gè)重點(diǎn):
通過(guò)在Category的+ (void)load方法中添加Method Swizzling的代碼,在類(lèi)初始加載時(shí)自動(dòng)被調(diào)用,load方法按照父類(lèi)到子類(lèi),類(lèi)自身到Category的順序被調(diào)用.
在dispatch_once中執(zhí)行Method Swizzling是一種防護(hù)措施,以保證代碼塊只會(huì)被執(zhí)行一次并且線程安全,不過(guò)此處并不需要,因?yàn)楫?dāng)前Category中的load方法并不會(huì)被多次調(diào)用.
嘗試先調(diào)用class_addMethod方法,以保證即便originalSelector只在父類(lèi)中實(shí)現(xiàn),也能達(dá)到Method Swizzling的目的.
xxx_viewWillAppear:方法中[self xxx_viewWillAppear:animated];代碼并不會(huì)造成死循環(huán),因?yàn)镸ethod Swizzling之后,調(diào)用xxx_viewWillAppear:實(shí)際執(zhí)行的代碼已經(jīng)是原來(lái)viewWillAppear中的代碼了.
其實(shí)以上的代碼也可以簡(jiǎn)寫(xiě)為以下:
+?(void)load?{
Classclass=?[selfclass];
SEL?originalSelector?=?@selector(viewWillAppear:);
SEL?swizzledSelector?=?@selector(xxx_viewWillAppear:);
Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);
if(!originalMethod?||?!swizzledMethod)?{
return;
}
IMP?originalIMP?=?method_getImplementation(originalMethod);
IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);
constchar*originalType?=?method_getTypeEncoding(originalMethod);
constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);
//?這兒的先后順序是有講究的,如果先執(zhí)行后一句,那么在執(zhí)行完瞬間方法被調(diào)用容易引發(fā)死循環(huán)
class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
}
這是因?yàn)閏lass_replaceMethod方法其實(shí)能夠覆蓋到class_addMethod和method_setImplementation兩種場(chǎng)景, 對(duì)于第一個(gè)class_replaceMethod來(lái)說(shuō), 如果viewWillAppear:實(shí)現(xiàn)在父類(lèi), 則執(zhí)行class_addMethod, 否則就執(zhí)行method_setImplementation將原方法的IMP指定新的代碼塊; 而第二個(gè)class_replaceMethod完成的工作便只是將新方法的IMP指向原來(lái)的代碼.
但此處需要特別注意交換的順序,應(yīng)該優(yōu)化把新的方法指定原IMP,再修改原有的方法的IMP.
除了以上的場(chǎng)景之外,其它場(chǎng)景下我們?nèi)绾问褂肕ethod Swizzling呢?
1.在不同類(lèi)之間實(shí)現(xiàn)Method Swizzling
上面示例是通過(guò)Category來(lái)新增一個(gè)方法然后實(shí)現(xiàn)Method Swizzling的, 但有一些場(chǎng)景可能并不適合使用Category(比如私有的類(lèi),未獲取到該類(lèi)的聲明), 此時(shí)我們應(yīng)該如何來(lái)做Method Swizzling呢?
例如已知一個(gè)className為Car的類(lèi)中有一個(gè)實(shí)例方法- (void)run:(double)speed, 目前需要Hook該方法對(duì)速度小于120才執(zhí)行run的代碼, 按照方法交換的流程, 代碼應(yīng)該是這樣的:
#import?@interface?MyCar?:?NSObject
@end
@implementation?MyCar
+?(void)load?{
Class?originalClass?=?NSClassFromString(@"Car");
Class?swizzledClass?=?[selfclass];
SEL?originalSelector?=?NSSelectorFromString(@"run:");
SEL?swizzledSelector?=?@selector(xxx_run:);
Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);
//?向Car類(lèi)中新添加一個(gè)xxx_run:的方法
BOOLregisterMethod?=?class_addMethod(originalClass,
swizzledSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if(!registerMethod)?{
return;
}
//?需要更新swizzledMethod變量,獲取當(dāng)前Car類(lèi)中xxx_run:的Method指針
swizzledMethod?=?class_getInstanceMethod(originalClass,?swizzledSelector);
if(!swizzledMethod)?{
return;
}
//?后續(xù)流程與之前的一致
BOOLdidAddMethod?=?class_addMethod(originalClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if(didAddMethod)?{
class_replaceMethod(originalClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod,?swizzledMethod);
}
}
-?(void)xxx_run:(double)speed?{
if(speed?<?120)?{
[self?xxx_run:speed];
}
}
@end
與之前的流程相比,在前面添加了兩個(gè)邏輯:
利用runtime向目標(biāo)類(lèi)Car動(dòng)態(tài)添加了一個(gè)新的方法,此時(shí)Car類(lèi)與MyCar類(lèi)一樣具備了xxx_run:這個(gè)方法,MyCar的利用價(jià)值便結(jié)束了;
為了完成后續(xù)Car類(lèi)中run:與xxx_run:的方法交換,此時(shí)需要更新swizzledMethod變量為Car中的xxx_run:方法所對(duì)應(yīng)的Method.
以上所有的邏輯也可以合并簡(jiǎn)化為以下:
+?(void)load?{
Class?originalClass?=?NSClassFromString(@"Car");
Class?swizzledClass?=?[selfclass];
SEL?originalSelector?=?NSSelectorFromString(@"run:");
SEL?swizzledSelector?=?@selector(xxx_run:);
Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);
IMP?originalIMP?=?method_getImplementation(originalMethod);
IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);
constchar*originalType?=?method_getTypeEncoding(originalMethod);
constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
簡(jiǎn)化后的代碼便與之前使用Category的方式并沒(méi)有什么差異, 這樣代碼就很容易覆蓋到這兩種場(chǎng)景了, 但我們需要明確此時(shí)class_replaceMethod所完成的工作卻是不一樣的.
第一個(gè)class_replaceMethod直接在Car類(lèi)中注冊(cè)了xxx_run:方法,并且指定的IMP為當(dāng)前run:方法的IMP;
第二個(gè)class_replaceMethod與之前的邏輯一致,當(dāng)run:方法是實(shí)現(xiàn)在Car類(lèi)或Car的父類(lèi),分別執(zhí)行method_setImplementation或class_addMethod;
2.如何實(shí)現(xiàn)類(lèi)方法的Method Swizzling
以上的代碼都是實(shí)現(xiàn)的對(duì)實(shí)例方法的交換, 那如何來(lái)實(shí)現(xiàn)對(duì)類(lèi)方法的交換呢, 依舊直接貼代碼吧:
@interface?NSDictionary?(Test)
@end
@implementation?NSDictionary?(Test)
+?(void)load?{
Class?cls?=?[selfclass];
SEL?originalSelector?=?@selector(dictionary);
SEL?swizzledSelector?=?@selector(xxx_dictionary);
//?使用class_getClassMethod來(lái)獲取類(lèi)方法的Method
Method?originalMethod?=?class_getClassMethod(cls,?originalSelector);
Method?swizzledMethod?=?class_getClassMethod(cls,?swizzledSelector);
if(!originalMethod?||?!swizzledMethod)?{
return;
}
IMP?originalIMP?=?method_getImplementation(originalMethod);
IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);
constchar*originalType?=?method_getTypeEncoding(originalMethod);
constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);
//?類(lèi)方法添加,需要將方法添加到MetaClass中
Class?metaClass?=?objc_getMetaClass(class_getName(cls));
class_replaceMethod(metaClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(metaClass,originalSelector,swizzledIMP,swizzledType);
}
+?(id)xxx_dictionary?{
id?result?=?[self?xxx_dictionary];
returnresult;
}
@end
相比實(shí)例方法的Method Swizzling,流程有兩點(diǎn)差異:
獲取Method的方法變更為class_getClassMethod(Class cls, SEL name),從函數(shù)命名便直觀體現(xiàn)了和class_getInstanceMethod(Class cls, SEL name)的差別;
對(duì)于類(lèi)方法的動(dòng)態(tài)添加,需要將方法添加到MetaClass中,因?yàn)閷?shí)例方法記錄在class的method-list中,類(lèi)方法是記錄在meta-class中的method-list中的.
3.在類(lèi)簇中如何實(shí)現(xiàn)Method Swizzling
在上面的代碼中我們實(shí)現(xiàn)了對(duì)NSDictionary中的+ (id)dictionary方法的交換,但如果我們用類(lèi)似代碼嘗試對(duì)- (id)objectForKey:(id)key方法進(jìn)行交換后, 你便會(huì)發(fā)現(xiàn)這似乎并沒(méi)有什么用.
這是為什么呢? 平常我們?cè)赬code調(diào)試時(shí),在下方Debug區(qū)域左側(cè)的Variables View中,常常會(huì)發(fā)現(xiàn)如__NSArrayI或是__NSCFConstantString這樣的Class類(lèi)型, 這便是在Foundation框架中被廣泛使用的類(lèi)簇, 詳情請(qǐng)參看Apple文檔class cluster的內(nèi)容.
所以針對(duì)類(lèi)簇的Method Swizzling問(wèn)題就轉(zhuǎn)變?yōu)槿绾螌?duì)這些類(lèi)簇中的私有類(lèi)做Method Swizzling, 在上面介紹的不同類(lèi)之間做Method Swizzling便已經(jīng)能解決該問(wèn)題, 下面一個(gè)簡(jiǎn)單的示例通過(guò)交換NSMutableDictionary的setObject:forKey:方法,讓調(diào)用這個(gè)方法時(shí)當(dāng)參數(shù)object或key為空的不會(huì)拋出異常:
@interface?MySafeDictionary?:?NSObject
@end
@implementation?MySafeDictionary
+?(void)load?{
Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");
Class?swizzledClass?=?[selfclass];
SEL?originalSelector?=?@selector(setObject:forKey:);
SEL?swizzledSelector?=?@selector(safe_setObject:forKey:);
Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);
IMP?originalIMP?=?method_getImplementation(originalMethod);
IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);
constchar*originalType?=?method_getTypeEncoding(originalMethod);
constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
-?(void)safe_setObject:(id)anObject?forKey:(id)aKey?{
if(anObject?&&?aKey)?{
[self?safe_setObject:anObject?forKey:aKey];
}
elseif(aKey)?{
[(NSMutableDictionary?*)self?removeObjectForKey:aKey];
}
}
@end
4.在Method Swizzling之后如何恢復(fù)
使用了Method Swizzling的各種姿勢(shì)之后, 是否有考慮如何恢復(fù)到交換之前的現(xiàn)場(chǎng)呢?
一種方案就是通過(guò)一個(gè)開(kāi)關(guān)標(biāo)識(shí)符, 如果需要從邏輯上面恢復(fù)到交換之前, 就設(shè)置一下這個(gè)標(biāo)識(shí)符, 在實(shí)現(xiàn)中判定如果設(shè)定了該標(biāo)識(shí)符, 邏輯就直接調(diào)用原方法的實(shí)現(xiàn), 其它什么事兒也不干, 這是目前大多數(shù)代碼的實(shí)現(xiàn)方法, 當(dāng)然也是非常安全的方式, 只不過(guò)當(dāng)交換方法過(guò)多時(shí), 每一個(gè)交換的方法體中都需要增加這樣的邏輯, 并且也需要維護(hù)大量這些標(biāo)識(shí)符變量, 只是會(huì)覺(jué)得不夠優(yōu)雅, 所以此處也就不展開(kāi)詳細(xì)討論了.
那下面來(lái)討論一下有沒(méi)有更好的方案, 以上描述的Method Swizzling各種場(chǎng)景和處理的技巧, 但綜合總結(jié)之后最核心的其實(shí)也只做了兩件事情:
class_addMethod添加一個(gè)新的方法,可能是把其它類(lèi)中實(shí)現(xiàn)的方法添加到目標(biāo)類(lèi)中,也可能是把父類(lèi)實(shí)現(xiàn)的方法添加一份在子類(lèi)中,可能是添加的實(shí)例方法,也可能是添加的類(lèi)方法,總之就是添加了方法.
交換IMP交換方法的實(shí)現(xiàn)IMP,完成這個(gè)步驟除了使用method_exchangeImplementations這個(gè)方法外,也可以是調(diào)用了method_setImplementation方法來(lái)單獨(dú)修改某個(gè)方法的IMP,或者是采用在調(diào)用class_addMethod方法中設(shè)定了IMP而直接就完成了IMP的交換,總之就是對(duì)IMP的交換.
那我們來(lái)分別看一下這兩件事情是否都還能恢復(fù):
對(duì)于class_addMethod,我們首先想到的可能就是有沒(méi)有對(duì)應(yīng)的remove方法呢,在Objective-C 1.0的時(shí)候有class_removeMethods這個(gè)方法,不過(guò)在2.0的時(shí)候就已經(jīng)被禁用了,也就是蘋(píng)果并不推薦我們這樣做,想想似乎也是挺有道理的,本來(lái)runtime的接口看著就挺讓人心驚膽戰(zhàn)的,又是添加又是刪除總覺(jué)得會(huì)出岔子,所以只能放棄remove的想法,反正方法添加在那兒倒也沒(méi)什么太大的影響.
針對(duì)IMP的交換,在Method Swizzling時(shí)做的交換動(dòng)作,如果需要恢復(fù)其實(shí)要做的動(dòng)作還是交換回來(lái)罷了,所以是可以做到的,不過(guò)需要怎樣做呢?對(duì)于同一個(gè)類(lèi),同一個(gè)方法,可能會(huì)在不同的地方被多次做Method Swizzling,所以要回退某一次的Method Swizzling,我們就需要記錄下來(lái)這一次交換的時(shí)候是哪兩個(gè)IMP做了交換,恢復(fù)的時(shí)候再換回來(lái)即可.另一個(gè)問(wèn)題是如果已經(jīng)經(jīng)過(guò)多次交換,我們?cè)鯓诱业竭@兩個(gè)IMP所對(duì)應(yīng)的Mehod呢,還好runtime提供了一個(gè)class_copyMethodList方法,可以直接取出Method列表,然后我們就可以逐個(gè)遍歷找到IMP所對(duì)應(yīng)的Method了,下面是對(duì)上一個(gè)示例添加恢復(fù)之后實(shí)現(xiàn)的代碼邏輯:
#import?@interface?MySafeDictionary?:?NSObject
@end
staticNSLock?*kMySafeLock?=?nil;
staticIMP?kMySafeOriginalIMP?=?NULL;
staticIMP?kMySafeSwizzledIMP?=?NULL;
@implementation?MySafeDictionary
+?(void)swizzlling?{
staticdispatch_once_t?onceToken;
dispatch_once(&onceToken,?^{
kMySafeLock?=?[[NSLock?alloc]?init];
});
[kMySafeLock?lock];
do{
if(kMySafeOriginalIMP?||?kMySafeSwizzledIMP)break;
Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");
if(!originalClass)break;
Class?swizzledClass?=?[selfclass];
SEL?originalSelector?=?@selector(setObject:forKey:);
SEL?swizzledSelector?=?@selector(safe_setObject:forKey:);
Method?originalMethod?=?class_getInstanceMethod(originalClass,?originalSelector);
Method?swizzledMethod?=?class_getInstanceMethod(swizzledClass,?swizzledSelector);
if(!originalMethod?||?!swizzledMethod)break;
IMP?originalIMP?=?method_getImplementation(originalMethod);
IMP?swizzledIMP?=?method_getImplementation(swizzledMethod);
constchar*originalType?=?method_getTypeEncoding(originalMethod);
constchar*swizzledType?=?method_getTypeEncoding(swizzledMethod);
kMySafeOriginalIMP?=?originalIMP;
kMySafeSwizzledIMP?=?swizzledIMP;
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}while(NO);
[kMySafeLock?unlock];
}
+?(void)restore?{
[kMySafeLock?lock];
do{
if(!kMySafeOriginalIMP?||?!kMySafeSwizzledIMP)break;
Class?originalClass?=?NSClassFromString(@"__NSDictionaryM");
if(!originalClass)break;
Method?originalMethod?=?NULL;
Method?swizzledMethod?=?NULL;
unsignedintoutCount?=?0;
Method?*methodList?=?class_copyMethodList(originalClass,?&outCount);
for(unsignedintidx=0;?idx?<?outCount;?idx++)?{
Method?aMethod?=?methodList[idx];
IMP?aIMP?=?method_getImplementation(aMethod);
if(aIMP?==?kMySafeSwizzledIMP)?{
originalMethod?=?aMethod;
}
elseif(aIMP?==?kMySafeOriginalIMP)?{
swizzledMethod?=?aMethod;
}
}
//?盡可能使用exchange,因?yàn)樗莂tomic的
if(originalMethod?&&?swizzledMethod)?{
method_exchangeImplementations(originalMethod,?swizzledMethod);
}
elseif(originalMethod)?{
method_setImplementation(originalMethod,?kMySafeOriginalIMP);
}
elseif(swizzledMethod)?{
method_setImplementation(swizzledMethod,?kMySafeSwizzledIMP);
}
kMySafeOriginalIMP?=?NULL;
kMySafeSwizzledIMP?=?NULL;
}while(NO);
[kMySafeLock?unlock];
}
-?(void)safe_setObject:(id)anObject?forKey:(id)aKey?{
if(anObject?&&?aKey)?{
[self?safe_setObject:anObject?forKey:aKey];
}
elseif(aKey)?{
[(NSMutableDictionary?*)self?removeObjectForKey:aKey];
}
}
@end
注意這段代碼的Method Swizzling和恢復(fù)都需要主動(dòng)調(diào)用, 并且相比上面其它的示例, 這段代碼還添加如鎖機(jī)制來(lái)加之保護(hù). 這個(gè)示例是以不同的類(lèi)來(lái)實(shí)現(xiàn)的Method Swizzling和恢復(fù), 如果是Category或者是類(lèi)方法, 根據(jù)之前的示例也需要做相應(yīng)的調(diào)整.