首先來看下什么是方法的交換,也是很多人口中的runtime黑魔法。方法交換,就是把Method的IMP指針給交換了。關(guān)于這兩Method和IMP到底是啥?請先看這里
本文的demo可以下載調(diào)試,不過代碼量很少,建議自己動手?jǐn)]一遍。參考鏈接
先直接上代碼吧:
- (void)textSwizzLing {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod([self class],
@selector(orignMethod));
Method swizzMthod = class_getInstanceMethod([self class], @selector(swizzLingMethod));
BOOL didAdd = class_addMethod([self class], @selector(orignMethod),
method_getImplementation(swizzMthod), method_getTypeEncoding(swizzMthod));
//,把swiz的實(shí)現(xiàn)方法,添加給originmethod,如果沒有實(shí)現(xiàn)origin方法,則添加成功,否則添加失敗。
如果直接exchange的話,那么就會覆蓋掉父類的方法。,
if (didAdd) {
class_replaceMethod([self class],@selector(swizzLingMethod),
method_getImplementation(originMethod),method_getTypeEncoding(originMethod));
}else {
method_exchangeImplementations(originMethod, swizzMthod);
//如果origin的方法,本類中已經(jīng)實(shí)現(xiàn)了,直接交換就好了。
}
});
[self performSelector:@selector(orignMethod) withObject:nil];
}
整個邏輯是這樣的:把swizz的實(shí)現(xiàn),添加給origin,如果origin的方法,已經(jīng)在本類中實(shí)現(xiàn)了,那么直接exchange就可以了。 這是一種情況。因?yàn)閍ddMethod的官方文檔說,如果origin在本類有實(shí)現(xiàn),那么會添加失敗。
看了這一段代碼,其實(shí)很多教程教你這么寫,可是你知道為什么這么寫么?
如果didAdd失敗會怎樣?
下面分析一下第二種情況,請看代碼(or 自己寫一下):
Class cls = [UILabel class];
SEL originalSelector = @selector(willMoveToSuperview:);
SEL swizzledSelector = @selector(myWillMoveToSuperview:);
Method originalM = class_getInstanceMethod(cls, originalSelector);
Method swizzedM = class_getInstanceMethod(cls, swizzledSelector);
NSLog(@"%p",method_getImplementation(swizzedM));
NSLog(@"%p",method_getImplementation(originalM));
BOOL add = class_addMethod(cls, originalSelector, method_getImplementation(swizzedM), method_getTypeEncoding(swizzedM));
NSLog(@"%p",method_getImplementation(swizzedM));
NSLog(@"%p",method_getImplementation(originalM));
if (add) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalM), method_getTypeEncoding(originalM));
NSLog(@"%p",method_getImplementation(swizzedM));
NSLog(@"%p",method_getImplementation(originalM));
}else {
method_exchangeImplementations(originalM, swizzedM);
}
這個willMoveToSuperview:是父類中實(shí)現(xiàn)的。UILabel中并沒有實(shí)現(xiàn)這個函數(shù),所以,Add為YES,那么,通過Add把swizz的實(shí)現(xiàn)傳給了willMoveToSuperview。那么, class_replaceMethod(cls,swizzledSelector, method_getImplementation(originalM), method_getTypeEncoding(originalM));就是把origin的實(shí)現(xiàn)傳給swizzled這個函數(shù)名了?

打印一下我發(fā)現(xiàn),可是這兩個函數(shù)都指向的同一個實(shí)現(xiàn)方法啊。意味著,origin確實(shí)指向了swizz的實(shí)現(xiàn),可是swizz的實(shí)現(xiàn)還是swizz的實(shí)現(xiàn)?郁悶?zāi)?,怎么會這樣?不應(yīng)該是交換么,說好的SwizzLing呢,那豈不是調(diào)用兩個都會執(zhí)行同一個函數(shù)。既然網(wǎng)上的資料說不清,那我還是看官方文檔吧

終于找到了罪魁禍?zhǔn)?,注意看最后一句話,大概意思就是去這個類的父類中去找這個方法。
現(xiàn)在明白了,class_getInstanceMethod原來拿到的是父類中的方法,后面這句
class_replaceMethod(cls,swizzledSelector, method_getImplementation(originalM), method_getTypeEncoding(originalM));
是把父類的實(shí)現(xiàn),覆蓋了swizz的實(shí)現(xiàn),所以兩個打印出來的地址會是一樣.所以在打印 NSLog(@"%p",method_getImplementation(originalM));
的時候,還是打印的父類中的實(shí)現(xiàn),并不是現(xiàn)在新加的實(shí)現(xiàn)。
那么現(xiàn)在稍微多了解一點(diǎn),class_getInstanceMethod會先在子類找還是先在父類找?(是想知道,如果我在子類中重寫了父類中的方法,那么這個函數(shù)是拿到的子類中的還是父類中的)
SEL originalSelector = @selector(willMoveToSuperview:);
Method originalM = class_getInstanceMethod(cls, originalSelector);
Method supperM = class_getInstanceMethod([UIView class], originalSelector);
NSLog(@"%p",method_getImplementation(originalM));
NSLog(@"%p",method_getImplementation(supperM));
并且實(shí)現(xiàn)這個方法:
- (void)willMoveToSuperview:(UIView *)view {
NSLog(@"1");
}
然后打印發(fā)現(xiàn),兩個的地址不一樣,所以這個方法class_getInstanceMethod,是先在本類中找實(shí)現(xiàn),找不到再去父類找。證明,在子類中找到了,就不會去父類中找了。
現(xiàn)在理解了為什么要這樣寫,那結(jié)合關(guān)聯(lián)對象試著給label寫個屬性。
先介紹三個方法
1,objc_setAssociatedObject(self, @selector(isSystemColor), @(isSystemColor),
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
第一個參數(shù)是要關(guān)聯(lián)的對象。第二個是一個能夠唯一辨識的key(建議用@selector(get方法)),第三個參數(shù)是要關(guān)聯(lián)的值,第四個是關(guān)聯(lián)的方案。這個根據(jù)枚舉的定義,也表達(dá)的很清楚了,基本上和@property的屬性類型對應(yīng)。
2, objc_getAssociatedObject(self, _cmd);
第一個參數(shù)要拿哪個對象的關(guān)聯(lián)對象
第二個參數(shù),是這個關(guān)聯(lián)參數(shù)的key值,看了這里的都知道,每個都有兩個隱藏參數(shù),一個是self,一個是_cmd,也就是@selector()
3,objc_removeAssociatedObjects,基本不用,會移除self的所有關(guān)。如果要移除,直接傳nil就可以移除了。
這里,我理解關(guān)聯(lián)對象的意思,就是把這個數(shù)值以一種字典的形式存起來了,需要的時候再用key值取出來。
而存儲方案,就是關(guān)聯(lián)方案,不過我也沒有測到到底這幾種在ARC下的不同,以及對象的移除時機(jī),所以這里先不做介紹。
demo中用的是給label改變背景顏色,一般項(xiàng)目中會用到改變字體,不過背景顏色來得明顯一些。這里結(jié)合關(guān)聯(lián)對象,是因?yàn)閷?shí)際項(xiàng)目中,有的label不需要改變字體,所以我們需要傳入一個值判斷是否設(shè)為統(tǒng)一的字符,并且又改最少的代碼,這種少數(shù)的label,就給這個對象設(shè)為NO就可以了。
具體代碼可以參看demo
最后不要忘記移除關(guān)聯(lián)對象
objc_setAssociatedObject(self, @selector(isSystemColor), nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);