ios -- Objc --Runtime(1) :理解 Objective-C Runtime

C/C++ 屬于靜態的語言;而ObjC屬于動態語言。
什么是靜態語言?
就是在編譯器編譯后就調用函數地址,代碼結構就固定了,無法在運行的時候改變

什么是動態語言?
該語言將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理,比如: 在運行的時候才知道方法的具體實現在什么地方,程序在運行時可以改變其結構,

什么原因造成了oc 是動態語言?
答案:就是運行時

什么又是運行時?
Objective-C Runtime是一個運行時庫, 包含兩大組件——編譯器和Runtime API ,他們是匯編和c語言編寫的, 運行時為c語言提供了面向對象的功能,進而創造了OC 這門語言[ OC運行時本質上就是給OC面向對象編程提供了可能 ] , 也就是說運行時需要提供加載對象, 方法分發/轉發等功能

一: runtime編譯器

作用: 把任何 Objective-C 的代碼編譯為 Runtime 的 C 代碼。這個過程中,會把 Objective-C 的數據結構編譯為 Runtime 的 C 的數據結構。把 Objective-C 的消息傳遞編譯為 Runtime 的 C 函數調用。

下面看一下Objective_c中各種元素在Runtime 中對應的是什么類型【數據結構】?

1: 對象

Runtime 的核心數據結構就是對象,對象在 Runtime 中是一個名為 objc_object 的結構體。也就是說Objective-C 中的任何對象都是用 objc_object 來表示的。

struct objc_object {
private:
       isa_t isa;
public:
       // ...
}

objc_object 中最關鍵的部分是一個 isa_t 類型的 isa 變量。isa_t 類型使用了 Tagged Pointer 技術來減小內存空間的占用。isa 指針主要保存了對象關聯的類的信息。

id 的定義:

typedef struct objc_object *id;

id 是一個指向 objc_object 類型的指針,因此 id 可以代表任何對象。

2: 類

類在runtime 中表示為objc_class 類型

struct objc_class {
       Class isa;
}

其中: 類的 isa 指針指向它的元類。
Class 的定義:

typedef struct objc_class *Class;

也就是說類和他的元類都是同一種類型

3: 元類

元類中保存了這個類的類方法的地址。元類的 isa 指針指向元類對應的類的父類的元類。
提個問題,類方法是通過元類找到, 一般對象方法是根據啥找到? 后續解答

4: 方法

方法在 Runtime 中用 Method 類型來表示,從 Runtime 的源碼可以看到,Method 類型是一個指向 method_t 結構體類型的指針。

typedef struct method_t *Method;
struct method_t {
      SEL name;  // 稱為方法子,也就是封裝了方法名的數據結構objc_selector
      const char *types; //types – 表示該方法參數的類型 比如 
      IMP imp;  // IMP 是一個函數指針,也就是函數實現地址 
      // ...
};
其中: 
      typedef struct objc_selector   *SEL;   
      typedef id (*IMP)(id, SEL, …);

IMP 是一個函數指針,這個函數指針包含一個接收消息的對象id(self 指針), 調用方法的選標 SEL (方法名),以及不定個數的方法參數,并返回一個id,我們可以通過NSObject 類中的methodForSelector:方法就是這樣一個獲取指向方法實現IMP 的指針,
例如:

    void (*setter)(id, SEL, BOOL);
    setter = (void(*)(id, SEL, BOOL))[target       
    methodForSelector:@selector(setFilled:)];

函數調用:

    setter(targetList[i], @selector(setFilled:), YES);

注意,methodForSelector:是Cocoa運行時系統的提供的功能,而不是Objective-C語言本身的功能
一般我們源碼中不使用IMP,SEL ,但是在源碼中使用也是可以的,比如:

     id target = getTheReceiver();

     SEL method = getTheMethod();

     [target performSelector:method]; 
5: 實例變量
typedef struct ivar_t *Ivar;

    struct ivar_t {

        int32_t *offset;

        const char *name;

        const char *type;

        // ...

    };
6 屬性
    typedef struct property_t *objc_property_t;

        struct property_t {

            const char *name;

            const char *attributes;

        };
7:協議
        struct protocol_t : objc_object {

            const char *mangledName;

            struct protocol_list_t *protocols;

            method_list_t *instanceMethods;

            method_list_t *classMethods;

            method_list_t *optionalInstanceMethods;

            method_list_t *optionalClassMethods;

              property_list_t *instanceProperties;

            // ...

        };

8:Objective-C 方法和隱含參數

對于每一個 Objective-C 的方法,例如:

- (void)printCount:(NSInteger)count {

    // ...

}

