Runtime系列之OC對象和方法的本質(zhì)

前言:什么是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'

報錯信息提示的是BMPersonrun沒有實現(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.cppC++文件,打開文件,在最底部找到下面代碼


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指針走位圖

通過以上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)系:對象是類的一個實例,類是元類的一個實例!
所以類方法是存儲在元類里面的,并且是以元類的一個實例方法的形式進行存儲的。

溪浣雙鯉的技術(shù)摸爬滾打之路

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

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