前言:什么是runtime?根據(jù)官方文檔的解釋,Objective-C語言將決定盡可能的從編譯和鏈接時推遲到運行時。只要有可能,Objective-C總是使用動態(tài)的方式來解決問題。這意味著Objective-C語言不僅需要一個編譯器,同時也需要一個運行時系統(tǒng)來執(zhí)行編譯好的代碼,這兒的運行時系統(tǒng)扮演的角色類似于Objective-C語言的操作系統(tǒng),Objective-C基于該系統(tǒng)來工作。
一、Runtime初識
1、什么是Runtime?
`Runtime`是一套由C,C++,及匯編語言寫成的一套`API`,為我們的Objective-C增加運行時功能,OC的所有代碼在編譯時最終都會轉(zhuǎn)化成直接執(zhí)行`Runtime`中`API`的代碼。
比如下面這個方法的調(diào)用部分
BMPerson * person = [[BMPerson alloc]init];
[person run];
經(jīng)過轉(zhuǎn)化就會變成這樣
objc_msgSend(person, sel_registerName("run"));
2、什么是運行時?什么是編譯時?
1、編譯時:
編譯就是一系列工作,作用就是把我們可讀性非常強的源代碼,比如`Objective`、`Swift`等高級語言編譯成機器語言的過程。比如匯編語言再到最后的二進制從而被我們的系統(tǒng)識別。
2、運行時:
運行時就是我們的代碼run起來后被裝載到內(nèi)存上。
值得注意的是,將靜態(tài)語言編譯和鏈接時期需要做的事放到了運行時來處理之后,我們寫的代碼的靈活度就很高了,比如我們可以選擇把消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換方法的實現(xiàn)等等。
3、Runtime版本和平臺
Runtime運行時系統(tǒng)有兩個已知版本:早期版本(Legacy)和現(xiàn)行版本(Modern),早期版本對應(yīng)的編程接口 Objective 1.0;現(xiàn)行版本對應(yīng)的編程接口 Objective-C 2.0;早期版本和現(xiàn)行版本的區(qū)別就是:早期版本中,如果你想改變類中實例變量的布局,您必須重新編譯該類的所有子類;而現(xiàn)行版本中則無需編譯該類的任何子類就可以達到原來的效果。
iPhone程序和Mac OS X v10.5及以后的系統(tǒng)中的64位程序使用的都是Objective-C系統(tǒng)的現(xiàn)行版本。
其他情況(Mac OS X系統(tǒng)中的32位程序)使用的是早期版本。
4、和運行時系統(tǒng)的交互
Objective-C程序有三種途徑和運行時系統(tǒng)交互:
1、通過Objective-C源代碼
2、通過類NSObject的方法
3、通過運行時系統(tǒng)的函數(shù)
二、Objective-C 的對象和方法的本質(zhì)
先創(chuàng)建一個工程,創(chuàng)建一個類BMPerson繼承自NSObject,聲明并實現(xiàn)BMPerson的實例方法- (void)run;,接著創(chuàng)建一個類BMStudent繼承自BMPerson,聲明并實現(xiàn)實例方法- (void)learn;,
接著在main.m執(zhí)行一段程序并run運行一下
//
// main.m
// RuntimeProjectTest
//
// Created by battleMage on 2019/7/22.
// Copyright ? 2019 battleMage. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BMPerson.h"
#import "BMStudent.h"
#include <objc/runtime.h>
void study(){
printf("跑呀跑呀?。。n");
}
int main(int argc, char * argv[]) {
@autoreleasepool {
//調(diào)用person對象方法
BMPerson * person = [[BMPerson alloc]init];
[person run];
//調(diào)用BMPerson子類BMStudent的對象方法
BMStudent * student = [[BMStudent alloc] init];
[student learn];
//調(diào)用C函數(shù)
study();
}
}
運行完成后,可以看到打印臺打印如下信息
跑呀跑呀!??!
2019-07-22 23:00:18.827778+0800 RuntimeProjectTest[11943:1704750] 跑步
2019-07-22 23:00:18.828556+0800 RuntimeProjectTest[11943:1704750] 好好學(xué)習(xí),天天向上
把C函數(shù)study()的實現(xiàn)部分注釋,就可以發(fā)現(xiàn)study()調(diào)用那一行編譯直接提示報錯 Implicit declaration of function 'study' is invalid in C99,而把BMPerson.m的實現(xiàn)部分注掉,編譯時是不會報錯的,但是點擊run運行時就會打印臺信息報錯:
2019-07-22 23:31:15.114209+0800 RuntimeProjectTest[12107:1729913] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BMPerson run]: unrecognized selector sent to instance 0x6000019f8130'
報錯信息提示的是BMPerson的run沒有實現(xiàn)
上述對比一下,就能明白運行時和編譯時明顯的區(qū)別。
接著刪除main.m中的其他代碼只留下BMPerson的創(chuàng)建和方法調(diào)用,如下
int main(int argc, char * argv[]) {
@autoreleasepool {
//調(diào)用person對象方法
BMPerson * person = [[BMPerson alloc]init];
[person run];
}
}
接著showinfinder,打開終端,使用clang編譯輸出main.cpp文件,在終端輸入命令
clang -rewrite-objc main.m -o main.cpp
看到該文件夾下多出了一個main.cpp的C++文件,打開文件,在最底部找到下面代碼
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
BMPerson * person = ((BMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((BMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BMPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("run"));
}
}
找到這一層先不要著急,然后接著在文件內(nèi)全局搜索BMPerson,能夠找到這部分代碼
#ifndef _REWRITER_typedef_BMPerson
#define _REWRITER_typedef_BMPerson
typedef struct objc_object BMPerson;//注意這一行?。?!
typedef struct {} _objc_exc_BMPerson;
#endif
struct BMPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
看到 typedef struct objc_object BMPerson; 這里其實就很明白了,
Objective-C對象的本質(zhì)其實就是結(jié)構(gòu)體!
再接著看這一塊
BMPerson * person = ((BMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((BMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BMPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("run"));
很明顯:
Objective-C方法的本質(zhì)就是發(fā)送消息!
接下來我們拿[person run]的調(diào)用,仔細分析消息的組成
(void ()(id, SEL))(void )objc_msgSend)((id)person --- 就是消息的接收者
sel_registerName("run") --- SEL是方法編號,具體底層是一個字符串name,這里順便提一下imp,imp是函數(shù)實現(xiàn)的指針,實際過程中是要用SEL方法編號去找到imp,拿到函數(shù)實現(xiàn),然后直接調(diào)用實現(xiàn)部分
我們可以接著在main.m下面打印一下地址就能看出來
//調(diào)用person對象方法
BMPerson * person = [[BMPerson alloc]init];
[person run];
NSLog(@"%p----%p", sel_registerName("run"), @selector(run));
打印臺打印信息:
2019-07-23 22:30:22.259406+0800 RuntimeProjectTest[14063:1897197] 0x1077c2483----0x1077c2483
可以看出這兩個地址是完全一致的,這也就是說@selector(run)在編譯之后就是sel_registerName("run")
三、Objective-C 的對象的結(jié)構(gòu)
Objective-C對象大致分為三類:
實例對象,類對象,元類對象
方法對應(yīng)有:
實例方法,類方法
上一塊,我們通過clang大致了解了一下對象的本質(zhì),我們繼續(xù)深入探索對象的詳細結(jié)構(gòu)部分。
接下來我們command+B點擊 Class 進入objc.h文件,發(fā)現(xiàn)一個結(jié)構(gòu)體重定義
typedef struct objc_class *Class;
繼續(xù)command+B點擊查看,進入runtime.h文件的55行,看到這段代碼,為了直觀,我把注釋直接加在代碼里面
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; //isa指針
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;//父類
const char * _Nonnull name OBJC2_UNAVAILABLE;//類名
long version OBJC2_UNAVAILABLE;//類的版本信息,默認0
long info OBJC2_UNAVAILABLE;//類的信息,供運行期使用的一些位標(biāo)識
long instance_size OBJC2_UNAVAILABLE;//該類的實例變量大小
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;//該類的成員變量的鏈表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; //方法定義的鏈表
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; //方法緩存
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; //協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
什么是isa? 一個經(jīng)過特殊處理優(yōu)化過的指針,指向?qū)ο蟮念悺?/strong> 如果是實例對象,指向的就是它的類;如果是類對象,就指向該類對象的元類,如果是元類對象,就指向另一個基類的元類。(什么是元類?在Objective-C中,每當(dāng)我們創(chuàng)建一個類,編譯時就會創(chuàng)建一個元類,而這個元類的對象就是我們創(chuàng)建的這個類)
我們創(chuàng)建的實例對象,在C語言中就是
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
這里的isa指針就指向了其類地址;下面這圖就是對類,元類,對象的isa指向說明:

通過以上isa指針走位圖,我們可以結(jié)合一些問題,來加深一下我們對runtime isa指針相關(guān)知識點的理解:
問題:OC對象方法存在哪里?類方法存在哪里?
OC對象方法存儲在對應(yīng)的類里面,具體存儲形式是以散列表(hash table)的形式;
OC類方法存儲在對應(yīng)的元類里面,存儲形式同上;
對象在類里面,是以一個實例對象的姿態(tài)出現(xiàn)的;同理,類在元類里面也是以一個實例對象的姿態(tài)出現(xiàn)的。
準(zhǔn)確描述類比對象、類以及元類之間的關(guān)系:對象是類的一個實例,類是元類的一個實例!
所以類方法是存儲在元類里面的,并且是以元類的一個實例方法的形式進行存儲的。