編譯器都會將其編譯成一個 C 函數,上面的方法會被編譯成:

void foo(id self, SEL _cmd, int count) {

    // ...

}

這個 C 函數的第一個和第二個參數就是隱含參數,在 Objective-C 的方法體中,也是可以直接使用的。編譯為 C 函數后,需要在函數聲明中明確的聲明。

9: 消息傳遞

 [receiver message];

 編譯器會把它編譯成對 C 函數 objc_msgSend 的調用。

 objc_msgSend(receiver, @selector(message));

上述objc_class是簡單描述,下面具體描述一下:

1》 類 具體數據結構類型

struct objc_class {

    struct objc_class * isa; /* 指向元類,元類里面存放這所有的類方法*/

    struct objc_class * super_class;  /*父類*/

    const char *name;                 /*類名字*/

    long version;                   /*版本信息*/

    long info;                        /*類信息*/

    long instance_size;               /*實例大小*/

    struct objc_ivar_list *ivars;     /*實例參數鏈表*/

    struct objc_method_list **methodLists;  /*方法鏈表*/

    struct objc_cache *cache;               /*方法緩存*/

    struct objc_protocol_list *protocols;   /*協議鏈表*/

};//  存放類的結構的對象 isa 也稱為元類對象

二: Runtime API

API 主要有下面的類型:

  • objc_

  • class_

  • object_

  • method_

  • property_

  • protocol_

  • ivar_ ,sel_ ,imp_

1.objc_xxx 系列函數

函數名稱 函數作用

objc_getClass 獲取Class對象

objc_getProtocol 獲取某個協議

objc_getMetaClass 獲取MetaClass對象

objc_copyProtocolList 拷貝在運行時中注冊過的協議列表

objc_allocateClassPair 分配空間,創建類(僅在 創建之后,注冊之前 能夠添加成員變量)

objc_registerClassPair 注冊一個類(注冊后方可使用該類創建對象)

objc_disposeClassPair 注銷某個類

objc_allocateProtocol 開辟空間創建協議

objc_registerProtocol 注冊一個協議

objc_constructInstance 構造一個實例對象(ARC下無效)

objc_destructInstance 析構一個實例對象(ARC下無效)

objc_setAssociatedObject 為實例對象關聯對象

objc_getAssociatedObject 獲取實例對象的關聯對象

objc_removeAssociatedObjects 清空實例對象的所有關聯對象

objc_msgSend 發送ObjC消息

objc_ 系列函數關注于宏觀使用,如類與協議的空間分配,注冊,注銷等操作

2.class_xxx 系列函數

函數名稱 函數作用

class_addIvar 為類添加實例變量

class_addProperty 為類添加屬性

class_addMethod 為類添加方法

class_addProtocol 為類遵循協議

class_replaceMethod 替換類某方法的實現

class_getName 獲取類名

class_isMetaClass 判斷是否為元類

class_getSuperclass 獲取某類的父類

class_setSuperclass 設置某類的父類

class_getProperty 獲取某類的屬性

class_getInstanceVariable 獲取實例變量

class_getClassVariable 獲取類變量

class_getInstanceMethod 獲取實例方法

class_getClassMethod 獲取類方法

class_getMethodImplementation 獲取方法的實現

class_getInstanceSize 獲取類的實例的大小

class_respondsToSelector 判斷類是否實現某方法

class_conformsToProtocol 判斷類是否遵循某協議

class_createInstance 創建類的實例

class_copyIvarList 拷貝類的實例變量列表

class_copyMethodList 拷貝類的方法列表

class_copyProtocolList 拷貝類遵循的協議列表

class_copyPropertyList 拷貝類的屬性列表

class_系列函數關注于類的內部,如實例變量,屬性,方法,協議等相關問題

3.object_xxx 系列函數

函數名稱 函數作用

object_copy 對象copy(ARC無效)

object_dispose 對象釋放(ARC無效)

object_getClassName 獲取對象的類名

object_getClass 獲取對象的Class

object_setClass 設置對象的Class

object_getIvar 獲取對象中實例變量的值

object_setIvar 設置對象中實例變量的值

object_getInstanceVariable 獲取對象中實例變量的值 (ARC中無效,使用object_getIvar)

object_setInstanceVariable 設置對象中實例變量的值 (ARC中無效,使用object_setIvar)

objcet_系列函數關注于對象的角度,如實例變量

4.method_xxx 系列函數

函數名稱 函數作用

method_getName 獲取方法名

method_getImplementation 獲取方法的實現

method_getTypeEncoding 獲取方法的類型編碼

method_getNumberOfArguments 獲取方法的參數個數

method_copyReturnType 拷貝方法的返回類型

