標籤:des style blog http 使用 strong
[0] Outline
-- [1] 版本和平台
-- [2] 與Runtime System互動
-- [3] 方法的動態決議
-- [4] 訊息轉寄
-- [5] 類型編碼
-- [6] 屬性聲明
[1] 版本和平台
Runtime System對於Objective-C來說就好比是它的作業系統,或者說是啟動並執行支撐平台,它使得Objective-C代碼能夠按照既定的語言特性跑起來。相對於C/C++來說,Objective-C儘可能地把一些動作延遲到運行時來執行,即儘可能動態地做事情。因此,它不僅需要一個編譯器,還需要一個運行時環境來執行編譯後的代碼。
Runtime System分為Legacy和Modern兩個版本,一般來說,我們現在用的都是Modern版本。Modern版本的Runtime System有一個顯著的特徵就是“non-fragile”,即父類的成員變數的布局發生改變時,子類不需要重新編譯。此外,還支援為聲明的屬性進行合成操作(即@property和@synthesis)。
下面會討論NSObject類、Objective-C程式如何與Runtime System互動、運行時動態地載入類、發訊息給其它對象,以及運行時如何擷取對象資訊。
[2] 與Runtime System互動
Objective-C程式和Runtime System在三個不同層次進行互動:通過Objective-C源碼;通過NSObject定義的函數;以及通過直接調用runtime functions。
通常來講,Runtime System都是在幕後工作,我們需要做的就是編寫Objective-C代碼,然後編譯。編譯器會為我們建立相應的資料結構和函數調用來實現語言的動態特性。這些資料結構儲存著在類、Category定義和Protocol聲明中所能找到的資訊,包括成員變數模板、selectors,以及其它從源碼中提取到的資訊。
Runtime System是一個動態共用程式庫,位於/usr/include/objc,擁有一套公用的介面,由一系列函數和資料結構組成。開發人員可以使用純C調用一些函數來做編譯器做的事情,或者擴充Runtime System,為開發環境製作一些工具等等。儘管一般情況下,編寫Objective-C並不需要瞭解這些內容,但有時候會很有用。所有的函數都在Objective-C Runtime Reference有文檔化資訊。
Cocoa中大部分對象都是NSObject的子類(NSProxy是一個例外),繼承了NSObject的方法。因此在這個繼承體系中,子類可以根據需求重新實現NSObject定義的一些函數,實現多態和動態性,比如description方法(返回描述自身的字串,類似Python中開頭的三引號)。
一些NSObject定義的方法只是簡單地詢問Runtime System獲得資訊,使得對象可以進行自省(introspection),比如用來確定類類型的isKindOfClass:,確定對象在繼承體系中的位置的isMemberOfClass:,判斷一個對象是否能接收某個特定訊息的respondsToSelector:,判斷一個對象是否遵循某個協議的conformsToProtocol:,以及提供方法實現地址的methodForSelector:。這些方法讓一個對象可以進行自省(introspect about itself)。
最主要的Runtime函數是用來發送訊息的,它由源碼中的訊息運算式激發。發送訊息是Objective-C程式中最經常出現的運算式,而該運算式最終會被轉換成objc_msgSend函數調用。比如一個訊息運算式[receiver message]會被轉換成objc_msgSend(receiver, selector),如果有參數則為objc_msgSend(receiver, selector, arg1, arg2, …)。
訊息只有到運行時才會和函數實現綁定起來:首先objc_msgSend在receiver中尋找selector對應的函數實現;然後調用函數過程,將receiving object(即this指標)和參數傳遞過去;最後,返回函數的傳回值。
發送訊息的關鍵是編譯器為類和對象建立的結構,包含兩個主要元素,一個是指向superclass的指標,另一個是類的dispatch table,該dispatch table中的表項將selector和對應的函數入口地址關聯起來。
當一個對象被建立時,記憶體布局中的第一個元素是指向類結構的指標,isa。通過isa指標,一個對象可以訪問它的類結構,進而訪問繼承的類結構。樣本圖可參見此處。
當向一個對象發送訊息時,objc_msgSend先通過isa指標在類的dispatch table中尋找對應selector的函數入口地址,如果沒有找到,則沿著class hierarchy(類的繼承體系)尋找,直到NSObject類。這就是在運行時選擇函數實現,用OOP的行話來說,就是動態綁定。
為了加速發送訊息的速度,Runtime System為每個類建立了一個cache,用來緩衝selector和對應函數入口地址的映射。
當objc_msgSend找到對應的函數實現時,它除了傳遞函數參數,還傳遞了兩個隱藏參數:receiving object和selector。之所以稱之為隱藏參數,是因為這兩個參數在原始碼中沒有顯示聲明,但還是可以通過self和_cmd來訪問。
當一個訊息要被發送給某個對象很多次的時候,可以直接使用methodForSelector:來進行最佳化,比如下述代碼:
[cpp] view plaincopy
- //////////////////////////////////////////////////////////////
- void (*setter)(id, SEL, BOOL);
- int i;
-
- setter = (void (*)(id, SEL, BOOL))[target
- methodForSelector:@selector(setFilled:)];
- for ( i = 0; i < 1000, i++ )
- setter(targetList[i], @selector(setFilled:), YES);
- //////////////////////////////////////////////////////////////
其中,methodForSelector:是由Cocoa Runtime System提供的,而不是Objective-C本身的語言特性。這裡需要注意轉換過程中函數類型的正確性,包括傳回值和參數,而且這裡的前兩個參數需要顯示聲明為id和SEL。
[3] 方法的動態決議
有時候我們想要為一個方法動態地提供實現,比如Objective-C的@dynamic指示符,它告訴編譯器與屬性對應的方法是動態提供的。我們可以利用resolveInstanceMethod:和resolveClassMethod:分別為對象方法和類方法提供動態實現。
一個Objective-C方法本質上是一個擁有至少兩個參數(self和_cmd)的C函數,我們可以利用class_addMethod向一個類添加一個方法。比如對於下面的函數:
[cpp] view plaincopy
- //////////////////////////////////////////////////////////////
- void dynamicMethodIMP(id self, SEL _cmd) {
- // implementation ….
- }
- //////////////////////////////////////////////////////////////
我們可以利用resolveInstanceMethod:將它添加成一個方法(比如叫resolveThisMethodDynamically):
[cpp] view plaincopy
- //////////////////////////////////////////////////////////////
- @implementation MyClass
- + (BOOL)resolveInstanceMethod:(SEL)aSEL
- {
- if (aSEL == @selector(resolveThisMethodDynamically)) {
- class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "[email protected]:");
- return YES;
- }
- return [super resolveInstanceMethod:aSEL];
- }
- @end
- //////////////////////////////////////////////////////////////
動態決議和發送訊息並不衝突,在訊息機制起作用之前,一個類是有機會動態決議一個方法的。當respondsToSelector:或者instancesRespondToSelector:被啟用時,dynamic method resolver會優先有個機會為這個selector提供一份實現。如果實現了resolveInstanceMethod:,對於不想動態決議而想讓其遵循訊息轉寄機制的selectors,返回NO即可。
Objective-C程式可以在運行時連結新的類和category。動態載入可以用來做很多不同的事情,比如System Preferences裡頭各種模組就是動態載入的。儘管有運行時函數可以動態載入Objective-C模組(objc/objc-load.h中的objc_loadModules),但Cocoa的NSBundle類提供了更方便的動態載入介面。
[4] 訊息轉寄
向一個對象發送它不處理的訊息是一個錯誤,不過在報錯之前,Runtime System給了接收對象第二次的機會來處理訊息。在這種情況下,Runtime System會向對象發一個訊息,forwardInvocation:,這個訊息只攜帶一個NSInvocation對象作為參數——這個NSInvocation對象封裝了原始訊息和相應參數。
通過實現forwardInvocation:方法(繼承於NSObject),可以給不響應的訊息一個預設處理方式。正如方法名一樣,通常的處理方式就是轉寄該訊息給另一個對象:
[cpp] view plaincopy
- //////////////////////////////////////////////////////////////
- - (void)forwardInvocation:(NSInvocation *)anInvocation
- {
- if ([someOtherObject respondsToSelector:[anInvocation selector]])
- [anInvocation invokeWithTarget:someOtherObject];
- else
- [super forwardInvocation:anInvocation];
- }
- //////////////////////////////////////////////////////////////
對於不識別的訊息(在dispatch table中找不到),forwardInvocation:就像一個中轉站,想繼續投遞或者停止不處理,都由開發人員決定。
[5] 類型編碼
為了支援Runtime System,編譯器將傳回值類型、參數類型進行編碼,相應的編譯器指示符是@encode。
比如,void編碼為v,char編碼為c,對象編碼為@,類編碼為#,選擇符編碼為:,而符合類型則由基本類型組成,比如
[cpp] view plaincopy
- typedef struct example {
- id anObject;
- char *aString;
- int anInt;
- } Example;
編碼為{[email protected]*i}。
[6] 屬性聲明
當編譯器遇到屬性聲明時,它會產生一些可描述的中繼資料(metadata),將其與相應的類、category和協議關聯起來。存在一些函數可以通過名稱在類或者協議中尋找這些metadata,通過這些函數,我們可以獲得編碼後的屬性類型(字串),複製屬性的attribute列表(C字串數組)。因此,每個類和協議的屬性列表我們都可以獲得。
與類型編碼類別似,屬性類型也有相應的編碼方案,比如readonly編碼為R,copy編碼為C,retain編碼為&等。
通過property_getAttributes函數可以後去編碼後的字串,該字串以T開頭,緊接@encode type和逗號,接著以V和變數名結尾。比如:
[cpp] view plaincopy
- @property char charDefault;
描述為:Tc,VcharDefault
[cpp] view plaincopy
- @property(retain)ididRetain;
描述為:[email protected],&,VidRetain
Property結構體定義了一個指向屬性描述符的不透明控制代碼:typedef struct objc_property *Property;。
通過class_copyPropertyList和protocol_copyPropertyList函數可以擷取相應的屬性數組:
[cpp] view plaincopy
- objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
- objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
通過property_getName函數可以擷取屬性名稱。
通過class_getProperty和protocol_getProperty可以相應地根據給定名稱擷取到屬性引用:
[cpp] view plaincopy
- objc_property_t class_getProperty(Class cls, const char *name)
- objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
通過property_getAttributes函數可以擷取屬性的@encode type string:
const char *property_getAttributes(objc_property_t property)
以上函數組合成一段範例程式碼:
[cpp] view plaincopy
- @interface Lender : NSObject {
- float alone;
- }
- @property float alone;
- @end
-
- id LenderClass = objc_getClass("Lender");
- unsigned int outCount, i;
- objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
- for (i = 0; i < outCount; i++) {
- objc_property_t property = properties[i];
- fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
- }
轉載自:http://blog.csdn.net/jasonblog/article/details/7246822