iOS底層原理總結 - 探尋Runtime本質(四)

xx_cc發表於2018-07-11

super的本質

首先來看一道面試題。 下列程式碼中Person繼承自NSObjectStudent繼承自Person,寫出下列程式碼輸出內容。

#import "Student.h"
@implementation Student
- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]);
        NSLog(@"[self superclass] = %@", [self superclass]);
        NSLog(@"----------------");
        NSLog(@"[super class] = %@", [super class]);
        NSLog(@"[super superclass] = %@", [super superclass]);

    }
    return self;
}
@end
複製程式碼

直接來看一下列印內容

Runtime-super[6601:1536402] [self class] = Student
Runtime-super[6601:1536402] [self superclass] = Person
Runtime-super[6601:1536402] ----------------
Runtime-super[6601:1536402] [super class] = Student
Runtime-super[6601:1536402] [super superclass] = Person
複製程式碼

上述程式碼中可以發現無論是self還是super呼叫classsuperclass的結果都是相同的。

為什麼結果是相同的?super關鍵字在呼叫方法的時候底層呼叫流程是怎樣的?

我們通過一段程式碼來看一下super底層實現,為Person類提供run方法,Student類中重寫run方法,方法內部呼叫[super run];,將Student.m轉化為c++程式碼檢視其底層實現。

- (void) run
{
    [super run];
    NSLog(@"Student...");
}
複製程式碼

上述程式碼轉化為c++程式碼

static void _I_Student_run(Student * self, SEL _cmd) {
    
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
    
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_Student_e677aa_mi_0);
}
複製程式碼

通過上述原始碼可以發現,[super run];轉化為底層原始碼內部其實呼叫的是objc_msgSendSuper函式。

objc_msgSendSuper函式內傳遞了兩個引數。__rw_objc_super結構體和sel_registerName("run")方法名。

__rw_objc_super結構體內傳入的引數是selfclass_getSuperclass(objc_getClass("Student"))也就是Student的父類Person

首先我們找到objc_msgSendSuper函式檢視內部結構

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_msgSendSuper中傳入的結構體是objc_super,我們來到objc_super內部檢視其內部結構。 我們通過原始碼查詢objc_super結構體檢視其內部結構。

// 精簡後的objc_super結構體
struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 訊息接受者
    __unsafe_unretained _Nonnull Class super_class; // 訊息接受者的父類
    /* super_class is the first class to search */ 
    // 父類是第一個開始查詢的類
};
複製程式碼

objc_super結構體中可以發現receiver訊息接受者仍然為selfsuperclass僅僅是用來告知訊息查詢從哪一個類開始。從父類的類物件開始去查詢。

我們通過一張圖看一下其中的區別。

self/super呼叫方法的區別

從上圖中我們知道 super呼叫方法的訊息接受者receiver仍然是self,只是從父類的類物件開始去查詢方法。

那麼此時重新回到面試題,我們知道class的底層實現如下面程式碼所示

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
複製程式碼

class內部實現是根據訊息接受者返回其對應的類物件,最終會找到基類的方法列表中,而selfsuper的區別僅僅是self從本類類物件開始查詢方法,super從父類類物件開始查詢方法,因此最終得到的結果都是相同的。

另外我們在回到run方法內部,很明顯可以發現,如果super不是從父類開始查詢方法,從本類查詢方法的話,就呼叫方法本身造成迴圈呼叫方法而crash。

同理superclass底層實現同class類似,其底層實現程式碼如下入所示

+ (Class)superclass {
    return self->superclass;
}

- (Class)superclass {
    return [self class]->superclass;
}
複製程式碼

因此得到的結果也是相同的。

objc_msgSendSuper2函式

上述OC程式碼轉化為c++程式碼並不能說明super底層呼叫函式就一定objc_msgSendSuper

其實super底層真正呼叫的函式時objc_msgSendSuper2函式我們可以通過檢視super呼叫方法轉化為彙編程式碼來驗證這一說法

- (void)viewDidLoad {
    [super viewDidLoad];
}
複製程式碼

通過斷點檢視其彙編呼叫棧

objc_msgSendSuper2函式

上圖中可以發現super底層其實呼叫的是objc_msgSendSuper2函式,我們來到原始碼中查詢一下objc_msgSendSuper2函式的底層實現,我們可以在彙編檔案中找到其相關底層實現。

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START

