iOS Runtime 基礎(chǔ)原理

關(guān)于 Runtime 物独,網(wǎng)上已經(jīng)有很多很好的文章房揭,寫得很詳盡备闲。本篇主要是從新手的角度出發(fā)晌端,逐步介紹 Runtime 的原理、常用方法浅役、應(yīng)用場景等斩松。

相關(guān)鏈接:

蘋果維護(hù)的Runtime開源代碼
GNU維護(hù)一個開源的runtime 版本
官方Api

一、Runtime 是什么

C 語言中觉既,將代碼轉(zhuǎn)換為可執(zhí)行程序惧盹,一般要經(jīng)歷三個步驟,即編譯瞪讼、鏈接钧椰、運(yùn)行在鏈接的時候符欠,對象的類型嫡霞、方法的實(shí)現(xiàn)就已經(jīng)確定好了

而在 Objective-C 中希柿,卻將一些在編譯鏈接過程中的工作诊沪,放到了運(yùn)行階段。也就是說曾撤,就算是一個編譯好的 .ipa 包端姚,在程序沒運(yùn)行的時候,也不知道調(diào)用一個方法會發(fā)生什么挤悉。這也為后來大行其道的「熱修復(fù)」提供了可能渐裸。因此我們稱 Objective-C為一門動態(tài)語言。

這樣的設(shè)計(jì)使 Objective-C 變得靈活装悲,甚至可以讓我們在程序運(yùn)行的時候昏鹃,去動態(tài)修改一個方法的實(shí)現(xiàn)。而實(shí)現(xiàn)這一切的基礎(chǔ)就是 Runtime诀诊。
簡單來說洞渤, Runtime 是一個庫,這個庫使我們可以在程序運(yùn)行時創(chuàng)建對象属瓣、檢查對象您宪,修改類和對象的方法。
至于這個庫是怎么實(shí)現(xiàn)的奠涌,請緊張刺激地往下看宪巨。

二、Runtime 是怎么工作的

要了解 Runtime 是怎么工作的溜畅,首先要知道類和對象在 Objective-C 中是怎么定義的捏卓。

注意:以下會用到 C 語言中結(jié)構(gòu)體的內(nèi)容,包括結(jié)構(gòu)體的定義、為結(jié)構(gòu)體定義別名等怠晴。如果你對這塊不熟悉遥金,建議先復(fù)習(xí)一下這塊的語法。傳送門

1. Class 和 Object

objc.h 中蒜田, Class 被定義為指向 objc_class 的指針稿械,定義如下:

typedef struct objc_class *Class;

objc_class 是一個結(jié)構(gòu)體,在 runtime.h 中的定義如下:

struct objc_class {
    Class isa;                                // 實(shí)現(xiàn)方法調(diào)用的關(guān)鍵
    Class super_class;                        // 父類
    const char * name;                        // 類名
    long version;                             // 類的版本信息冲粤,默認(rèn)為0
    long info;                                // 類信息美莫,供運(yùn)行期使用的一些位標(biāo)識
    long instance_size;                       // 該類的實(shí)例變量大小
    struct objc_ivar_list * ivars;            // 該類的成員變量鏈表
    struct objc_method_list ** methodLists;   // 方法定義的鏈表
    struct objc_cache * cache;                // 方法緩存
    struct objc_protocol_list * protocols;    // 協(xié)議鏈表
};

為了方便理解,我這里去掉了一些聲明梯捕,主要是和 Objective-C 語言版本相關(guān)厢呵,這里可以暫時忽略。完整的定義可以自己去 runtime.h 中查看傀顾。

提示:在 Xcode 中襟铭,使用快捷鍵 command + shift + o ,可以打開搜索窗口短曾,輸入 objc_class 即可看到頭文件定義寒砖。

可以看到,一個類保存了自身所有的成員變量( ivars )嫉拐、所有的方法( methodLists )哩都、所有實(shí)現(xiàn)的協(xié)議( objc_protocol_list )。

比較重要的字段還有 isacache 椭岩,它們是什么東西茅逮,先不著急璃赡,我們來看下 Objective-C 中對象的定義判哥。

struct objc_object {
    Class isa;
};

typedef struct objc_object *id;

