本文詳細整理了 Cocoa 的 Runtime 系統的知識,它使得 Objective-C 如虎添翼,具有了靈活的動態特性,使這門古老的語言煥發生機。主要內容以下:程序員
引言objective-c
簡介算法
與Runtime交互編程
Runtime術語數組
消息緩存
動態方法解析數據結構
消息轉發框架
健壯的實例變量(Non Fragile ivars)函數
Objective-C Associated Objects佈局
總結
曾經以爲Objc特別方便上手,面對着 Cocoa 中大量 API,只知道簡單的查文檔和調用。還記得初學 Objective-C 時把[receiver message]
當成簡單的方法調用,而無視了「發送消息」這句話的深入含義。因而[receiver message]
會被編譯器轉化爲:
objc_msgSend(receiver, selector)
若是消息含有參數,則爲:
objc_msgSend(receiver, selector, arg1, arg2, ...)
若是消息的接收者可以找到對應的selector,那麼就至關於直接執行了接收者這個對象的特定方法;不然,消息要麼被轉發,或是臨時向接收者動態添加這個selector對應的實現內容,要麼就乾脆玩完崩潰掉。
如今能夠看出[receiver message]
真的不是一個簡簡單單的方法調用。由於這只是在編譯階段肯定了要向接收者發送message這條消息,而receive將要如何響應這條消息,那就要看運行時發生的狀況來決定了。
Objective-C 的 Runtime 鑄就了它動態語言的特性,這些深層次的知識雖然平時寫代碼用的少一些,可是倒是每一個 Objc 程序員須要瞭解的。
由於Objc是一門動態語言,因此它老是想辦法把一些決定工做從編譯鏈接推遲到運行時。也就是說只有編譯器是不夠的,還須要一個運行時系統 (runtime system) 來執行編譯後的代碼。這就是 Objective-C Runtime 系統存在的意義,它是整個Objc運行框架的一塊基石。
Runtime其實有兩個版本:「modern」和 「legacy」。咱們如今用的 Objective-C 2.0 採用的是現行(Modern)版的Runtime系統,只能運行在 iOS 和 OS X 10.5 以後的64位程序中。而OS X較老的32位程序仍採用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統。這兩個版本最大的區別在於當你更改一個類的實例變量的佈局時,在早期版本中你須要從新編譯它的子類,而現行版就不須要。
Runtime基本是用C和彙編寫的,可見蘋果爲了動態系統的高效而做出的努力。你能夠在這裏下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。
Objc 從三種不一樣的層級上與 Runtime 系統進行交互,分別是經過 Objective-C 源代碼,經過 Foundation 框架的NSObject類定義的方法,經過對 runtime 函數的直接調用。
大部分狀況下你就只管寫你的Objc代碼就行,runtime 系統自動在幕後辛勤勞做着。
還記得引言中舉的例子吧,消息的執行會使用到一些編譯器爲實現動態語言特性而建立的數據結構和函數,Objc中的類、方法和協議等在 runtime 中都由一些數據結構來定義,這些內容在後面會講到。(好比objc_msgSend
函數及其參數列表中的id和SEL都是啥)
Cocoa 中大多數類都繼承於NSObject類,也就天然繼承了它的方法。最特殊的例外是NSProxy,它是個抽象超類,它實現了一些消息轉發有關的方法,能夠經過繼承它來實現一個其餘類的替身類或是虛擬出一個不存在的類,說白了就是領導把本身展示給你們風光無限,可是把活兒都交給幕後小弟去幹。
有的NSObject中的方法起到了抽象接口的做用,好比description方法須要你重載它併爲你定義的類提供描述內容。NSObject還有些方法能在運行時得到類的信息,並檢查一些特性,好比class
返回對象的類;isKindOfClass:
和isMemberOfClass:
則檢查對象是否在指定的類繼承體系中;respondsToSelector:
檢查對象可否響應指定的消息;conformsToProtocol:
檢查對象是否實現了指定協議類的方法;methodForSelector:
則返回指定方法實現的地址。
Runtime 系統是一個由一系列函數和數據結構組成,具備公共接口的動態共享庫。頭文件存放於/usr/include/objc
目錄下。許多函數容許你用純C代碼來重複實現 Objc 中一樣的功能。雖然有一些方法構成了NSObject類的基礎,可是你在寫 Objc 代碼時通常不會直接用到這些函數的,除非是寫一些 Objc 與其餘語言的橋接或是底層的debug工做。在Objective-C Runtime Reference中有對 Runtime 函數的詳細文檔。
還記得引言中的objc_msgSend:方法吧,它的真身是這樣的:
id objc_msgSend ( id self, SEL op, ... );
下面將會逐漸展開介紹一些術語,其實它們都對應着數據結構。
objc_msgSend
函數第二個參數類型爲SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,能夠理解爲區分方法的 ID,而這個 ID 的數據結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串,你能夠用 Objc 編譯器命令@selector()
或者 Runtime 系統的sel_registerName
函數來得到一個SEL類型的方法選擇器。
不一樣類中相同名字的方法所對應的方法選擇器是相同的,即便方法名字相同而變量類型不一樣也會致使它們具備相同的方法選擇器,因而 Objc 中方法命名有時會帶上參數類型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長長的方法哦。
objc_msgSend
第一個參數類型爲id,你們對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object
又是啥呢:
struct objc_object { Class isa; };
objc_object
結構體包含一個isa指針,根據isa指針就能夠順藤摸瓜找到對象所屬的類。
之因此說isa是指針是由於Class實際上是一個指向objc_class
結構體的指針:
typedef struct objc_class *Class;
而objc_class
就是咱們摸到的那個瓜,裏面的東西多着呢:
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
能夠看到運行時一個類還關聯了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協議。
其中objc_ivar_list
和objc_method_list
分別是成員變量列表和方法列表:
struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; } struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; }
若是你C語言不是特別好,能夠直接理解爲objc_ivar_list
結構體存儲着objc_ivar
數組列表,而objc_ivar
結構體存儲了類的單個成員變量的信息;同理objc_method_list
結構體存儲着objc_method
數組列表,而objc_method
結構體存儲了類的某個方法的信息。
最後要提到的還有一個objc_cache
,顧名思義它是緩存,它在objc_class
的做用很重要,在後面會講到。
一個 ObjC 類同時也是一個對象,爲了處理類和對象的關係,runtime 庫建立了一種叫作元類 (Meta Class) 的東西。當你發出一個相似[NSObject alloc]的消息時,你事實上是把這個消息發給了一個類對象 (Class Object) ,這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類 (root meta class) 的實例。你會說 NSObject 的子類時,你的類就會指向 NSObject 作爲其超類。可是全部的元類最終都指向根元類爲其超類。全部的元類的方法列表都有可以響應消息的類方法。因此當 [NSObject alloc] 這條消息發給類對象的時候,objc_msgSend()會去它的元類裏面去查找可以響應消息的方法,若是找到了,而後對這個類對象執行方法調用。
Method是一種表明類中的某個方法的類型。
typedef struct objc_method *Method;
而objc_method
在上面的方法列表中提到過,它存儲了方法名,方法類型和方法實現:
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
方法名類型爲SEL,前面提到過相同名字的方法即便在不一樣類中定義,它們的方法選擇器也相同。
方法類型method_types是個char指針,其實存儲着方法的參數類型和返回值類型。
method_imp指向了方法的實現,本質上是一個函數指針,後面會詳細講到。
Ivar是一種表明類中實例變量的類型。
typedef struct objc_ivar *Ivar;
而objc_ivar在上面的成員變量列表中也提到過:
struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif }
PS:OBJC2_UNAVAILABLE之類的宏定義是蘋果在 Objc 中對系統運行版本進行約束的黑魔法,有興趣的能夠查看源代碼。
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個函數指針,這是由編譯器生成的。當你發起一個 ObjC 消息以後,最終它會執行的那段代碼,就是由這個函數指針指定的。而IMP這個函數指針就指向了這個方法的實現。既然獲得了執行某個實例某個方法的入口,咱們就能夠繞開消息傳遞階段,直接執行方法,這在後面會提到。
你會發現IMP指向的方法與objc_msgSend
函數類型相同,參數都包含id和SEL類型。每一個方法名都對應一個SEL類型的方法選擇器,而每一個實例對象中的SEL對應的方法實現確定是惟一的,經過一組id和SEL參數就能肯定惟一的方法實現地址;反之亦然。
在runtime.h中Cache的定義以下:
typedef struct objc_cache *Cache
還記得以前objcclass結構體中有一個struct objc_cache *cache
吧,它究竟是緩存啥的呢,先看看objccache的實現:
struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method buckets[1] OBJC2_UNAVAILABLE; };
Cache爲方法調用的性能進行優化,通俗地講,每當實例對象接收到一個消息時,它不會直接在isa指向的類的方法列表中遍歷查找可以響應消息的方法,由於這樣效率過低了,而是優先在Cache中查找。Runtime 系統會把被調用的方法存到Cache中(理論上講一個方法若是被調用,那麼它有可能從此還會被調用),下次查找的時候效率更高。這根計算機組成原理中學過的 CPU 繞過主存先訪問Cache的道理挺像,而我猜蘋果爲提升Cache命中率應該也作了努力吧。
前面作了這麼多鋪墊,如今終於說到了消息了。Objc 中發送消息是用中括號([])把接收者和消息括起來,而直到運行時纔會把消息與方法實現綁定。
在引言中已經對objc_msgSend
進行了一點介紹,看起來像是objc_msgSend
返回了數據,其實objc_msgSend
從不返回數據而是你的方法被調用後返回了數據。下面詳細敘述下消息發送步驟:
檢測這個 selector 是否是要忽略的。好比 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數了。
檢測這個 target 是否是 nil 對象。ObjC 的特性是容許對一個 nil 對象執行任何一個方法不會 Crash,由於會被忽略掉。
若是上面兩個都過了,那就開始查找這個類的 IMP,先從 cache 裏面找,完了找獲得就跳到對應的函數去執行。
若是 cache 找不到就找一下方法分發表。
若是分發表找不到就到超類的分發表去找,一直找,直到找到NSObject類爲止。
若是還找不到就要開始進入動態方法解析了,後面會提到。
PS:這裏說的分發表其實就是Class中的方法列表,它將方法選擇器和方法實現地質聯繫起來。
其實編譯器會根據狀況在objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
, 或objc_msgSendSuper_stret
四個方法中選擇一個來調用。若是消息是傳遞給超類,那麼會調用名字帶有」Super」的函數;若是消息返回值是數據結構而不是簡單值時,那麼會調用名字帶有」stret」的函數。排列組合正好四個方法。
咱們常常在方法中使用self關鍵字來引用實例自己,但從沒有想過爲何self就能取到調用當前方法的對象吧。其實self的內容是在方法運行時被偷偷的動態傳入的。
當objc_msgSend
找到方法對應的實現時,它將直接調用該方法實現,並將消息中全部的參數都傳遞給方法實現,同時,它還將傳遞兩個隱藏的參數:
– 接收消息的對象(也就是self指向的內容) – 方法選擇器(_cmd指向的內容)
之因此說它們是隱藏的是由於在源代碼方法的定義中並無聲明這兩個參數。它們是在代碼被編譯時被插入實現中的。儘管這些參數沒有被明確聲明,在源代碼中咱們仍然能夠引用它們。在下面的例子中,self引用了接收者對象,而_cmd引用了方法自己的選擇器:
- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }
在這兩個參數中,self 更有用。實際上,它是在方法實現中訪問消息接收者對象的實例變量的途徑。
而當方法中的super關鍵字接收到消息時,編譯器會建立一個objc_super結構體:
struct objc_super { id receiver; Class class; };
這個結構體指明瞭消息應該被傳遞給特定超類的定義。但receiver仍然是self自己,這點須要注意,由於當咱們想經過[super class]
獲取超類時,編譯器只是將指向self的id指針和class的SEL傳遞給了objc_msgSendSuper
函數,由於只有在NSObject類找到class方法,而後class方法調用object_getClass()
,接着調用objc_msgSend(objc_super->receiver, @selector(class))
,傳入的第一個參數是指向self的id指針,與調用[self class]
相同,因此咱們獲得的永遠都是self的類型。
在IMP那節提到過能夠避開消息綁定而直接獲取方法的地址並調用方法。這種作法不多用,除非是須要持續大量重複調用某方法的極端狀況,避開消息發送氾濫而直接調用該方法會更高效。
NSObject類中有個methodForSelector:實例方法,你能夠用它來獲取某個方法選擇器對應的IMP,舉個栗子:
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);
當方法被當作函數調用時,上節提到的兩個隱藏參數就須要咱們明確給出了。上面的例子調用了1000次函數,你能夠試試直接給target發送1000次setFilled:消息會花多久。
PS:methodForSelector:方法是由 Cocoa 的 Runtime 系統提供的,而不是 Objc 自身的特性。
你能夠動態地提供一個方法的實現。例如咱們能夠用@dynamic關鍵字在類的實現文件中修飾一個屬性:
@dynamic propertyName;
這代表咱們會爲這個屬性動態提供存取方法,也就是說編譯器不會再默認爲咱們生成setPropertyName:
和propertyName:
方法,而須要咱們動態提供。咱們能夠經過分別重載resolveInstanceMethod:
和resolveClassMethod:
方法分別添加實例方法實現和類方法實現。由於當 Runtime 系統在Cache和方法分發表中(包括超類)找不到要執行的方法時,Runtime會調用resolveInstanceMethod:
或resolveClassMethod:
來給程序員一次動態添加方法實現的機會。咱們須要用class_addMethod
函數完成向特定類添加特定方法實現的操做:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } @implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @end
上面的例子爲resolveThisMethodDynamically
方法添加了實現內容,也就是dynamicMethodIMP
方法中的代碼。其中 「v@:」 表示返回值和參數,這個符號涉及 Type Encoding
PS:動態方法解析會在消息轉發機制浸入前執行。若是 respondsToSelector:
或instancesRespondToSelector:
方法被執行,動態方法解析器將會被首先給予一個提供該方法選擇器對應的IMP的機會。若是你想讓該方法選擇器被傳送到轉發機制,那麼就讓resolveInstanceMethod:
返回NO。
在消息轉發機制執行前,Runtime 系統會再給咱們一次偷樑換柱的機會,即經過重載- (id)forwardingTargetForSelector:(SEL)aSelector
方法替換消息的接受者爲其餘對象:
- (id)forwardingTargetForSelector:(SEL)aSelector { if(aSelector == @selector(mysteriousMethod:)){ return alternateObject; } return [super forwardingTargetForSelector:aSelector]; }
畢竟消息轉發要耗費更多時間,抓住此次機會將消息重定向給別人是個不錯的選擇,不過千萬別返回self,由於那樣會死循環。
當動態方法解析不做處理返回NO時,消息轉發機制會被觸發。在這時forwardInvocation:
方法會被執行,咱們能夠重寫這個方法來定義咱們的轉發邏輯:
- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; }
該消息的惟一參數是個NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數。咱們能夠實現
forwardInvocation:`方法來對不能處理的消息作一些默認的處理,也能夠將消息轉發給其餘對象來處理,而不拋出錯誤。
這裏須要注意的是參數anInvocation
是從哪的來的呢?其實在forwardInvocation:
消息發送前,Runtime系統會向對象發送methodSignatureForSelector:
消息,並取到返回的方法簽名用於生成NSInvocation
對象。因此咱們在重寫forwardInvocation:
的同時也要重寫methodSignatureForSelector:
方法,不然會拋異常。
當一個對象因爲沒有相應的方法實現而沒法響應某消息時,運行時系統將經過forwardInvocation:
消息通知該對象。每一個對象都從NSObject類中繼承了forwardInvocation:
方法。然而,NSObject中的方法實現只是簡單地調用了doesNotRecognizeSelector:
。經過實現咱們本身的forwardInvocation:
方法,咱們能夠在該方法實現中將消息轉發給其它對象。
forwardInvocation:
方法就像一個不能識別的消息的分發中心,將這些消息轉發給不一樣接收對象。或者它也能夠象一個運輸站將全部的消息都發送給同一個接收對象。它能夠將一個消息翻譯成另一個消息,或者簡單的」吃掉「某些消息,所以沒有響應也沒有錯誤。forwardInvocation:
方法也能夠對不一樣的消息提供一樣的響應,這一切都取決於方法的具體實現。該方法所提供是將不一樣的對象連接到消息鏈的能力。
注意: forwardInvocation:
方法只有在消息接收對象中沒法正常響應消息時纔會被調用。 因此,若是咱們但願一個對象將negotiate
消息轉發給其它對象,則這個對象不能有negotiate
方法。不然,forwardInvocation:
將不可能會被調用。
轉發和繼承類似,能夠用於爲Objc編程添加一些多繼承的效果。一個對象把消息轉發出去,就好似它把另外一個對象中的方法借過來或是「繼承」過來同樣。
這使得不一樣繼承體系分支下的兩個類能夠「繼承」對方的方法,
轉發不只能模擬多繼承,也能使輕量級對象表明重量級對象。弱小的女人背後是強大的男人,畢竟女人遇到難題都把它們轉發給男人來作了。這裏有一些適用案例,能夠參看官方文檔。
儘管轉發很像繼承,可是NSObject類不會將二者混淆。像respondsToSelector:
和 isKindOfClass:
這類方法只會考慮繼承體系,不會考慮轉發鏈。
若是你爲了某些意圖偏要「弄虛做假」讓別人覺得Warrior繼承到了Diplomat的negotiate方法,你得從新實現 respondsToSelector: 和 isKindOfClass:來加入你的轉發算法:
- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. */ } return NO; }
除了respondsToSelector:
和 isKindOfClass:
以外,instancesRespondToSelector:
中也應該寫一份轉發算法。若是使用了協議,conformsToProtocol:
一樣也要加入到這一行列中。相似地,若是一個對象轉發它接受的任何遠程消息,它得給出一個methodSignatureForSelector:
來返回準確的方法描述,這個方法會最終響應被轉發的消息。好比一個對象能給它的替代者對象轉發消息,它須要像下面這樣實現methodSignatureForSelector:
:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
在 Runtime 的現行版本中,最大的特色就是健壯的實例變量。當一個類被編譯時,實例變量的佈局也就造成了,它代表訪問類的實例變量的位置。從對象頭部開始,實例變量依次根據本身所佔空間而產生位移:
超類後面加上咱們本身類的實例變量,看起來不錯。但試想若是那天蘋果更新了NSObject類,發佈新版本的系統的話,那就悲劇了:
咱們自定義的類被劃了兩道線,那是由於那塊區域跟超類重疊了。惟有蘋果將超類改成之前的佈局才能拯救咱們,但這樣也致使它們不能再拓展它們的框架了,由於成員變量佈局被死死地固定了。在脆弱的實例變量(Fragile ivars) 環境下咱們須要從新編譯繼承自 Apple 的類來恢復兼容性。那麼在健壯的實例變量下回發生什麼呢?
在健壯的實例變量下編譯器生成的實例變量佈局跟之前同樣,可是當 runtime 系統檢測到與超類有部分重疊時它會調整你新添加的實例變量的位移,那樣你在子類中新添加的成員就被保護起來了。
須要注意的是在健壯的實例變量下,不要使用sizeof(SomeClass)
,而是用class_getInstanceSize([SomeClass class])
代替;也不要使用offsetof(SomeClass, SomeIvar)
,而要用ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))
來代替。
在 OS X 10.6 以後,Runtime系統讓Objc支持向對象動態添加變量。涉及到的函數有如下三個:
void objcsetAssociatedObject ( id object, const void *key, id value, objcAssociationPolicy policy );
id objc_getAssociatedObject ( id object, const void *key );
void objc_removeAssociatedObjects ( id object );
這些方法以鍵值對的形式動態地向對象添加、獲取或刪除關聯值。其中關聯政策是一組枚舉常量:
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
這些常量對應着引用關聯值的政策,也就是 Objc 內存管理的引用計數機制。
咱們之因此讓本身的類繼承NSObject不只僅由於蘋果幫咱們完成了複雜的內存分配問題,更是由於這使得咱們可以用上 Runtime 系統帶來的便利。可能咱們平時寫代碼時可能不多會考慮一句簡單的[receiver message]
背後發生了什麼,而只是當作方法或函數調用。深刻理解 Runtime 系統的細節更有利於咱們利用消息機制寫出功能更強大的代碼,好比 Method Swizzling 等。
原文出處: 楊蕭玉的博客
原文連接: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/