下午接到一個(gè)有趣的問(wèn)題:
看到這個(gè)問(wèn)題的第一想法就是利用runtime
的方法交換琳水,通過(guò)自己的方法替換系統(tǒng)方法,在自己的方法里面添加判斷褒墨。
當(dāng)列表滑動(dòng)的時(shí)候會(huì)去調(diào)用scrollViewDidScroll
這個(gè)代理方法炫刷,需求實(shí)現(xiàn)的切入點(diǎn)應(yīng)該就是這里。但是Method Swizzling
方法只能替換類本身的方法郁妈,對(duì)于delegate
這種虛函數(shù)一樣的東西就無(wú)從下手了。所以Method Swizzling
就暫且放在一邊绍申,那通過(guò)kvo
呢噩咪?在擴(kuò)展里面去監(jiān)聽(tīng)某個(gè)變化的值?scrollView
剛好有個(gè)contentOffset
符合要求极阅,但是kvo
是不能寫(xiě)在+ (void)load
方法里面的胃碾,這時(shí)候靈光一閃,我們可以用Method Swizzling
來(lái)替換contentOffset
的set
方法敖畈仆百!
思路有了,那就上代碼:
#import "UIScrollView+Additions.h"
#import <objc/runtime.h>
@implementation UIScrollView (Additions)
+ (void)load {
Method contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(setContentOffset:));
Method class_contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(class_setContentOffset:));
method_exchangeImplementations(contentOffsetMethod, class_contentOffsetMethod);
}
-(void)class_setContentOffset:(CGPoint)contentOffset{
if (contentOffset.y > self.bounds.size.height) {
NSLog(@"change sucess!");
} else {
NSLog(@"change fail!");
}
[self class_setContentOffset:contentOffset];
}
@end
運(yùn)行效果如下:
沒(méi)毛病~
干貨: Method Swizzling
這種我們既不需要源代碼奔脐,也不需要通過(guò)繼承子類類覆寫(xiě)方法就能改變這個(gè)類本身功能的方案被稱為——方法調(diào)配(method swizzling)
類的方案列表會(huì)把選擇子的名稱映射到相關(guān)方法的實(shí)現(xiàn)之上俄周,是的“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠根據(jù)此找到應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式來(lái)表示髓迎,這種指針叫做IMP
峦朗。原型如下:
id (*IMP)(ID,SEL,...)
NSString
類可以相應(yīng)lowercaseString
,uppercaseString
,capitalizedString
等選擇子。這張映射表中的每個(gè)選擇子都映射到了不同的IMP之上排龄,如圖:
runtime
運(yùn)行時(shí)提供的幾個(gè)方法都能來(lái)操作這張表波势。deveolper可以向其中新增selector
,也可以改變某個(gè)selector
對(duì)應(yīng)的方法實(shí)現(xiàn)橄维,還可以交換兩個(gè)selector
所映射到的指針尺铣。經(jīng)過(guò)幾次操作之后,類的方法表就會(huì)變成下圖的樣子:
在新的映射表中争舞,多了一個(gè)名為newSelector
的selector
,capitalizedString
的實(shí)現(xiàn)也變了凛忿,而lowercaseString
與uppercaseString
的實(shí)現(xiàn)則互相轉(zhuǎn)換了。上述修改均無(wú)需編寫(xiě)子類兑障,只要修改了“方法表”的布局侄非,就會(huì)反映到程序中所有的NSString
實(shí)例指向蕉汪。這下大家見(jiàn)識(shí)到此特性的強(qiáng)大之處了吧?
本條將會(huì)談到如何互換兩個(gè)方法實(shí)現(xiàn)逞怨。通過(guò)此操作者疤,可為已有的方法添加新功能。不過(guò)在講解怎樣添加新功能之前叠赦,我們先來(lái)看看怎樣互換兩個(gè)已經(jīng)寫(xiě)好的方法實(shí)現(xiàn)驹马。想交換方法實(shí)現(xiàn),可用下列函數(shù):
void method_exchangeImplementations(Method m1, Method m2)
此函數(shù)的兩個(gè)參數(shù)表示待交換的兩個(gè)方法實(shí)現(xiàn)除秀,而方法實(shí)現(xiàn)則可通過(guò)下列函數(shù)獲得:
//得到類的實(shí)例方法(-號(hào)方法)
Method class_getInstanceMethod(Class aClass, SEL aSelector)
//得到類的類方法(+號(hào)方法)
Method class_getInstanceMethod(Class aClass, SEL aSelector)
此函數(shù)根據(jù)給定的選擇從類中取出與之相關(guān)的方法糯累。會(huì)執(zhí)行下列代碼,即可交換前面提到的兩個(gè)方法實(shí)現(xiàn):
Method contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(setContentOffset:));
Method class_contentOffsetMethod = class_getInstanceMethod([UIScrollView class], @selector(class_setContentOffset:));
method_exchangeImplementations(contentOffsetMethod, class_contentOffsetMethod);
從現(xiàn)在開(kāi)始册踩,如果UIScrollView
上調(diào)用了setContentOffset:
泳姐,那么將會(huì)執(zhí)行class_contentOffsetMethod:
的方法實(shí)現(xiàn)。然后我們可以在class_contentOffsetMethod
方法里面去實(shí)現(xiàn)所需的附加功能暂吉,并調(diào)用原有實(shí)現(xiàn)胖秒。
-(void)class_setContentOffset:(CGPoint)contentOffset{
NSLog(@"這里調(diào)用的先后順序?慕的?");
if (contentOffset.y > self.bounds.size.height) {
NSLog(@"change sucess!");
} else {
NSLog(@"change fail!");
}
[self class_setContentOffset:contentOffset];
}
這段代碼看上去好像會(huì)陷入遞歸調(diào)用的死循環(huán)阎肝,不過(guò)請(qǐng)記住,此方法是準(zhǔn)備和setContentOffset:
方法互換的肮街。所以在運(yùn)行時(shí)风题,class_setContentOffset:
實(shí)際上對(duì)應(yīng)的是setContentOffset:
方法實(shí)現(xiàn)。