前言
學(xué)習(xí)Objective-C的運(yùn)行時(shí)Runtime系統(tǒng)是很有必要的。個(gè)人覺(jué)得,得之可得天下,失之則失天下。
Objective-C提供了編譯運(yùn)行時(shí),只要有可能,它都可以動(dòng)態(tài)地運(yùn)作。這意味著不僅需要編譯器,還需要運(yùn)行時(shí)系統(tǒng)執(zhí)行編譯的代碼。運(yùn)行時(shí)系統(tǒng)充當(dāng)Objective-C語(yǔ)言的操作系統(tǒng),有了它才能運(yùn)作。
運(yùn)行時(shí)系統(tǒng)所提供功能是非常強(qiáng)大的,在實(shí)際開(kāi)發(fā)中是經(jīng)常使用到的。比如,蘋(píng)果不允許我們給Category追加擴(kuò)展屬性,是因?yàn)樗粫?huì)自動(dòng)生成成員變量,那么我們通過(guò)運(yùn)行時(shí)就可以很好的解決這個(gè)問(wèn)題。另外,常見(jiàn)的模型轉(zhuǎn)字典或者字典轉(zhuǎn)模型,對(duì)象歸檔等。后續(xù)我們?cè)賮?lái)學(xué)習(xí)如何應(yīng)用,本節(jié)只是講講理論。
與Runtime交互
Objective-C程序有有三種與runtime系統(tǒng)交互的級(jí)別:
- 通過(guò)
Objective-C源代碼 - 通過(guò)
Foundation庫(kù)中定義的NSObject提供的方法 - 通過(guò)直接調(diào)用
runtime方法
通過(guò)Objective-C源代碼
在大多數(shù)的部分,運(yùn)行時(shí)系統(tǒng)會(huì)自動(dòng)運(yùn)行并在后臺(tái)運(yùn)行。我們使用它只是寫(xiě)源代碼并編譯源代碼。當(dāng)編譯包含Objective-C類(lèi)和方法的代碼時(shí),編譯器會(huì)創(chuàng)建實(shí)現(xiàn)了語(yǔ)言動(dòng)態(tài)特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用。該數(shù)據(jù)結(jié)構(gòu)捕獲在類(lèi)、擴(kuò)展和協(xié)議中所定義的信息。
最重要的runtime函數(shù)是發(fā)消息函數(shù),在編譯時(shí),編譯器會(huì)轉(zhuǎn)換成類(lèi)似objc_msgSend這樣的發(fā)送消息的函數(shù)。因此,我們通過(guò)寫(xiě)好源代碼,編譯器會(huì)自動(dòng)幫助我們編譯成runtime代碼。
通過(guò)NSObject提供的方法
在Cocoa編程中,大部分的類(lèi)都繼承于NSObject,也就是說(shuō)NSObject通常是根類(lèi),大部分的類(lèi)都繼承于NSObject。有些特殊的情況下,NSObject只是提供了它應(yīng)該要做什么的模板,卻沒(méi)有提供所有必須的代碼。
有些NSObject提供的方法僅僅是為了查詢(xún)運(yùn)動(dòng)時(shí)系統(tǒng)的相關(guān)信息,這此方法都可以反查自己。比如-isKindOfClass:和-isMemberOfClass:都是用于查詢(xún)?cè)诶^承體系中的位置。-respondsToSelector:指明是否接受特定的消息。+conformsToProtocol:指明是否要求實(shí)現(xiàn)在指定的協(xié)議中聲明的方法。-methodForSelector:提供方法實(shí)現(xiàn)的地址。
通過(guò)直接調(diào)用runtime函數(shù)
runtime庫(kù)函數(shù)在usr/include/objc目錄下,我們主要關(guān)注是這兩個(gè)頭文件:
#import <objc/runtime.h>
#import <objc/objc.h>
關(guān)于如何使用,后續(xù)的文章再細(xì)細(xì)講解。
消息(Message)
為什么叫消息呢?因?yàn)槊嫦驅(qū)ο缶幊讨?,?duì)象調(diào)用方法叫做發(fā)送消息。在編譯時(shí),應(yīng)用的源代碼就會(huì)被編將對(duì)象發(fā)送消息轉(zhuǎn)換成runtime的objc_msgSend函數(shù)調(diào)用。
在Objective-C,消息在運(yùn)行時(shí)并不要求實(shí)現(xiàn)。編譯器會(huì)轉(zhuǎn)換消息表達(dá)式:
[receiver message];
在編譯時(shí)會(huì)轉(zhuǎn)換成類(lèi)似這樣的函數(shù)調(diào)用:
objc_msgSend(receiver, selector);
具體會(huì)轉(zhuǎn)換成哪個(gè),我們來(lái)看看官方的原話(huà):
When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
也就是說(shuō),我們是通過(guò)編譯器來(lái)自動(dòng)轉(zhuǎn)換成運(yùn)行時(shí)代碼時(shí),它會(huì)根據(jù)類(lèi)型自動(dòng)轉(zhuǎn)換成下面的其它一個(gè)函數(shù):
- objc_msgSend:其它普通的消息都會(huì)通過(guò)該函數(shù)來(lái)發(fā)送
- objc_msgSend_stret:消息中需要有數(shù)據(jù)結(jié)構(gòu)作為返回值時(shí),會(huì)通過(guò)該函數(shù)來(lái)發(fā)送消息并接收返回值
- objc_msgSendSuper:與objc_msgSend函數(shù)類(lèi)似,只是它把消息發(fā)送給父類(lèi)實(shí)例
- objc_msgSendSuper_stret:與objc_msgSend_stret函數(shù)類(lèi)似,只是它把消息發(fā)送給父類(lèi)實(shí)例并接收數(shù)組結(jié)構(gòu)作為返回值
另外,如果函數(shù)返回值是浮點(diǎn)類(lèi)型,官方說(shuō)明如下:
* arm: objc_msgSend_fpret not used
* i386: objc_msgSend_fpret used for `float`, `double`, `long double`.
* x86-64: objc_msgSend_fpret used for `long double`.
*
* arm: objc_msgSend_fp2ret not used
* i386: objc_msgSend_fp2ret not used
* x86-64: objc_msgSend_fp2ret used for `_Complex long double`.
其實(shí)這是一個(gè)條件編譯,我們不用擔(dān)心是哪種處理器上,我們只需要調(diào)用objc_msgSend_fpret函數(shù)即可。
注意事項(xiàng):一定要調(diào)用所調(diào)用的API支持哪些平臺(tái),亂調(diào)在導(dǎo)致部分平臺(tái)上不支持而崩潰的。
當(dāng)消息被發(fā)送到實(shí)例對(duì)象時(shí),它是如何處理的:

我們的根類(lèi)是NSObject,它會(huì)一層一層的傳遞,直接找到要處理該消息的對(duì)象,若都沒(méi)有找到,正常情況下會(huì)出現(xiàn)Unreconized selector ...這樣的崩潰提示了。
Message Forwarding
當(dāng)發(fā)送消息給一個(gè)不處理該消息的對(duì)象是錯(cuò)誤的。然后在宣布錯(cuò)誤之前,運(yùn)行時(shí)系統(tǒng)給了接收消息的對(duì)象處理消息的第二個(gè)機(jī)會(huì)。
當(dāng)某對(duì)象不處理某消息時(shí),可以通過(guò)重寫(xiě)-forwardInvocation:方法來(lái)提供一個(gè)默認(rèn)的消息響應(yīng)或者避免出錯(cuò)。當(dāng)對(duì)象中找不到方法實(shí)現(xiàn)時(shí),會(huì)按照類(lèi)繼承關(guān)系一層層往上找。
所有元類(lèi)中的isa指針都指向根元類(lèi),而根元類(lèi)的isa指針則指向自身。根元類(lèi)是繼承于根類(lèi)的,與根類(lèi)的結(jié)構(gòu)體成員一致,都是objc_class結(jié)構(gòu)體,不同的是根元類(lèi)的isa指針指向自身,而根類(lèi)的isa指針為nil
當(dāng)對(duì)象查詢(xún)不到相關(guān)的方法,消息得不到該對(duì)象處理,會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制。消息轉(zhuǎn)發(fā)還分為幾個(gè)階段:先詢(xún)問(wèn)receiver或者說(shuō)是它所屬的類(lèi)是否能動(dòng)態(tài)添加方法,以處理當(dāng)前這個(gè)消息,這叫做“動(dòng)態(tài)方法解析”,runtime會(huì)通過(guò)+resolveInstanceMethod:判斷能否處理。如果runtime完成動(dòng)態(tài)添加方法的詢(xún)問(wèn)之后,receiver仍然無(wú)法正常響應(yīng)則Runtime會(huì)繼續(xù)向receiver詢(xún)問(wèn)是否有其它對(duì)象即其它receiver能處理這條消息,若返回能夠處理的對(duì)象,Runtime會(huì)把消息轉(zhuǎn)給返回的對(duì)象,消息轉(zhuǎn)發(fā)流程也就結(jié)束。若無(wú)對(duì)象返回,Runtime會(huì)把消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給receiver最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息。
提示:消息處理越往后,開(kāi)銷(xiāo)也就會(huì)越大,因此最好直接在第一步就可以得到消息處理。
我們看看類(lèi)結(jié)構(gòu)體:
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;
/* Use `Class` instead of `struct objc_class *` */
我們可以看到每個(gè)類(lèi)結(jié)構(gòu)體都會(huì)有一個(gè)isa指針,它是指向元類(lèi)的。它還有一個(gè)父類(lèi)指針super_class,指針父類(lèi)。包含了類(lèi)的名稱(chēng)name、類(lèi)的版本信息version、類(lèi)的一些標(biāo)識(shí)信息info、實(shí)例大小instance_size、成員變量地址列表ivars、方法地址列表methodLists、緩存最近使用的方法地址cache、協(xié)議列表protocols`。
我們?cè)谑褂脮r(shí),經(jīng)常使用到Class,它就是:
typedef struct objc_class *Class;
當(dāng)類(lèi)為根類(lèi)時(shí),它的super_class就會(huì)是nil。普通的Class存儲(chǔ)的是實(shí)例成員,如-號(hào)方法、屬性、成員變量,而isa則指向元類(lèi),而元類(lèi)存儲(chǔ)的是靜態(tài)成員,如+號(hào)方法、static成員。
Type Encoding
| 編碼值 | 含意 |
|---|---|
| c | 代表char類(lèi)型 |
| i | 代表int類(lèi)型 |
| s | 代表short類(lèi)型 |
| l | 代表long類(lèi)型,在64位處理器上也是按照32位處理 |
| q | 代表long long類(lèi)型 |
| C | 代表unsigned char類(lèi)型 |
| I | 代表unsigned int類(lèi)型 |
| S | 代表unsigned short類(lèi)型 |
| L | 代表unsigned long類(lèi)型 |
| Q | 代表unsigned long long類(lèi)型 |
| f | 代表float類(lèi)型 |
| d | 代表double類(lèi)型 |
| B | 代表C++中的bool或者C99中的_Bool |
| v | 代表void類(lèi)型 |
| * | 代表char *類(lèi)型 |
| @ | 代表對(duì)象類(lèi)型 |
| # | 代表類(lèi)對(duì)象 (Class) |
| : | 代表方法selector (SEL) |
| [array type] | 代表array |
| {name=type...} | 代表結(jié)構(gòu)體 |
| (name=type...) | 代表union |
| bnum | A bit field of num bits |
| ^type | A pointer to type |
| ? | An unknown type (among other things, this code is used for function pointers) |
我們想要通過(guò)運(yùn)行時(shí)處理各種類(lèi)型,那么我們必須要知道哪種字符代表什么類(lèi)型。