iOS底層探索之Runtime(一):運行時&方法的本質(zhì)

主題

1. 回顧

在之前的幾篇博客里面辕万,已經(jīng)對OC類的底層結(jié)構(gòu)進行了分析,并對內(nèi)部主要的成員變量(isa/bits)做了詳細的分析瓶盛。在上兩個博客
iOS底層探索之類的結(jié)構(gòu)—cache分析(上)
iOS底層探索之類的結(jié)構(gòu)—cache分析(下)

對類中的cache做了比較詳細的分析带猴。后面通過斷點查看匯編可以發(fā)現(xiàn)在insert方法調(diào)用流程之前观堂,還有一個cache讀取流程,objc_msgSendcache_getImp蛤签。這就涉及到Runtime的知識點了辞友,之前的內(nèi)容都是承上啟下的,是互相關(guān)聯(lián)的。

2. Runtime

2.1 什么是Runtime

runtime翻譯過來稱為運行時称龙,與之對應的是編譯時留拾。大部分的iOS開發(fā)人員,都聽過runtime這個詞鲫尊,也知道運行時痴柔。但只是停留在表面,只是知道而已疫向,并沒有去深入的去探索和分析過竞帽。

OC語言是一門動態(tài)語言,擁有動態(tài)語言的三大特性:動態(tài)類型鸿捧、動態(tài)綁定屹篓、動態(tài)加載。而底層實現(xiàn)就是熟悉又陌生的Runtime匙奴。

  • 運行時是一種面向?qū)ο蟮木幊陶Z言(面向?qū)ο缶幊蹋┑倪\行環(huán)境堆巧。運行時表明了在某個時間段內(nèi),哪個程序正在運行泼菌。運行時是計算機程序運行生命周期內(nèi)的一個階段谍肤,其它階段還包括:編譯時、鏈接時和加載時哗伯。簡單理解就是荒揣, 代碼跑起來,被裝載到內(nèi)存中的過程焊刹。(你的代碼保存在磁盤上沒裝入內(nèi)存之前是個死家伙系任,只有跑到內(nèi)存中才變成活的)。
  • 編譯時顧名思義就是正在編譯的時候虐块。那什么叫編譯呢?就是編譯器幫你把源代碼翻譯成機器能識別的二進制代碼 俩滥。
  • (當然只是一般意義上這么說,實際上可能只是翻譯成某個中間狀態(tài)的語言。比如 Java 只有JVM識別的字節(jié)碼贺奠,C#中只有CLR能識別的MSIL霜旧。另外還有鏈接器、匯編器儡率、為了了便于理解我們可以統(tǒng)稱為編譯器)
  • 那編譯時就是簡單的作一些翻譯工作挂据,比如檢查老兄你有沒有粗心寫錯啥關(guān)鍵字了。
  • 詞法分析,語法分析之類的過程儿普。就像個老師檢查學生的作文中有沒有錯別字和病句一樣 崎逃。
  • 如果發(fā)現(xiàn)啥錯誤編譯器就告訴你,平時使用Xcode時,點下build那就開始編譯箕肃。
  • 如果下面有errors或者warning信息婚脱,那都是編譯器檢查出來的。這時的錯誤就叫編譯時錯誤勺像,這個過程中做的類型檢查也就叫編譯時類型檢查障贸,或靜態(tài)類型檢查(所謂靜態(tài)嘛就是沒把真把代碼放內(nèi)存中運行起來,而只是把代碼當作文本來掃描下)。
image

2.2 runtime的使用的三種方式

runtime的使用的三種方式吟宦,其三種實現(xiàn)方法與編譯層和底層的關(guān)系如圖所示

  • 通過OC上層的代碼實現(xiàn)篮洁,例如 [JPerson hello]

  • 通過NSObject方法實現(xiàn),例如isKindOfClass

  • 通過Runtime API底層方法實現(xiàn)殃姓,例如class_getInstanceSize

    Runtime三種方式及底層的關(guān)系圖

圖中的compiler就是編譯器袁波,就是我們熟悉的LLVM

image

3. OC方法的本質(zhì)

在之前的一篇博客iOS開發(fā)之結(jié)構(gòu)體底層探索我們知道平時寫的OC代碼,底層實現(xiàn)其實都是C/C++的代碼實現(xiàn)的蜗侈,再經(jīng)過編譯器LLVM編譯篷牌,最終轉(zhuǎn)化為機器語言。
通過clang編譯的源碼踏幻,理解了OC對象的本質(zhì)(結(jié)構(gòu)體)枷颊,同樣的,我們也可以使用clang命令編譯成main.cpp文件该面,看看方法的本質(zhì)是什么夭苗?

3.1 objc_msgSend

  • 編譯前
@interface JPPerson : NSObject
@property (nonatomic, readwrite , copy) NSString *personName;
- (void)superTest;
@end

@implementation JPPerson
- (void)superTest {
    NSLog(@"這是父類");
}
@end

@interface JPStudent : JPPerson

@property (nonatomic, readwrite , copy) NSString *studentName;
- (void)test;

@end

@implementation JPStudent
- (void)test {
    NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JPStudent *stu = [[JPStudent alloc]init];
        [stu test];
        [stu superTest];
    }
    return 0;
}
  • 編譯后