method_getReturnType 獲取方法的返回類型

method_copyArgumentType 拷貝方法的參數類型

method_getArgumentType 獲取方法的參數類型

method_getDescription 獲取方法的描述

method_setImplementation 設置方法的實現

method_exchangeImplementations 替換方法的實現

method_系列函數關注于方法內部,如果方法的參數及返回值類型和方法的實現

5.property_xxx 系列函數

函數名稱 函數作用

property_getName 獲取屬性名

property_getAttributes 獲取屬性的特性列表

property_copyAttributeList 拷貝屬性的特性列表

property_copyAttributeValue 拷貝屬性中某特性的值

property_系類函數關注與屬性*內部,如屬性的特性等

6.protocol_xxx 系列函數

函數名稱 函數作用

protocol_conformsToProtocol 判斷一個協議是否遵循另一個協議

protocol_isEqual 判斷兩個協議是否一致

protocol_getName 獲取協議名稱

protocol_copyPropertyList 拷貝協議的屬性列表

protocol_copyProtocolList 拷貝某協議所遵循的協議列表

protocol_copyMethodDescriptionList 拷貝協議的方法列表

protocol_addProtocol 為一個協議遵循另一協議

protocol_addProperty 為協議添加屬性

protocol_getProperty 獲取協議中的某個屬性

protocol_addMethodDescription 為協議添加方法描述

protocol_getMethodDescription 獲取協議中某方法的描述

7.ivar_xxx 系列函數

函數名稱 函數作用

ivar_getName 獲取Ivar名稱

ivar_getTypeEncoding 獲取類型編碼

ivar_getOffset 獲取偏移量

8.sel_xxx 系列函數

函數名稱 函數作用

sel_getName 獲取名稱

sel_getUid 注冊方法

sel_registerName 注冊方法

sel_isEqual 判斷方法是否相等

9.imp_xxx 系列函數

函數名稱 函數作用

imp_implementationWithBlock 通過代碼塊創建IMP

imp_getBlock 獲取函數指針中的代碼塊

imp_removeBlock 移除IMP中的代碼塊

還有一些方便的函數

我們可以通過NSObject的一些方法獲取運行時信息或動態執行一些消息:

isKindOfClass 和 isMemberOfClass檢查對象是否在指定的類繼承體系中;

respondsToSelector 檢查對象能否相應指定的消息;

conformsToProtocol 檢查對象是否實現了指定協議類的方法;

methodForSelector 返回指定方法實現的地址。

performSelector:withObject 執行SEL 所指代的方法。

函數調用舉例:

image.png
image.png
image.png
image.png

三: 代碼編譯實例

因為 Objective-C 的源代碼都會被編譯成 Runtime 代碼來運行,我們一樣可以通過直接編寫 Runtime 代碼的方式來編寫程序。

舉例:

我們有個類叫 ClassA:

@interface ClassA : NSObject

@property (nonatomic, assign) NSInteger count;

  • (void)printCount;

@end

@implementation ClassA

  • (void)printCount {

    NSLog(@"count = %@", @(self.count));

}

@end

然后來執行調用:

ClassA *a = [[ClassA alloc] init];

a.count = 100;

[a printCount];

下面來看看下面代碼轉成 Runtime 怎么寫【自己寫的】?

// 獲取到 ClassA 的 Class 對象

Class ClassA = objc_getClass("ClassA");

// 發送 alloc 和 init 消息來創建和初始化實例對象

id a = objc_msgSend(ClassA, @selector(alloc));

a = objc_msgSend(a, @selector(init));

// 獲取到屬性 count 背后的實例變量

Ivar countIvar = class_getInstanceVariable(ClassA, "_count");

assert(countIvar);

// 通過實例對象首地址 + 實例變量的地址偏移量得到實例變量的指針地址,然后通過 * 取指針值操作符修改指針指向的地址的值。

CFTypeRef aRef = CFBridgingRetain(a);

int *countIvarPtr = (int *)((uint8_t *)aRef + ivar_getOffset(countIvar));

*countIvarPtr = 100;

CFBridgingRelease(aRef);

// 給 a 對象發送 printCount 消息,打印 count 屬性的值

objc_msgSend(a, @selector(printCount));

再看看通過clang編譯后輸出的和我們寫的有什么區別?

執行命令: clang -rewrite-objc main.m

結果如下:

int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

  // 一次性發送消息init和alloc 進而得到ClassA的對象

    ClassA *a = ((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("alloc")), sel_registerName("init"));

   // 執行setCount 方法進行賦值

    ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)a, sel_registerName("setCount:"), (NSInteger)100);

   // 執行printCount 方法

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("printCount"));

}

return 0;

}

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容