ldp	x0, x16, [x0]		// x0 = real receiver, x16 = class
ldr	x16, [x16, #SUPERCLASS]	// x16 = class->superclass
CacheLookup NORMAL

END_ENTRY _objc_msgSendSuper2
複製程式碼

通過上面彙編程式碼我們可以發現,其實底層是在函式內部呼叫的class->superclass獲取父類,並不是我們上面分析的直接傳入的就是父類物件。

其實_objc_msgSendSuper2內傳入的結構體為objc_super2

struct objc_super2 {
    id receiver;
    Class current_class;
};
複製程式碼

我們可以發現objc_super2中除了訊息接受者receiver,另一個成員變數current_class也就是當前類物件。

與我們上面分析的不同_objc_msgSendSuper2函式內其實傳入的是當前類物件,然後在函式內部獲取當前類物件的父類,並且從父類開始查詢方法。

我們也可以通過程式碼驗證上述結構體內成員變數究竟是當前類物件還是父類物件。下文中我們會通過另外一道面試題驗證。

isKindOfClass 與 isMemberOfClass

首先看一下isKindOfClass isKindOfClass物件方法底層實現

- (BOOL)isMemberOfClass:(Class)cls {
   // 直接獲取例項類物件並判斷是否等於傳入的類物件
    return [self class] == cls;
}

- (BOOL)isKindOfClass:(Class)cls {
   // 向上查詢,如果找到父類物件等於傳入的類物件則返回YES
   // 直到基類還不相等則返回NO
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
複製程式碼

isKindOfClass isKindOfClass類方法底層實現

// 判斷元類物件是否等於傳入的元類元類物件
// 此時self是類物件 object_getClass((id)self)就是元類
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

// 向上查詢,判斷元類物件是否等於傳入的元類物件
// 如果找到基類還不相等則返回NO
// 注意:這裡會找到基類
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
複製程式碼

通過上述原始碼分析我們可以知道。 isMemberOfClass 判斷左邊是否剛好等於右邊型別。 isKindOfClass 判斷左邊或者左邊型別的父類是否剛好等於右邊型別。 注意:類方法內部是獲取其元類物件進行比較

我們檢視以下程式碼

NSLog(@"%d",[Person isKindOfClass: [Person class]]);
NSLog(@"%d",[Person isKindOfClass: object_getClass([Person class])]);
NSLog(@"%d",[Person isKindOfClass: [NSObject class]]);

// 輸出內容
Runtime-super[46993:5195901] 0
Runtime-super[46993:5195901] 1
Runtime-super[46993:5195901] 1
複製程式碼

分析上述輸出內容: 第一個 0:上面提到過類方法是獲取self的元類物件與傳入的引數進行比較,但是第一行我們傳入的是類物件,因此返回NO。

第二個 1:同上,此時我們傳入Person元類物件,此時返回YES。驗證上述說法

第三個 1:我們發現此時傳入的是NSObject類物件並不是元類物件,但是返回的值卻是YES。 原因是基元類的superclass指標是指向基類物件的。如下圖13號線

isa、superclass指向圖

那麼Person元類通過superclass指標一直找到基元類,還是不相等,此時再次通過superclass指標來到基類,那麼此時發現相等就會返回YES了。

複習

通過一道面試題對之前學習的知識進行復習。 問:以下程式碼是否可以執行成功,如果可以,列印結果是什麼。

// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end

// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
    NSLog(@"test print name is : %@", self.name);
}
@end

// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
    
    Person *person = [[Person alloc] init];
    [person test];
}
複製程式碼

這道面試題確實很無厘頭的一道題,日常工作中沒有人這樣寫程式碼,但是需要解答這道題需要很完備的底層知識,我們通過這道題來複習一下,首先看一下列印結果。

Runtime面試題[15842:2579705] test print name is : <ViewController: 0x7f95514077a0>
Runtime面試題[15842:2579705] test print name is : (null)
複製程式碼

通過上述列印結果我們可以看出,是可以正常執行並列印的,說明obj可以正常呼叫test方法,但是我們發現列印self.name的內容卻是<ViewController: 0x7f95514077a0>。下面person例項呼叫test不做過多解釋了,主要用來和上面方法呼叫做對比。

為什麼會是這樣的結果呢?首先通過一張圖看一下兩種呼叫方法的記憶體資訊。

兩種呼叫方法的記憶體資訊

通過上圖我們可以發現兩種方法呼叫方式很相近。那麼obj為什麼可以正常呼叫方法?

obj為什麼可以正常呼叫方法

首先通過之前的學習我們知道,person呼叫方法時首先通過isa指標找到類物件進而查詢方法並進行呼叫。