int main(int argc, const char * argv[]) {
 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  JPStudent *stu = ((JPStudent *(*)(id, SEL))(void *)objc_msgSend)((id)((JPStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JPStudent"), sel_registerName("alloc")), sel_registerName("init"));
     
  ((void (*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("test"));
  ((void (*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("superTest"));
 }
 return 0;
}

通過上述代碼可以看出,OC的方法調(diào)用隔缀,底層變成了objc_msgSend题造,也就是我們熟悉的消息發(fā)送

我們可以通過模仿objc_msgSend方法來實現(xiàn),[stu test]的調(diào)用

objc_msgSend測試

從控制臺的輸出可以看到猾瘸,是一模摸一樣樣界赔。
由此可以斷定 [stu test]等價于objc_msgSend(stu,sel_registerName("test"))

厲害.png

注意

不能直接調(diào)用objc_msgSend,需要導入頭文件#import <objc/message.h>

需要將target --> Build Setting -->搜索msg -- 將enable strict checking of obc_msgSend callsYES改為NO牵触,將嚴厲的檢查機制關(guān)掉仔蝌,否則objc_msgSend的參數(shù)會報錯。

  • 未導入#import <objc/message.h>
未導入頭文件報錯
  • 啟用 objc_msgSend 調(diào)用的嚴格檢查荒吏,設置為NO
嚴格檢查設置

objc_msgSend(消息的接受者,消息的主體(sel + 參數(shù)))

3.2 objc_msgSendSuper

在上面??在main函數(shù)中調(diào)用了父類的方法[stu superTest]敛惊,clang編譯的源碼里面發(fā)現(xiàn)了objc_msgSendSuper

objc_msgSendSuper

這是子類完全調(diào)用了父類的方法绰更,那么我們子類要是也有一個superTest方法瞧挤,但是子類并沒有實現(xiàn)這個方法,那么我們看看結(jié)果如何儡湾?

@interface JPPerson : NSObject
@property (nonatomic, readwrite , copy) NSString *personName;
- (void)superTest;
@end

@implementation JPPerson
- (void)superTest {
    NSLog(@"%s",__func__);
}
@end

@interface JPStudent : JPPerson

@property (nonatomic, readwrite , copy) NSString *studentName;
- (void)test;
- (void)superTest;
@end

@implementation JPStudent
- (void)test {
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JPStudent *stu = [[JPStudent alloc]init];
        [stu test];
        NSLog(@"-------華麗的分割線-----------");
        objc_msgSend(stu,sel_registerName("test"));
        [stu superTest];
    }
    return 0;
}

打印結(jié)果:

 -[JPStudent test]
 -------華麗的分割線-----------
 -[JPStudent test]
 -[JPPerson superTest]
 
Program ended with exit code: 0

對象的方法調(diào)用特恬,實際是父類的實現(xiàn)方法,為了驗證這個說法徐钠,我們可以嘗試通過objc_msgSendSuper實現(xiàn)驗證癌刽。

objc_msgSendSuper方法中有兩個參數(shù)(結(jié)構(gòu)體,sel),其結(jié)構(gòu)體類型是objc_super定義的結(jié)構(gòu)體對象显拜,且需要指定receiversuper_class兩個屬性衡奥,源碼實現(xiàn)定義如下

objc_msgSendSuper

通過查看蘋果的源碼,找到了如下方法

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
objc_super

代碼改造:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JPStudent *stu = [[JPStudent alloc]init];
        JPPerson *person = [JPPerson alloc];
        [person superTest];

        struct objc_super jpsuper;
        jpsuper.receiver = stu; //消息的接收者
        jpsuper.super_class = [JPStudent class]; //告訴父類是誰远荠,改成 [JPPerson class]也是一樣的
        //消息的接受者還是自己 -> 父類 -> 方法么有找到請你直接找我的父親
        objc_msgSendSuper(&jpsuper, sel_registerName("superTest"));
    }
    return 0;
}

打印結(jié)果

[26066:278406] -[JPPerson superTest]
[26066:278406] -[JPPerson superTest]

4. 總結(jié)

  • OC調(diào)用方法矮固,其實本質(zhì)是發(fā)送消息(objc_msgSend)

  • OC方法的調(diào)用,首先是在類中查找譬淳,如果類中沒有找到档址,會到類的父類中查找。

  • 子類調(diào)用父類的方法邻梆,底層會調(diào)用objc_msgSendSuper

更多內(nèi)容持續(xù)更新

?? 請動動你的小手守伸,點個贊????

?? 喜歡的可以來一波,收藏+關(guān)注浦妄,評論 + 轉(zhuǎn)發(fā)尼摹,以免你下次找不到我,哈哈????

??歡迎大家留言交流校辩,批評指正窘问,互相學習??,提升自我??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宜咒,一起剝皮案震驚了整個濱河市惠赫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌故黑,老刑警劉巖儿咱,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異场晶,居然都是意外死亡混埠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門诗轻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钳宪,“玉大人,你說我怎么就攤上這事扳炬±粲保” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵恨樟,是天一觀的道長半醉。 經(jīng)常有香客問我,道長劝术,這世上最難降的妖魔是什么缩多? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任呆奕,我火速辦了婚禮,結(jié)果婚禮上衬吆,老公的妹妹穿的比我還像新娘梁钾。我一直安慰自己,他們只是感情好咆槽,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布陈轿。 她就那樣靜靜地躺著圈纺,像睡著了一般秦忿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛾娶,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天灯谣,我揣著相機與錄音,去河邊找鬼蛔琅。 笑死胎许,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的罗售。 我是一名探鬼主播辜窑,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寨躁!你這毒婦竟也來了穆碎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤职恳,失蹤者是張志新(化名)和其女友劉穎所禀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體放钦,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡色徘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了操禀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褂策。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颓屑,靈堂內(nèi)的尸體忽然破棺而出斤寂,到底是詐尸還是另有隱情,我是刑警寧澤邢锯,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布扬蕊,位于F島的核電站,受9級特大地震影響丹擎,放射性物質(zhì)發(fā)生泄漏尾抑。R本人自食惡果不足惜歇父,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望再愈。 院中可真熱鬧榜苫,春花似錦、人聲如沸翎冲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抗悍。三九已至驹饺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缴渊,已是汗流浹背赏壹。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衔沼,地道東北人蝌借。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像指蚁,于是被迫代替她去往敵國和親菩佑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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