這里看到了我們熟悉的 id ,一般我們用它來實(shí)現(xiàn)類似于 C++ 中泛型的一些操作碉考,該類型的對象可以轉(zhuǎn)換為任意一種對象塌计。在這里 id 被定義為一個指向 objc_object 的指針。說明 objc_object 就是我們平時常用的對象的定義侯谁,它只包含一個 isa 指針锌仅。

也就是說,一個對象唯一保存的信息就是它的 Class 的地址 isa墙贱。當(dāng)我們調(diào)用一個對象的方法時热芹,它會通過 isa 去找到對應(yīng)的 objc_class,然后再在 objc_classmethodLists 中找到我們調(diào)用的方法惨撇,然后執(zhí)行伊脓。

再說說 cache ,因?yàn)檎{(diào)用方法的過程是個查找 methodLists 的過程魁衙,如果每次調(diào)用都去查找报腔,效率會非常低株搔。所以對于調(diào)用過的方法,會以 map 的方式保存在 cache 中纯蛾,下次再調(diào)用就會快很多纤房。

2. Meta Class 元類

上一小節(jié)講了 Objective-C 中類和對象的定義,也講了調(diào)用對象方法的實(shí)現(xiàn)過程翻诉。但還留下了許多問題炮姨,比如調(diào)用一個對象的類方法的過程是怎么樣的?還有 objc_class 中也有一個 isa 指針米丘,它是干嘛用的剑令?

現(xiàn)在劃重點(diǎn),在 Objective-C 中拄查,類也被設(shè)計(jì)為一個對象吁津。

其實(shí)觀察 objc_classobjc_object 的定義,會發(fā)現(xiàn)兩者其實(shí)本質(zhì)相同(都包含 isa 指針)堕扶,只是 objc_class 多了一些額外的字段碍脏。相應(yīng)的,類也是一個對象稍算,只是保存了一些字段典尾。

既然說類也是對象,那么類的類型是什么呢糊探?這里就引出了另外一個概念 —— Meta Class(元類)钾埂。

Objective-C 中,每一個類都有對應(yīng)的元類科平。而在元類的 methodLists 中褥紫,保存了類的方法鏈表,即所謂的「類方法」瞪慧。并且類的 isa 指針指向?qū)?yīng)的元類髓考。因此上面的問題答案就呼之欲出,調(diào)用一個對象的類方法的過程如下:

  1. 通過對象的 isa 指針找到對應(yīng)的類弃酌。
  2. 通過類的 isa 指針找到對應(yīng)元類氨菇。
  3. 在元類的 methodLists 中,找到對應(yīng)的方法妓湘,然后執(zhí)行查蓉。

注意:上面類方法的調(diào)用過程不考慮繼承的情況,這里只是說明一下類方法的調(diào)用原理榜贴,完整的調(diào)用流程在后面會提到豌研。

這么說來元類也有一個 isa 指針,元類也應(yīng)該是一個對象。的確是這樣聂沙。那么元類的 isa 指向哪里呢秆麸?為了不讓這種結(jié)構(gòu)無限延伸下去, Objective-C 的設(shè)計(jì)者讓所有的元類的 isa 指向基類(比如 NSObject )的元類及汉。而基類的元類的 isa 指向自己沮趣。這樣就形成了一個完美的閉環(huán)。

下面這張圖可以清晰地表示出這種關(guān)系坷随。


1852765-244b037923a6c2aa.jpg

同時注意 super_class 的指向房铭,基類的 super_class 指向 nil

3. Method

上面講到温眉,「找到對應(yīng)的方法缸匪,然后執(zhí)行」,那么這個「執(zhí)行」是怎樣進(jìn)行的呢类溢?下面就來介紹一下 Objective-C 中的方法調(diào)用凌蔬。

先來看一下 Method 在頭文件中的定義:

typedef struct objc_method *Method;

struct objc_method {
    SEL method_name;
    char * method_types;
    IMP method_imp;
};

Method 被定義為一個 objc_method 指針,在 objc_method 結(jié)構(gòu)體中闯冷,包含一個 SEL 和一個 IMP 砂心,同樣來看一下它們的定義:

// SEL
typedef struct objc_selector *SEL;

// IMP
typedef id (*IMP)(id, SEL, ...); 

1、先說一下 SEL 蛇耀。 SEL 是一個指向 objc_selector 的指針辩诞,而 objc_selector 在頭文件中找不到明確的定義。

