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

super的本質

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

#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的結果都是相同的。bash

爲何結果是相同的?super關鍵字在調用方法的時候底層調用流程是怎樣的?函數

咱們經過一段代碼來看一下super底層實現,爲Person類提供run方法,Student類中重寫run方法,方法內部調用[super run];,將Student.m轉化爲c++代碼查看其底層實現。源碼分析

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

上述代碼轉化爲c++代碼post

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")方法名。ui

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

首先咱們找到objc_msgSendSuper函數查看內部結構atom

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

相關文章
相關標籤/搜索