Objective-C運行時的一些技巧

Apple的iOS8發佈之後,你們都開始了適配的工做了。可是這個過程總會遇到一些攔路虎,例如推送的API改動。但是商業項目上嵌入了各類各樣的第三方靜態庫,這些靜態庫質量良莠不齊,其中一個靜態庫甚至在Xcode6上編譯後出現問題。因而只能使用Xcode5來編譯,但這樣就有一個很糾結的問題就是,UIMutableUserNotificationAction等一些類在舊版的Xcode要麼就是沒法編譯,要麼只能用宏來跳過。html

這時候,我仍是想起了Objective-C的運行時方法,使用NSClassFromString(@"UIMutableUserNotificationAction")來獲取到系統的類。光這樣子仍是有不少不足,由於這個類中有不少方法、屬性。雖然保證了運行的正常,可是編寫這些方法仍是有各類不便。例如各類performSelector:、objc_msgSend、setValue: forKey:,實在寫得很痛苦。我這裏用了一個比較取巧的方法,新建一個僞造的類如「XXXMutableUserNotificationAction」,繼承NSObject便可。而後將UIMutableUserNotificationAction全部的屬性和方法的聲明添加到XXXMutableUserNotificationAction的頭文件。之後,使用UIMutableUserNotificationAction時,就以下方:緩存

1 Class XXXMutableUserNotificationActionClass = NSClassFromString(@"UIMutableUserNotificationAction");
2 XXXUIMutableUserNotificationAction *action = [[XXXMutableUserNotificationActionClass alloc] init];

既可使用Xcode的補全提示,又能夠經過編譯。(若是你們有更好的方法,歡迎探討探討)ide

 

Object-C運行時的方法當然強大,可是使用這些方法仍是有必定的風險。例如靜態分析對於一些運行時的問題是檢查不出來的,這裏我舉一個內存泄漏的例子。個人項目中使用了一些運行時添加屬性的方法,同時爲一些View添加了block類型的屬性。在使用的時候,不當心在block中直接使用了self,就會出現編譯器沒法檢查的內存泄露。泄漏路徑:VC->View->block->VC,造成了循環引用。這種泄漏相對隱蔽一些,但對於常常RAC的童鞋來講,可能已經練就到百毒不侵了(^_^)。由於@weakify和@strongify的頻繁使用,我對這類型泄漏已經比較敏感。__weak typeof(self) weakSelf = self,算是一種雖然難看,可是行之有效的方法,若是有興趣也能夠參考RAC的解決方案,本質上也是同樣的。
題外話,BlockKit包含了一個很好用的分類「NSObject+BKAssociatedObjects」,能夠用更友好的方法實現運行時添加屬性,順帶一提bk_weaklyAssociateValue的實現思路至關巧妙,值得借鑑。
性能

 

最後一個技巧是關於RAC和系統API的一些關係,先來看看一下兩段代碼:ui

1 UIGestureRecognizer *dismissKeyboardGR = [[UIGestureRecognizer alloc] init];
2 [self.view addGestureRecognizer:dismissKeyboardGR];
3 [[[self rac_signalForSelector:@selector(gestureRecognizer:shouldReceiveTouch:)
4                  fromProtocol:@protocol(UIGestureRecognizerDelegate)]
5   takeUntil:dismissKeyboardGR.rac_willDeallocSignal]
6   subscribeNext:^(id x) {
7       [Utils hideKeyboardInAllView];
8   }];
9 dismissKeyboardGR.delegate = self;
1 UIGestureRecognizer *dismissKeyboardGR = [[UIGestureRecognizer alloc] init];
2 [self.view addGestureRecognizer:dismissKeyboardGR];
3 dismissKeyboardGR.delegate = self;
4 [[[self rac_signalForSelector:@selector(gestureRecognizer:shouldReceiveTouch:)
5                  fromProtocol:@protocol(UIGestureRecognizerDelegate)]
6   takeUntil:dismissKeyboardGR.rac_willDeallocSignal]
7   subscribeNext:^(id x) {
8       [Utils hideKeyboardInAllView];
9   }];

先來看看這兩段代碼的區別只在於delegate的設置前後不同,但這就形成了後一段代碼在iOS6上就沒法觸發RAC裏方法,iOS7上正常。爲何呢?spa

這涉及到一個相似緩存的機制。平時,咱們會習慣使用respondsToSelector:來檢查一個對象或者類是否實現了對應的方法,但頻繁調用respondsToSelector:會對性能有必定的影響。特別是UITableView的dataSource一些方法,調用頻率很高的。所以,在設置delegate後,UIGestureRecognizer使用了respondsToSelector:檢查了一次self是否gestureRecognizer:shouldReceiveTouch:的方法,而後把這個結果緩存起來。因爲RAC也使用了相似Method Swizzling方法,所以在設置delegate之後再使用RAC的方法,UIGestureRecognizer也只讀取了緩存,並不會再次檢查,因此認爲self並未實現gestureRecognizer:shouldReceiveTouch:的方法,因而不做調用。(具體緩存的實現方法,能夠參照http://www.cnblogs.com/ipinka/p/3862786.html)code

相關文章
相關標籤/搜索