iOS的Method Swizzling是一個(gè)非常有意思的run time應(yīng)用案例.用它可以實(shí)現(xiàn)AOP,也可以用來(lái)hook很多API,進(jìn)行很多hack的操作.
相關(guān)資料很多,總體來(lái)講就是調(diào)換兩個(gè)Method的Imp,也就是調(diào)換函數(shù)指針.在很多AOP的案例中,使用這種方式hook住關(guān)鍵方法,在遞歸調(diào)用方法前/后實(shí)現(xiàn)AOP.例如:在一個(gè)UIViewController的category中,可以這樣
+ (void)load {
Method currentDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoadAOP));
Method oriDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
method_exchangeImplementations(currentDidLoadMethod, oriDidLoadMethod);
}
- (void)viewDidLoadAOP {
//在這里寫代碼,可以類似成為Before
[self viewDidLoadAOP];//這里遞歸調(diào)用自己
//在這里寫代碼,可以類似成為After
但是如果交換不同類之間的方法,這樣調(diào)用就會(huì)crash.
比如有A,B兩個(gè)類,分別有a方法和b方法.我們調(diào)用A類的a方法,希望通過(guò)method swizzling對(duì)A方法進(jìn)行處理.
調(diào)用A類的a方法
A *aClass = [A new];
[aClass a];
a方法
- (void)a {
NSLog(@"a");
}
b方法(我們希望增強(qiáng)a方法,所以遞歸調(diào)用)
- (void)b {
[self b];//遞歸調(diào)用
NSLog(@"b");
}
這樣就會(huì)導(dǎo)致crash,報(bào)錯(cuò)為A類中找不到b這個(gè)selector.
原因是當(dāng)執(zhí)行[aClass a]的時(shí)候,因?yàn)榻粨QImp的原因,則會(huì)進(jìn)入到b方法中.而b中此時(shí)的self,是指的A,因?yàn)榻粨Q僅僅改變Imp,receiver和selector都不會(huì)改變,A類中沒(méi)有b這個(gè)方法,當(dāng)然會(huì)crash.
解決方案是:
如果需要在不同的類中交換方法,一定要注意此時(shí)的self指的是什么.如果需要調(diào)用self沒(méi)有的方法,那么使用class_addMethod將不存在的方法add進(jìn)去即可.
這個(gè)小Tip是針對(duì)一個(gè)小工具中出現(xiàn)的問(wèn)題的一個(gè)記錄.下篇文章會(huì)來(lái)聊聊這個(gè)小工具:對(duì)UITableView高度的cache.
大致思路是:
大部分時(shí)候,UITableView的數(shù)據(jù)源并不會(huì)改變.但heightForRowAtIndexPath這個(gè)代理,在每一次滑動(dòng)中均會(huì)計(jì)算一次高度.實(shí)際上是沒(méi)有必要的.
比較好的做法是在model中自己緩存高度.但是如果有一個(gè)UITableview的分類,其中包含一個(gè)enableCache的屬性,開(kāi)啟后自動(dòng)緩存是否更好呢?這樣子沒(méi)有通用并且沒(méi)有侵入性.
所以我們通過(guò)一些黑魔法,hook這些代理方法,然后自動(dòng)緩存,從而得到這個(gè)小工具.