runtime基礎(chǔ)知識(shí)

前言

學(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í)別:

  1. 通過(guò)Objective-C源代碼
  2. 通過(guò)Foundation庫(kù)中定義的NSObject提供的方法
  3. 通過(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)換成runtimeobjc_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í),它是如何處理的:

messaging1.gif

我們的根類(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)型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評(píng)論 0 9
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂(lè)樂(lè)的簡(jiǎn)書(shū)閱讀 2,240評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,872評(píng)論 33 466
  • Runtime是什么 Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API,其為 iOS 內(nèi)部的核心之一,我...
    SuAdrenine閱讀 974評(píng)論 0 3
  • 現(xiàn)實(shí)生活中,一種商品需同另一種商品交換的比例,是價(jià)值的形式,或叫做交換價(jià)值。商品的價(jià)值形式是商品的社會(huì)形式...
    cooty閱讀 2,454評(píng)論 0 0

友情鏈接更多精彩內(nèi)容