標籤:成員 protocol proc objc 部分 cal 很多 等價 技術
一、什麼是執行時(Runtime)?
- 執行時是蘋果提供的純C語言的開發庫(執行時是開發中經經常使用到的底層技術)
二、執行時的作用?
- 能獲得某個類的全部成員變數
- 能獲得某個類的全部屬性
- 能獲得某個類的全部方法
- 交換方法實現
- 能動態加入一個成員變數
- 能動態加入一個屬性
- 能動態加入一個方法
三、案例:執行時擷取成員變數名稱
#import <Foundation/Foundation.h>#import "CKPerson.h"#import <objc/runtime.h>int main(int argc, const char * argv[]) { @autoreleasepool { // 成員變數的數量 unsigned int outCount = 0; // 獲得全部的成員變數 // ivars是一個指向成員變數的指標 // ivars預設指向第0個成員變數 Ivar *ivars = class_copyIvarList([CKPerson class], &outCount); // 遍曆全部的成員變數 for (int i = 0; i<outCount; i++) { // 取出i位置相應的成員變數// Ivar ivar = *(ivars + i); Ivar ivar = ivars[i]; // 獲得成員變數的名字 NSLog(@"%s", ivar_getName(ivar)); } // 假設函數名中包括了copy\new\retain\create等字眼,那麼這個函數返回的資料就須要手動釋放 free(ivars);// Ivar ivar = *ivars;// Ivar ivar2 = *(ivars + 1);// NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2)); // 一個Ivar就代表一個成員變數 // int *p; 指向int類型的變數 // Ivar *ivars; 指向Ivar類型的變數 } return 0;}
imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" class="imagebubble-image" />
// 成員變數的數量 unsigned int outCount = 0; // 獲得全部的成員變數 Ivar *ivars = class_copyIvarList([UITextField class], &outCount); // 遍曆全部的成員變數 for (int i = 0; i<outCount; i++) { // 取出i位置相應的成員變數 Ivar ivar = ivars[i]; // 獲得成員變數的名字 NSLog(@"%s", ivar_getName(ivar)); } // 假設函數名中包括了copy\new\retain\create等字眼,那麼這個函數返回的資料就須要手動釋放 free(ivars);
四、iOS底層1、The Runtime 簡介
- Objective-C是一門簡單的語言,95%是C。僅僅是在語言層面上加了些關鍵字和文法。真正讓Objective-C如此強大的是它的執行時。它非常小但卻非常強大。它的核心是訊息分發。
Messages:
- 在Objective-C中。訊息是通過objc_msgSend()這個runtime方法及相近的方法來實現的。這種方法須要一個target。selector,還有一些參數。理論上來說。編譯器僅僅是把訊息分發變成objc_msgSend來執行。比方以下這兩行代碼是等價的。
[array insertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Objects, Classes, MetaClasses
typedef struct objc_object { Class isa;} *id;
- object的class儲存了方法列表,還有指向父類的指標。
但classes也是objects,也會有isa變數,那麼它又指向哪兒呢?這裡就引出了第三個類型: metaclasses。一個 metaclass被指向class,class被指向object。
它儲存了全部實現的方法列表。以及父類的metaclass。假設想更清楚地瞭解objects,classes以及metaclasses是怎樣一起工作地,能夠閱讀這篇文章。
Methods, Selectors and IMPs
我們知道了執行時會發訊息給對象。
我們也知道一個對象的class儲存了方法列表。
那麼這些訊息是怎樣映射到方法的,這些方法又是怎樣被執行的呢?
第一個問題的答案非常easy。
class的方法列表事實上是一個字典。key為selectors,IMPs為value。一個IMP是指向方法在記憶體中的實現。
非常重要的一點是,selector和IMP之間的關係是在執行時才決定的,而不是編譯時間。這樣我們就能玩出些花樣。
IMP一般是指向方法的指標,第一個參數是self。類型為id,第二個參數是_cmd。類型為SEL,餘下的是方法的參數。
這也是self和_cmd被定義的地方。以下示範了Method和IMP
- (id)doSomethingWithInt:(int)aInt{}id doSomethingWithInt(id self, SEL _cmd, int aInt){}
- 如今我們知道了objects,classes,selectors,IMPs以及訊息分發,那麼執行時究竟能做什麼呢?
執行時究竟能做什麼呢?
class
- class開頭的方法是用來改動和自省classes。
- 方法如:
- 能拿到一個class的全部內容
class_addIvar, class_addMethod, class_addProperty和class_addProtocol同意重建classes。
class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
- 返回單個內容
class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
- 一些通用的自省方法
class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
- 建立一個object
class_createInstance來建立一個object
ivar
- 這些方法能讓你得到名字。記憶體位址和Objective-C type encoding。
method
method_getName, method_getImplementation, method_getReturnType等等
objc
- 一旦拿到了object,你就能夠對它做一些自省和改動。你能夠get/set ivar, 使用object_copy和object_dispose來copy和free object的記憶體。不僅是拿到一個class,而是能夠使用object_setClass來改變一個object的class。
property
- 屬性儲存了非常大一部分資訊。除了拿到名字,你還能夠使用property_getAttributes來發現property的很多其它資訊。如傳回值、是否為atomic、getter/setter名字、是否為dynamic、背後使用的ivar名字、是否為弱引用。
protocol
sel
- 最後我們有一些方法能夠處理 selectors,比方擷取名字,注冊一個selector等等。
2、執行時能幹什嗎?(舉例)2.1 Classes And Selectors From Strings
比較基礎的一個動態特性是通過String來產生Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起來非常easy:
Class stringclass = NSClassFromString(@"NSString")
於是我們就得到了一個string class。接下來:
NSString *myString = [stringclass stringWithString:@"Hello World"];
為什麼要這麼做呢?
直接使用Class不是更方便?通常情況下是。但有些情境下這種方法會非常實用。首先,能夠得知是否存在某個class,NSClassFromString 會返回nil,假設執行時不存在該class的話。
還有一個使用情境是依據不同的輸入返回不同的class或method。
比方你在解析一些資料。每個資料項目都有要解析的字串以及自身的類型(String,Number。Array)。你能夠在一個方法裡搞定這些,也能夠使用多個方法。當中一個方法是擷取type,然後使用if來調用匹配的方法。
還有一種是依據type來產生一個selector,然後調用之。以下是兩種實現方式:
- (void)parseObject:(id)object { for (id data in object) { if ([[data type] isEqualToString:@"String"]) { [self parseString:[data value]]; } else if ([[data type] isEqualToString:@"Number"]) { [self parseNumber:[data value]]; } else if ([[data type] isEqualToString:@"Array"]) { [self parseArray:[data value]]; } }}- (void)parseObjectDynamic:(id)object { for (id data in object) { [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]]; }}- (void)parseString:(NSString *)aString {}- (void)parseNumber:(NSString *)aNumber {}- (void)parseArray:(NSString *)aArray {}
2.2 Method Swizzling
之前我們講過。方法由兩個部分組成。Selector相當於一個方法的id;IMP是方法的實現。這樣分開的一個便利之處是selector和IMP之間的相應關係能夠被改變。比方一個 IMP 能夠有多個 selectors 指向它。
而 Method Swizzling 能夠交換兩個方法的實現。也許你會問“什麼情況下會須要這個呢?”。我們先來看下Objective-C中,兩種擴充class的途徑。首先是 subclassing。你能夠重寫某個方法。調用父類的實現,這也意味著你必須使用這個subclass的執行個體,但假設繼承了某個Cocoa class,而Cocoa又返回了原先的class(比方 NSArray)。這種情況下,你會想加入一個方法到NSArray。也就是使用Category。99%的情況下這是OK的,但假設你重寫了某個方法。就沒有機會再調用原先的實現了。
Method Swizzling 能夠搞定這個問題。
你能夠重寫某個方法而不用繼承,同一時候還能夠調用原先的實現。通常的做法是在category中加入一個方法(當然也能夠是一個全新的class)。
能夠通過method_exchangeImplementations這個執行時方法來交換實現。
來看一個demo,這個demo示範了怎樣重寫addObject:方法來紀錄每個新加入的對象。
#import <objc/runtime.h>@interface NSMutableArray (LoggingAddObject)- (void)logAddObject:(id)aObject;@end@implementation NSMutableArray (LoggingAddObject)+ (void)load { Method addobject = class_getInstanceMethod(self, @selector(addObject:)); Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:)); method_exchangeImplementations(addObject, logAddObject);}- (void)logAddObject:(id)aobject { [self logAddObject:aObject]; NSLog(@"Added object %@ to array %@", aObject, self);}@end
- 我們把方法交換放到了load中,這種方法僅僅會被調用一次。並且是執行時載入。假設指向暫時用一下,能夠放到別的地方。注意到一個非常明顯的遞迴調用logAddObject:。這也是Method Swizzlingeasy把我們搞混的地方,由於我們已經交換了方法的實現,所以事實上調用的是addObject:
動態繼承、交換
我們能夠在執行時建立新的class,這個特性用得不多,但事實上它還是非常強大的。你能通過它建立新的子類,並加入新的方法。
但這種一個子類有什麼用呢?別忘了Objective-C的一個關鍵點:object內部有一個叫做isa的變數指向它的class。
這個變數能夠被改變,而不須要又一次建立。然後就能夠加入新的ivar和方法了。
能夠通過以下命令來改動一個object的class.
object_setClass(myObject, [MySubclass class]);
這能夠用在Key Value Observing。當你開始observing an object時。Cocoa會建立這個object的class的subclass,然後將這個object的isa指向新建立的subclass。
動態方法處理
眼下為止,我們討論了方法交換。以及已有方法的處理。那麼當你發送了一個object無法處理的訊息時會發生什麼呢?非常明顯,"it breaks"。
大多數情況下確實如此。但Cocoa和runtime也提供了一些應對方法。
首先是動態方法處理。通常來說,處理一個方法,執行時尋找匹配的selector然後執行之。
有時,你僅僅想在執行時才建立某個方法,比方有些資訊僅僅有在執行時才幹得到。要實現這個效果,你須要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:。假設確實添加了一個方法,記得返回YES。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector { if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "[email protected]:"); return YES; } return [super resolveInstanceMethod:aSelector];}
訊息轉寄
假設 resolve method 返回NO。執行時就進入下一步驟:訊息轉寄。有兩種常見用例。1) 將訊息轉寄到還有一個能夠處理該訊息的object。2) 將多個訊息轉寄到同一個方法。
訊息轉寄分兩步。首先,執行時調用-forwardingTargetForSelector:,假設僅僅是想把訊息發送到還有一個object,那麼就使用這種方法,由於更高效。
假設想要改動訊息,那麼就要使用-forwardInvocation:,執行時將訊息打包成NSInvocation。然後返回給你處理。處理完之後,調用invokeWithTarget:。
Cocoa有幾處地方用到了訊息轉寄,基本的兩個地方是代理(Proxies)和響應鏈(Responder Chain)。
NSProxy是一個輕量級的class,它的作用就是轉寄訊息到還有一個object。
假設想要惰性載入object的某個屬性會非常實用。
NSUndoManager也實用到。只是是截取訊息,之後再執行。而不是轉寄到其它的地方。
響應鏈是關於Cocoa怎樣處理與發送事件與行為到相應的對象。
比方說,使用Cmd+C執行了copy命令。會發送-copy:到響應鏈。首先是First Responder,一般是當前的UI。假設沒有處理該訊息,則轉寄到下一個-nextResponder。這麼一直下去直到找到能夠處理該訊息的object,或者沒有找到。報錯。
使用Block作為Method IMP
- iOS 4.3帶來了非常多新的runtime方法。除了對properties和protocols的加強。還帶來一組新的以 imp 開頭的方法。通常一個 IMP 是一個指向方法實現的指標。頭兩個參數為 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 帶來了block,imp_implementationWithBlock() 能讓我們使用block作為 IMP,以下這個程式碼片段展示了怎樣使用block來加入新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) { NSLog(@"Hello %@", string);});class_addMethod([MYclass class], @selector(sayHello:), myIMP, "[email protected]:@");
- 能夠看到。Objective-C 表面看起來挺簡單,但還是非常靈活的,能夠帶來非常多可能性。動態語言的優勢在於在不擴充語言本身的情況下做非常多非常機靈的事情。比方Key Value Observing。提供了優雅的API能夠與已有的代碼無縫結合,而不須要新增語言層級的特性。
iOS 執行時