person例項物件內實際上是取最前面8個位元組空間也就是isa並通過計算得出類物件地址。

而通過上圖我們可以發現,obj在呼叫test方法時,也會通過其記憶體地址找到cls,而cls中取出最前面8個位元組空間其內部儲存的剛好是Person類物件地址。因此obj是可以正常呼叫方法的。

為什麼self.name列印內容為ViewController物件

問題出在[super viewDidLoad];這段程式碼中,通過上述對super本質的分析我們知道,super內部呼叫objc_msgSendSuper2函式。

我們知道objc_msgSendSuper2函式內部會傳入兩個引數,objc_super2結構體和SEL,並且objc_super2結構體內有兩個成員變數訊息接受者和其父類。

struct objc_super2 {
    id receiver; // 訊息接受者
    Class current_class; // 當前類
};
};
複製程式碼

通過以上分析我們可以得知[super viewDidLoad];內部objc_super2結構體記憶體儲如下所示

struct objc_super = {
    self,
    [ViewController Class]
};
複製程式碼

那麼objc_msgSendSuper2函式呼叫之前,會先建立區域性變數objc_super2結構體用於為objc_msgSendSuper2函式傳遞的引數。

區域性變數由高地址向低地址分配在棧空間

我們知道區域性變數是儲存在棧空間內的,並且是由高地址向低地址有序儲存。 我們通過一段程式碼驗證一下。

long long a = 1;
long long b = 2;
long long c = 3;
NSLog(@"%p %p %p", &a,&b,&c);
// 列印內容
0x7ffee9774958 0x7ffee9774950 0x7ffee9774948
複製程式碼

通過上述程式碼列印內容,我們可以驗證區域性變數在棧空間內是由高地址向低地址連續儲存的。

那麼我們回到面試題中,通過上述分析我們知道,此時程式碼中包含區域性變數以此為objc_super2 結構體clsobj。通過一張圖展示一下這些區域性變數儲存結構。

區域性變數儲存結構

上面我們知道當person例項物件呼叫方法的時候,會取例項變數前8個位元組空間也就是isa來找到類物件地址。那麼當訪問例項變數的時候,就跳過isa的8個位元組空間往下面去找例項變數。

那麼當obj在呼叫test方法的時候同樣找到cls中取出前8個位元組,也就是Person類物件的記憶體地址,那麼當訪問例項變數_name的時候,會繼續向高地址記憶體空間查詢,此時就會找到objc_super結構體,從中取出8個位元組空間也就是self,因此此時訪問到的self.name就是ViewController物件

當訪問成員變數_name的時候,test函式中的self也就是方法呼叫者其實是obj,那麼self.name就是通過obj去找_name,跳過cls的8個指標,在取8個指標此時自然獲取到ViewController物件

因此上述程式碼中cls就相當於isaisa下面的8個位元組空間就相當於_name成員變數。因此成員變數_name的訪問到的值就是cls地址後向高地址位取8個位元組地址空間儲存的值。

為了驗證上述說法,我們做一個實驗,在cls後高地址中新增一個string,那麼此時cls下面的高地址位就是string。以下示例程式碼

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *string = @"string";
    
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
    
    Person *person = [[Person alloc] init];
    [person test];
}
複製程式碼

此時的區域性變數記憶體結構如下圖所示

區域性變數記憶體結構

此時在訪問_name成員變數的時候,越過cls記憶體往高地址找就會來到string,此時拿到的成員變數就是string了。 我們來看一下列印內容

Runtime面試題[16887:2829028] test print name is : string
Runtime面試題[16887:2829028] test print name is : (null)
複製程式碼

再通過一段程式碼使用int資料進行試驗

- (void)viewDidLoad {
    [super viewDidLoad];

    int a = 3;
    
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
    
    Person *person = [[Person alloc] init];
    [person test];
}
// 程式crash,壞地址訪問
複製程式碼

我們發現程式因為壞地址訪問而crash,此時區域性變數記憶體結構如下圖所示

區域性變數記憶體結構

當需要訪問_name成員變數的時候,會在cls後高地址為查詢8位的位元組空間,而我們知道int佔4位位元組,那麼此時8位的記憶體空間同時佔據int資料及objc_super結構體內,因此就會造成壞地址訪問而crash。

我們新增新的成員變數進行訪問

// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *nickName;
- (void)test;
@end
------------
// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
    NSLog(@"test print name is : %@", self.nickName);
}
@end
--------
//  ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];

    NSObject *obj1 = [[NSObject alloc] init];
    
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
    
    Person *person = [[Person alloc] init];
    [person test];
}
複製程式碼

我們看一下列印內容

// 列印內容
// Runtime面試題[17272:2914887] test print name is : <ViewController: 0x7ffc6010af50>
// Runtime面試題[17272:2914887] test print name is : (null)
複製程式碼

可以發現此時列印的仍然是ViewController物件,我們先來看一下其區域性變數記憶體結構

區域性變數記憶體結構

首先通過obj找到clscls找到類物件進行方法呼叫,此時在訪問nickName時,obj查詢成員變數,首先跳過8個位元組的cls,之後跳過name所佔的8個位元組空間,最終再取8個位元組空間取出其中的值作為成員變數的值,那麼此時也就是self了。

總結:這道面試題雖然很無厘頭,讓人感覺無從下手但是考察的內容非常多。 1. super的底層本質為呼叫objc_msgSendSuper2函式,傳入objc_super2結構體,結構體內部儲存訊息接受者和當前類,用來告知系統方法查詢從父類開始。

2. 區域性變數分配在棧空間,並且從高地址向低地址連續分配。先建立的區域性變數分配在高地址,後續建立的區域性變數連續分配在較低地址。

3. 方法呼叫的訊息機制,通過isa指標找到類物件進行訊息傳送。

4. 指標儲存的是例項變數的首位元組地址,上述例子中person指標儲存的其實就是例項變數內部的isa指標的地址。

5. 訪問成員變數的本質,找到成員變數的地址,按照成員變數所佔的位元組數,取出地址中儲存的成員變數的值。

驗證objc_msgSendSuper2內傳入的結構體引數

我們使用以下程式碼來驗證上文中遺留的問題

- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
}
複製程式碼

上述程式碼的區域性變數記憶體結構我們之前已經分析過了,真正的記憶體結構應該如下圖所示

區域性變數記憶體結構

通過上面對面試題的分析,我們現在想要驗證objc_msgSendSuper2函式內傳入的結構體引數,只需要拿到cls的地址,然後向後移8個地址就可以獲取到objc_super結構體內的self,在向後移8個地址就是current_class的記憶體地址。通過列印current_class的內容,就可以知道傳入objc_msgSendSuper2函式內部的是當前類物件還是父類物件了。

我們來證明他是UIViewController 還是ViewController即可

結構體內傳入當前類

通過上圖可以發現,最終列印的內容確實為當前類物件。 因此objc_msgSendSuper2函式內部其實傳入的是當前類物件,並且在函式內部獲取其父類,告知系統從父類方法開始查詢的。

Runtime API

首先我們通過來看一段程式碼,後續Runtime API的使用均基於此程式碼。

// Person類繼承自NSObject,包含run方法
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)run;
@end

#import "Person.h"
@implementation Person
- (void)run
{
    NSLog(@"%s",__func__);
}
@end

// Car類繼承自NSObejct,包含run方法
#import "Car.h"
@implementation Car
- (void)run
{
    NSLog(@"%s",__func__);
}
@end
複製程式碼

類相關API

1. 動態建立一個類(引數:父類,類名,額外的記憶體空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

2. 註冊一個類(要在類註冊之前新增成員變數)
void objc_registerClassPair(Class cls) 

3. 銷燬一個類
void objc_disposeClassPair(Class cls)

示例:
void run(id self , SEL _cmd) {
    NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 建立類 superclass:繼承自哪個類 name:類名 size_t:格外的大小,建立類是否需要擴充空間
        // 返回一個類物件
        Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);
        
        // 新增成員變數 
        // cls:新增成員變數的類 name:成員變數的名字 size:佔據多少位元組 alignment:記憶體對齊,最好寫1 types:型別,int型別就是@encode(int) 也就是i
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_height", 4, 1, @encode(float));
        
        // 新增方法
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        
        // 註冊類
        objc_registerClassPair(newClass);
        
        // 建立例項物件
        id student = [[newClass alloc] init];
    
        // 通過KVC訪問
        [student setValue:@10 forKey:@"_age"];
        [student setValue:@180.5 forKey:@"_height"];
        
        // 獲取成員變數
        NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);
        
        // 獲取類的佔用空間
        NSLog(@"類物件佔用空間%zd", class_getInstanceSize(newClass));
        
        // 呼叫動態新增的方法
        [student run];
        
    }
    return 0;
}

