以前經過學習官方文檔對runtime有了初步的認識,接下來就要研究學習runtime到底能用在哪些地方,能如何改進咱們的程序。html
本文也能夠從icocoa瀏覽。ios
Swizzling能夠分爲method swizzling和class(isa)swizzling兩種。顧名思義就是將方法/類在運行時替換掉。git
在運行時替換/修改某個方法——能夠是本身寫的方法也能夠是系統的方法——固然通常是用於替換框架類中的方法。github
//ZJView.m -Swizzling + (void)swizzleSetFrame { SEL originalSel = @selector(setFrame:); Class myClass = [self class]; Method originMethod = class_getInstanceMethod(myClass, originalSel); const char *originType = method_getTypeEncoding(originMethod); originalIMP = (void *)method_getImplementation(originMethod); class_replaceMethod(myClass, originalSel, (IMP)mySetFrame, originType); } static void mySetFrame(id self, SEL _cmd,CGRect frame) { NSLog(@"run mySetFrame"); if (originalIMP) { frame.origin.y += 20; originalIMP(self, _cmd, frame); } }
如上是在自定義的View類裏替換了setFrame方法(注意這樣作在實際的編碼中沒有意義,由於徹底能夠經過繼承作到這一點,這裏只是從代碼的角度來理解method swizzling)。替換的方法能夠放在+load裏,或者自行顯式的調用。這裏須要注意的是stackoverflow上有不少是這樣進行替換的:objective-c
if(class_addMethod() ) { class_replaceMethod(); } else { method_exchangeImplementations(); }
之因此按這樣的流程處理是想先檢測下class下是否有須要被替換的selector。但其實runtime已經考慮了這種情形,因此直接進行class_replaceMethod便可。 一般狀況下,咱們能夠經過繼承來重載某個方法,但對於沒有繼承關係的類的方法重載ObjC提供了Category。好比在iOS5前,要自定義NavigationBar的背景,咱們就是經過建立一個category來重載drawRect。可是這樣使用Category的話有如下弊端:網絡
因此category每每用於給框架類添加方法。在這種狀況下,method swizzling就是一個很好的選擇。因爲如今iOS的版本也日趨變多,有時也會遇到某些類的方法在不一樣iOS下有不一樣的表現。那麼,咱們就能夠根據實際狀況,徵對不一樣的iOS版本,選擇繼續用默認的實現,或者自定義的實現,或者二者的結合。此外爲了不衝突,method swizzling最好在+load函數裏調用。 鑑於在實踐中使用method swizzling的場合較少,我的體會不夠深入,暫時我的理解只能在這個層次了。我在stakoverflow上找到一篇帖子,對於使用method swizzling須要注意的地方作了詳盡的說明。app
runtime經過object_setClass來動態的替換對象的class。值得注意的是新class的長度要和原先class的長度一致。此外,KVO的實現就是利用了isa swizzling,iOS6PTL中也對此進行了說明。好比對象a要觀察b的某個屬性,在添加observer的時候,系統會生成一箇中間類,並把b的isa指針指向這個新類。這也說明由於是在runtime時處理KVO,使用KVO時必定要注意遵循相應的命名規範。框架
關聯指針指的是在runtime給某對象添加一個變量,添加的變量不會對原有的類產生任何影響——這是優於ObjC擴展(Extension)的地方,主要使用如下方法:函數
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); objc_getAssociatedObject(id object, const void *key); void objc_removeAssociatedObjects(id object);
對於框架類,因爲沒有源碼,能夠經過這種方式添加一些變量。好比UIView,UIAlertView均可以添加tag方便之後從新得到,咱們也能夠經過關聯指針使其與某個對象相關聯。學習
好像也能叫Reflection(反射),不過我不肯定。Introspection是OO語言都應該具有的特性,它指的是在runtime時對象經過請求能夠查詢本身類的關鍵信息的能力。首先ObjC語言自己就有這樣的接口,好比:
-isKindOfClass:; -isMemberOfClass: -respondsToSelector: ;-conformsToProtocol: -isEqual:
這些分別對應着:
這些功能在NSObject類和協議裏定義的,通常狀況下,iOS上的類都能使用。 接下來要介紹兩個開源庫:Mantle 和Overcoat,他們是內省的重要應用。 在實際中,咱們一般會在程序中設計一個Model層,用於Json和Object之間的轉化。比較完備的Model類會考慮到:
通常狀況下,程序裏的Model不會只有一個,所有這樣實現的話,很顯然有很大一部分代碼是「冗餘」的但卻不能經過繼承之類的方法規避。Mantle就是一個很好的選擇,它將你的注意力集中到Model的設計,實現部分只須要一些必須的方法,如:
+ (NSDictionary *)JSONKeyPathsByPropertyKey { //提供Json中key與Model中屬性的對應,若是key與屬性一致能夠忽略不寫 } + (NSValueTransformer *)JSONTransformerForKey:(NSString *)key { //對一些須要進行格式化處理的key進行選擇性操做 }
Introspection在Mantle中的應用就是runtime時經過class_copyPropertyList來獲取類的屬性列表,從而簡化咱們的工做。 至於Overcoat則是AFNetworking和Mantle的一個結合。AFNetworking是繼ASINetwork後,iOS和OS X上出名的網絡庫,並且維護更新比較活躍。Overcoat的主要工做就是把經過AFnetworking獲取的結果轉化成對象,轉化的過程就是使用了Mantle,而且把這部分工做放在了後臺進行。Overcoat提供了一個例子ReadingList,你們能夠好好研究下。注意ReadingList是須要使用到cocoapods的,不知道的朋友,out啦~
以前提到過的,咱們能夠在runtime時添加方法,更進一步的,咱們能夠動態的添加屬性而不用實現聲明,下面的代碼來自gist:
#import <objc/runtime.h> #import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic,strong) NSMutableDictionary *properties; @end @implementation Person -(id) init { self = [super init]; if (self){ _properties = [NSMutableDictionary new]; } return self; } // generic getter static id propertyIMP(id self, SEL _cmd) { return [[self properties] valueForKey:NSStringFromSelector(_cmd)]; } // generic setter static void setPropertyIMP(id self, SEL _cmd, id aValue) { id value = [aValue copy]; NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy]; // delete "set" and ":" and lowercase first letter [key deleteCharactersInRange:NSMakeRange(0, 3)]; [key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)]; NSString *firstChar = [key substringToIndex:1]; [key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]]; [[self properties] setValue:value forKey:key]; } + (BOOL)resolveInstanceMethod:(SEL)aSEL { if ([NSStringFromSelector(aSEL) hasPrefix:@"set"]) { class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v@:@"); } else { class_addMethod([self class], aSEL,(IMP)propertyIMP, "@@:"); } return YES; } @end int main(int argc, char *argv[]) { @autoreleasepool { Person *p = [Person new]; [p setName:@"Jon"]; NSLog(@"%@",[p name]); } }
以上是現階段對runtime的總結,更多內容有待進一步的探索,歡迎一塊兒學習討論。