我們來測試以下代碼:

SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel);          // 輸出:viewDidLoad
SEL sel1 = @selector(viewDidLoad1);
NSLog(@"%s", sel1);         // 輸出:viewDidLoad1

可以看到纺涤, SEL 不過是保存了方法名的一串字符译暂。因此我們可以認(rèn)為, SEL 就是一個保存方法名的字符串撩炊。

由于一個 Method 只保存了方法的方法名外永,并最終要根據(jù)方法名來查找方法的實(shí)現(xiàn),因此在 Objective-C 中不支持下面這種定義衰抑。

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;

2象迎、再來說 IMP 荧嵌∏河唬可以看到它是一個「函數(shù)指針」。簡單來說啦撮,「函數(shù)指針」就是用來找到函數(shù)地址谭网,然后執(zhí)行函數(shù)。(「函數(shù)指針」了解一下

這里要注意赃春, IMP 指向的函數(shù)的前兩個參數(shù)是默認(rèn)參數(shù)愉择, idSEL 。這里的 SEL 好理解,就是函數(shù)名锥涕。而 id 衷戈,對于實(shí)例方法來說, self 保存了當(dāng)前對象的地址层坠;對于類方法來說殖妇, self 保存了當(dāng)前對應(yīng)類對象的地址。后面的省略號即是參數(shù)列表破花。

3谦趣、到這里, Method 的結(jié)構(gòu)就很明了了座每。 Method 建立了 SELIMP 的關(guān)聯(lián)前鹅,當(dāng)對一個對象發(fā)送消息時,會通過給出的 SEL 去找到 IMP 峭梳,然后執(zhí)行舰绘。

Objective-C 中,所有的方法調(diào)用葱椭,都會轉(zhuǎn)化成向?qū)ο蟀l(fā)送消息除盏。發(fā)送消息主要是使用 objc_msgSend 函數(shù)〈煲裕看一下頭文件定義:

id objc_msgSend(id self, SEL op, ...);

可以看到參數(shù)列表和 IMP 指向的函數(shù)參數(shù)列表是相對應(yīng)的者蠕。 Runtime 會將方法調(diào)用做下面的轉(zhuǎn)換,所以一般也稱 Objective-C 中的調(diào)用方法為「發(fā)送消息」掐松。

[self doSomething];

objc_msgSend(self, @selector(doSomething));

4踱侣、上面看到 objc_msgSend 會默認(rèn)傳入 idSEL 。這對應(yīng)了兩個隱含參數(shù)大磺, self_cmd 抡句。這意味著我們可以在方法的實(shí)現(xiàn)過程中拿到它們,并使用它們杠愧。下面來看個例子:

- (void)testCmd:(NSNumber *)num {

    NSLog(@"%ld", (long)num.integerValue);

    num = [NSNumber numberWithInteger:num.integerValue-1];

    if (num.integerValue > 0) {
        [self performSelector:_cmd withObject:num];
    }
}

嘗試調(diào)用:

[self testCmd:@(5)];

上面會按順序輸出 5, 4, 3, 2, 1 待榔,然后結(jié)束。即我們可以在方法內(nèi)部用 _cmd 來調(diào)用方法自身流济。

5锐锣、上面已經(jīng)介紹了方法調(diào)用的大致過程,下面來討論類之間繼承的情況绳瘟。重新回去看 objc_class 結(jié)構(gòu)體的定義雕憔,當(dāng)中包含一個指向父類的指針 super_class

當(dāng)向一個對象發(fā)送消息時糖声,會去這個類的 methodLists 中查找相應(yīng)的 SEL 斤彼,如果查不到分瘦,則通過 super_class 指針找到父類,再去父類的 methodLists 中查找琉苇,層層遞進(jìn)嘲玫。最后仍然找不到,才走拋異常流程并扇。

下面的圖演示了一個基本的消息發(fā)送框架:


1852765-d5c23b880cf2a7c5.jpg

6趁冈、當(dāng)一個方法找不到的時候,會走攔截調(diào)用和消息轉(zhuǎn)發(fā)流程拜马。我們可以重寫 +resolveClassMethod:+resolveInstanceMethod: 方法渗勘,在程序崩潰前做一些處理。通常的做法是動態(tài)添加一個方法俩莽,并返回 YES 告訴程序已經(jīng)成功處理消息旺坠。如果這兩個方法返回 NO ,這個流程會繼續(xù)往下走扮超,完整的流程如下圖所示:

1852765-3a683919c57a9cda.jpg

4. Category

我們來看一下 Category 在頭文件中的定義:

typedef struct objc_category *Category;

struct objc_category {
    char * category_name;
    char * class_name;
    struct objc_method_list * instance_methods;
    struct objc_method_list * class_methods;
    struct objc_protocol_list * protocols;
}   

Category是一個指向 objc_category結(jié)構(gòu)體的指針取刃,在 objc_category 中包含對象方法列表、類方法列表出刷、協(xié)議列表璧疗。從這里我們也可以看出, Category 支持添加對象方法馁龟、類方法崩侠、協(xié)議,但不能保存成員變量坷檩。

注意:在 Category 中是可以添加屬性的却音,但不會生成對應(yīng)的成員變量、 gettersetter 矢炼。因此系瓢,調(diào)用 Category中聲明的屬性時會報(bào)錯。

我們可以通過「關(guān)聯(lián)對象」的方式來添加可用的屬性句灌。具體操作如下:

  • 1夷陋、在 UIViewController+Tag.h 文件中聲明property
@property (nonatomic, strong) NSString *tag;
  • 2胰锌、在 UIViewController+Tag.m中實(shí)現(xiàn) gettersetter骗绕。記得添加頭文件 #import <objc/runtime.h> 。主要是用到 objc_setAssociatedObjectobjc_getAssociatedObject 這兩個方法匕荸。
static void *tag = &tag;

@implementation UIViewController (Tag)

- (void)setTag:(NSString *)t {
    
    objc_setAssociatedObject(self, &tag, t, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)tag {
    
    return objc_getAssociatedObject(self, &tag);
}

@end
  • 3爹谭、在子類中調(diào)用枷邪。
// 子類 ViewController.m
- (void)testCategroy {
    
    self.tag = @"TAG";
    NSLog(@"%@", self.tag);   // 這里輸出:TAG
}

注意:當(dāng)一個對象被釋放后榛搔, Runtime 回去查找這個對象是否有關(guān)聯(lián)的對象诺凡,有的話,會將它們釋放掉践惑。因此不需要我們手動去釋放腹泌。

注:
深入理解Objective-C:Category

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尔觉,隨后出現(xiàn)的幾起案子凉袱,更是在濱河造成了極大的恐慌,老刑警劉巖侦铜,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件专甩,死亡現(xiàn)場離奇詭異,居然都是意外死亡钉稍,警方通過查閱死者的電腦和手機(jī)涤躲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贡未,“玉大人种樱,你說我怎么就攤上這事】÷保” “怎么了嫩挤?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長消恍。 經(jīng)常有香客問我岂昭,道長,這世上最難降的妖魔是什么狠怨? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任佩抹,我火速辦了婚禮,結(jié)果婚禮上取董,老公的妹妹穿的比我還像新娘棍苹。我一直安慰自己,他們只是感情好茵汰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布枢里。 她就那樣靜靜地躺著,像睡著了一般蹂午。 火紅的嫁衣襯著肌膚如雪栏豺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天豆胸,我揣著相機(jī)與錄音奥洼,去河邊找鬼。 笑死晚胡,一個胖子當(dāng)著我的面吹牛灵奖,可吹牛的內(nèi)容都是我干的嚼沿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼瓷患,長吁一口氣:“原來是場噩夢啊……” “哼骡尽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擅编,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤攀细,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爱态,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谭贪,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年锦担,在試婚紗的時候發(fā)現(xiàn)自己被綠了故河。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吆豹,死狀恐怖鱼的,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痘煤,我是刑警寧澤凑阶,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站衷快,受9級特大地震影響宙橱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘸拔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一师郑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧调窍,春花似錦宝冕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缔恳,卻和暖如春宝剖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歉甚。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工万细, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纸泄。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓赖钞,卻偏偏與公主長得像腰素,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仁烹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉耸弄,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,548評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言咧虎,那么這個「動態(tài)」表現(xiàn)在哪呢卓缰?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,182評論 0 7
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 749評論 0 1
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,132評論 0 9