// 列印內容
// Runtime應用[25605:4723961] _age = 10 , _height = 180.5
// Runtime應用[25605:4723961] 類物件佔用空間16
// Runtime應用[25605:4723961] <Student: 0x10072e420> - run

注意
類一旦註冊完畢,就相當於類物件和元類物件裡面的結構就已經建立好了。
因此必須在註冊類之前,新增成員變數。方法可以在註冊之後再新增,因為方法是可以動態新增的。
建立的類如果不需要使用了 ,需要釋放類。
複製程式碼
4. 獲取isa指向的Class,如果將類物件傳入獲取的就是元類物件,如果是例項物件則為類物件
Class object_getClass(id obj)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"%p,%p,%p",object_getClass(person), [Person class],
              object_getClass([Person class]));
    }
    return 0;
}
// 列印內容
Runtime應用[21115:3807804] 0x100001298,0x100001298,0x100001270
複製程式碼
5. 設定isa指向的Class,可以動態的修改型別。例如修改了person物件的型別,也就是說修改了person物件的isa指標的指向,中途讓物件去呼叫其他類的同名方法。
Class object_setClass(id obj, Class cls)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person run];
        
        object_setClass(person, [Car class]);
        [person run];
    }
    return 0;
}
// 列印內容
Runtime應用[21147:3815155] -[Person run]
Runtime應用[21147:3815155] -[Car run]
最終其實呼叫了car的run方法
複製程式碼
6. 用於判斷一個OC物件是否為Class
BOOL object_isClass(id obj)

// 判斷OC物件是例項物件還是類物件
NSLog(@"%d",object_isClass(person)); // 0
NSLog(@"%d",object_isClass([person class])); // 1
NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1 
// 元類物件也是特殊的類物件
複製程式碼
7. 判斷一個Class是否為元類
BOOL class_isMetaClass(Class cls)
8. 獲取類物件父類
Class class_getSuperclass(Class cls)
複製程式碼

成員變數相關API

1. 獲取一個例項變數資訊,描述資訊變數的名字,佔用多少位元組等
Ivar class_getInstanceVariable(Class cls, const char *name)

2. 拷貝例項變數列表(最後需要呼叫free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

3. 設定和獲取成員變數的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

4. 動態新增成員變數(已經註冊的類是不能動態新增成員變數的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

5. 獲取成員變數的相關資訊,傳入成員變數資訊,返回C語言字串
const char *ivar_getName(Ivar v)
6. 獲取成員變數的編碼,types
const char *ivar_getTypeEncoding(Ivar v)

示例:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 獲取成員變數的資訊
        Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
        // 獲取成員變數的名字和編碼
        NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));
        
        Person *person = [[Person alloc] init];
        // 設定和獲取成員變數的值
        object_setIvar(person, nameIvar, @"xx_cc");
        // 獲取成員變數的值
        object_getIvar(person, nameIvar);
        NSLog(@"%@", object_getIvar(person, nameIvar));
        NSLog(@"%@", person.name);
        
        // 拷貝例項變數列表
        unsigned int count ;
        Ivar *ivars = class_copyIvarList([Person class], &count);

        for (int i = 0; i < count; i ++) {
            // 取出成員變數
            Ivar ivar = ivars[i];
            NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
        }
        
        free(ivars);

    }
    return 0;
}

// 列印內容
// Runtime應用[25783:4778679] _name, @"NSString"
// Runtime應用[25783:4778679] xx_cc
// Runtime應用[25783:4778679] xx_cc
// Runtime應用[25783:4778679] _name, @"NSString"
複製程式碼

屬性相關AIP

1. 獲取一個屬性
objc_property_t class_getProperty(Class cls, const char *name)

2. 拷貝屬性列表(最後需要呼叫free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

3. 動態新增屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

4. 動態替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

5. 獲取屬性的一些資訊
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
複製程式碼

方法相關API

1. 獲得一個例項方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

2. 方法實現相關操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

3. 拷貝方法列表(最後需要呼叫free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

4. 動態新增方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

5. 動態替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

6. 獲取方法的相關資訊(帶有copy的需要呼叫free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

7. 選擇器相關
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

8. 用block作為方法實現
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
複製程式碼

Runtime的應用

關於Runtime的應用請參考iOS-RunTime應用

底層原理相關文章:

底層原理文章專欄


文中如果有不對的地方歡迎指出。我是xx_cc,一隻長大很久但還沒有二夠的傢伙。需要視訊一起探討學習的coder可以加我Q:2336684744

相關文章