iOS全解3:Runtime

面試系列:


一、概念:什么是Runtime达布?平時(shí)項(xiàng)目中有用過么?

  • 簡(jiǎn)單來說:是一套底層的C語言API斩熊。
  • 具體應(yīng)用:能動(dòng)態(tài) “ 增/刪/改/查 ” 一個(gè)類的 成員變量 和 方法.
    1往枣、能動(dòng)態(tài) 創(chuàng)建 一個(gè)類,一個(gè)成員變量(屬性)粉渠,一個(gè)方法
    2分冈、能動(dòng)態(tài) 修改 一個(gè)類,一個(gè)成員變量(屬性)霸株,一個(gè)方法
    3雕沉、能動(dòng)態(tài) 刪除 一個(gè)類,一個(gè)成員變量(屬性)去件,一個(gè)方法

OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語言坡椒,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行。通過此篇文章了解多態(tài):iOS面試全解1:基礎(chǔ)/內(nèi)存管理/Block/GCD

OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的尤溜,Runtime是一套C語言的API倔叼,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù),平時(shí)編寫的OC代碼宫莱,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用:

比如類轉(zhuǎn)成了runtime庫里面的結(jié)構(gòu)體等數(shù)據(jù)類型丈攒,方法轉(zhuǎn)成了runtime庫里面的C語言函數(shù),平時(shí)調(diào)方法都是轉(zhuǎn)成了objc_msgSend函數(shù) (就是OC的消息發(fā)送機(jī)制)授霸。在OC中巡验,使用對(duì)象進(jìn)行方法調(diào)用是一個(gè)消息發(fā)送的過程(Objective-C采用“動(dòng)態(tài)綁定機(jī)制”,所以所要調(diào)用的方法直到運(yùn)行期才能確定)碘耳。

項(xiàng)目中的使用:

1显设、關(guān)聯(lián)對(duì)象: 給分類添加屬性,AssociatedObject(提高性能辛辨,避免不使用的 繼承類屬性 初始化)

2捕捂、遍歷類的所有成員變量:修改textfield的占位文字顏色瑟枫、字典轉(zhuǎn)模型Model、自動(dòng)歸檔解檔

3指攒、交換方法實(shí)現(xiàn):(交換系統(tǒng)的方法力奋,埋點(diǎn)、防逆向hook)

4幽七、利用消息轉(zhuǎn)發(fā)機(jī)制:解決 「方法找不到」的異常問題
......


什么是 isa 景殷?

isa 指針(NONPOINTER_ISA)

isa:是一個(gè)指向?qū)ο笏鶎貱lass類型的指針。(nonPointer_isa)
對(duì)象的isa指針澡屡,用來表明對(duì)象所屬的類類型和一些附加信息猿挚。

如果isa指針僅表示類型的話,對(duì)內(nèi)存顯然也是一個(gè)極大的浪費(fèi)驶鹉。于是绩蜻,就像tagged pointer一樣,對(duì)于isa指針室埋,蘋果同樣進(jìn)行了優(yōu)化办绝。isa指針表示的內(nèi)容變得更為豐富,除了表明對(duì)象屬于哪個(gè)類之外姚淆,還附加了 「引用計(jì)數(shù)」extra_rc孕蝉,是否有被weak引用標(biāo)志位weakly_referenced,是否有附加對(duì)象標(biāo)志位has_assoc等信息腌逢。(rc:reference counter 引用計(jì)數(shù))

1降淮、實(shí)例isa 指向類對(duì)象,類對(duì)象isa 指向元類對(duì)象(指向上)
2搏讶、在 arm64之前佳鳖,isa指針就直接存儲(chǔ)著 類對(duì)象/元類對(duì)象 的地址值;
在 arm64時(shí)媒惕,isa進(jìn)行了優(yōu)化系吩,采取 共用體(union)的結(jié)構(gòu),共有64位妒蔚,分開來存儲(chǔ)了很多東西穿挨,其中有33位是存儲(chǔ) 地址值,使用 &Mask 取出 地址值面睛。


objc源碼
//------- NSObject(實(shí)質(zhì)) ------- 
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

//------- objc_class(繼承)------- 
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable         以前的緩存指針和虛函數(shù)表
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags   class_rw_t *加上自定義rr/alloc標(biāo)志
    //絮蒿。尊搬。叁鉴。
}

//------- isa指針(指向)------- 
struct objc_object {
private:
    isa_t isa;

public:
    Class ISA();    // ISA() assumes this is NOT a tagged pointer object    假設(shè)這不是一個(gè)標(biāo)記的指針對(duì)象
    Class getIsa(); // getIsa() allows this to be a tagged pointer object   允許這是一個(gè)標(biāo)記的指針對(duì)象
    //。佛寿。幌墓。
}

//------- 聯(lián)合體(定義)------- 
union isa_t 
{
    isa_t() { }  &nbsp;&nbsp;&nbsp;&nbsp;       //構(gòu)造函數(shù)1
    isa_t(uintptr_t value) : bits(value) { }    //構(gòu)造函數(shù)2
    Class cls;          //成員1(占據(jù)64位內(nèi)存空間)
    uintptr_t bits;     //成員2(占據(jù)64位內(nèi)存空間)

#if SUPPORT_PACKED_ISA
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {                //成員3(占據(jù)64位內(nèi)存空間:從低位到高位依次是nonpointer到extra_rc但壮。成員后面的:表明了該成員占用幾個(gè)bit。)
        uintptr_t nonpointer        : 1;  //(低位)注意:標(biāo)志位常侣,表明isa_t *是否是一個(gè)真正的指針@!胳施!
        uintptr_t has_assoc         : 1;  // 關(guān)聯(lián)對(duì)象
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;  //弱引用
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;  //引用計(jì)數(shù) 相關(guān)成員1
        uintptr_t extra_rc          : 19; //引用計(jì)數(shù) 相關(guān)成員2 (用19位來 記錄對(duì)象的引用次數(shù))
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}


 struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
        //向該類所繼承的父類對(duì)象
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        //成員變量列表
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        //方法列表
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;//方法列表
        //用于緩存調(diào)用過的方法
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        //協(xié)議鏈表用來存儲(chǔ)聲明遵守的正式協(xié)議
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
    }


Runtime 實(shí)質(zhì)是:方法和消息動(dòng)態(tài)綁定的過程溯祸。

當(dāng)對(duì)象收到消息時(shí),消息函數(shù)首先根據(jù)該對(duì)象的isa指針找到該對(duì)象所對(duì)應(yīng)的類的方法表舞肆,并從表中尋找 該消息對(duì)應(yīng)的方法選標(biāo)焦辅。如果找不到,objc_msgSend 將繼續(xù)從父類中尋找椿胯,直到 NSObject 類筷登。一 旦找到了方法選標(biāo), objc_msgSend 則以消息接收者對(duì)象為參數(shù)調(diào)用哩盲,調(diào)用該選標(biāo)對(duì)應(yīng)的方法實(shí)現(xiàn)前方。

這就是在運(yùn)行時(shí)系統(tǒng)中選擇方法實(shí)現(xiàn)的方式。在面向?qū)ο缶幊讨辛停话惴Q作方法和消息動(dòng)態(tài)綁定的過程惠险。
為了加快消息的處理過程,運(yùn)行時(shí)系統(tǒng)通常會(huì)將使用過的方法選標(biāo)和方法實(shí)現(xiàn)的地址放入緩存中抒线。每個(gè)類都有一個(gè)獨(dú)立的緩存莺匠,同時(shí)包括繼承的方法和在該類中定義的方法。消息函數(shù)會(huì)首先檢查消息接收者對(duì)象對(duì)應(yīng)的類的緩存(理論上十兢,如果一個(gè)方法被使用過一次趣竣,那么它很可能被再次使用)。如果在緩存中已經(jīng)有了需要的方法選標(biāo)旱物,則消息僅僅比函數(shù)調(diào)用慢一點(diǎn)點(diǎn)遥缕。如果程序運(yùn)行了足夠長(zhǎng)的時(shí)間,幾乎每個(gè)消息都能在緩存中找到方法實(shí)現(xiàn)宵呛。程序運(yùn)行時(shí)单匣,緩存也將隨著新的消息的增加而增加。

既然類也是一個(gè)對(duì)象宝穗,那么它一定是其他類的實(shí)例户秤,那個(gè)類就是元類 (metaclass)元類是類(對(duì)象)的描述逮矛,就像類是普通實(shí)例的描述一樣鸡号。特別的是,元類的方法列表是類方法(類響應(yīng)的選擇器)须鼎。當(dāng)你發(fā)送消息給一個(gè)類(元類的實(shí)例)鲸伴,objc_msgSend()會(huì)查找元類(已及它的父類)的方法列表并確定調(diào)用的方法府蔗。類方法是由類代表的元類所描述,如同實(shí)例方法是由實(shí)例對(duì)象對(duì)應(yīng)的類(對(duì)象)來描述的汞窗。

注意:當(dāng)一個(gè)消息發(fā)送給任何一個(gè)對(duì)象時(shí)姓赤,將會(huì)由對(duì)象的 isa 指針開始查找方法,接著沿著父類鏈向上去查找仲吏。

  • 實(shí)例方法:被類定義不铆,
  • 類方法:被元類定義。

以下案例說明類的關(guān)系:
MyClass *myClass = [[MyClass alloc] init];
整理下相互間的關(guān)系:
    ?   myClass 是實(shí)例對(duì)象
    ?   MyClass 是類對(duì)象
    ?   MyClass 的元類就是 NSObject 
    ?   NSObject 就是 Root class (class)
    ?   NSObject 的 superclass 為 nil
    ?   NSObject 的元類就是它自己
    ?   NSObject 的元類的 superclass 就是 NSObject

你覺得元類是什么樣的裹唆?到元類還會(huì)繼續(xù)向下嗎狂男?不,元類其實(shí)是根類 (root class) 的元類的實(shí)例品腹;這個(gè)根類的元類岖食,實(shí)際上就是它自己。isa 鏈在這里結(jié)束舞吭,形成一個(gè)環(huán)形(實(shí)例->類->元類->父元類->根元類->根元類自己)泡垃。

objc_method:Method
objc_selector:SEL
objc_category:Category
objc_cache: 類緩存
objc_class: 類對(duì)象
objc_object:實(shí)例

Root class:根類 object(人的認(rèn)知)
meteClass:元類-人類
Superclass:父類 - 小明父親、小華父親......
Subclass: ??子類- 小明羡鸥、小華

找父類

子類的實(shí)例 -> 子類 -> 父類 -> 根類 -> nil
父類的實(shí)例 -> 父類 -> 根類 -> nil
根類的實(shí)例 -> 根類 -> nil
子類的元類 -> 父類的元類 -> 根類的元類 -> 根類的元類

找元類

子類 -> 子類的元類
父類 -> 父類的元類
根類 -> 根類的元類

終點(diǎn)是:根元類(NSObject


isa的關(guān)系圖

isa的關(guān)系圖.png
isa結(jié)構(gòu)關(guān)系.png


二蔑穴、基本源碼解析

1、基本符號(hào)與標(biāo)識(shí):

SEL:方法編號(hào)惧浴,類成員方法的指針存和,但不同于C語言中的函數(shù)指針,函數(shù)指針直接保存了方法的地址衷旅,但SEL只是方法編號(hào)捐腿。定義成 char *
IMP:函數(shù)指針,保存了方法的地址

2柿顶、常用的頭文件

#import <objc/ >//包含對(duì):類茄袖、成員變量、屬性嘁锯、方法的操作
#import <objc/message.h> //包含消息機(jī)制

3.常用方法
class_copyIvarList()     返回一個(gè)指向類的成員變量數(shù)組的指針
class_copyPropertyList()返回一個(gè)指向類的屬性數(shù)組的指針
注意:根據(jù)Apple官方runtime.h文檔所示宪祥,上面兩個(gè)方法返回的指針,在使用完畢之后必須free()家乘。
-------------------------------------
ivar_getName()    獲取成員變量名--> C類型的字符串
property_getName()獲取屬性名   --> C類型的字符串
-------------------------------------
typedef struct objc_method *Method;
class_getInstanceMethod()返回一個(gè)實(shí)例方法
class_getClassMethod()    返回一個(gè)類方法
method_exchangeImplementations()交換兩個(gè)方法的實(shí)現(xiàn)
-------------------------------------
.......很多方法蝗羊,在后面敘述!仁锯!
一種常見的辦法是通過runtime.h中 
objc_getAssociatedObject / 
objc_setAssociatedObject 來訪問和生成關(guān)聯(lián)對(duì)象耀找。
這兩個(gè)方法可以讓一個(gè)對(duì)象和另一個(gè)對(duì)象關(guān)聯(lián),就是說一個(gè)對(duì)象可以保持對(duì)另一個(gè)對(duì)象的引用,并獲取那個(gè)對(duì)象。
4.Runtime 基礎(chǔ)函數(shù)
 method相關(guān)的函數(shù)也不是太多,下邊簡(jiǎn)單羅列說明一下
 
 Method class_getInstanceMethod(Class cls, SEL name)             //(-)獲取類中的 實(shí)例方法(減號(hào)方法):-(void)test;
 Method class_getClassMethod(Class cls, SEL name)                //(+)獲取類中的 類方法  (加號(hào)方法):+(void)test;
 IMP    class_getMethodImplementation(Class cls, SEL name)       //獲取類中的方法實(shí)現(xiàn)
 IMP    class_getMethodImplementation_stret(Class cls, SEL name) //獲取類中的方法的實(shí)現(xiàn)插龄,該方法的返回值類型為struct
 SEL  method_getName(Method m)                                    //獲取Method中的SEL(方法的名稱)
 IMP  method_getImplementation(Method m)                         //獲取Method中的IMP(方法的指針)
 IMP  method_setImplementation(Method m, IMP imp)                //設(shè)置Method的IMP  (方法的指針)

// 消息發(fā)送:

  • objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

  • 方法執(zhí)行的邏輯是:先獲取對(duì)象對(duì)應(yīng)類的信息刽沾,再獲取方法的緩存,根據(jù)方法的 selector 查找函數(shù)指針灼芭, 經(jīng)過異常錯(cuò)誤處理后,最后跳到對(duì)應(yīng)函數(shù)的實(shí)現(xiàn)。

    對(duì)象對(duì)應(yīng)類效诅、方法的緩存、函數(shù)指針趟济、異常處理乱投、函數(shù)的實(shí)現(xiàn)。
    object -> class -> IMP -> exception -> method

//例5:方法的調(diào)用本質(zhì)是:消息機(jī)制顷编,objc_msgSend(消息接收者, 消息名稱);

    [TZPerson addRun];
    //同上
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TZPerson"), sel_registerName("addRun"));
    //同上
    objc_msgSend(objc_getClass("TZPerson"), sel_registerName("addRun"));
    //同上
    objc_msgSend(objc_getClass("TZPerson"), @selector(addRun));


三戚炫、方法的調(diào)用本質(zhì):消息轉(zhuǎn)發(fā)機(jī)制

以下是方法調(diào)用的過程:

0、通過isa找到類:先去緩存表中找媳纬,如果沒有双肤,方法表中尋找
1、如果沒有钮惠,就繼續(xù)找父類方法緩存表茅糜,沒有繼續(xù)找父父類方法表,直到根類素挽。
3蔑赘、 是否有方法:有就去調(diào)用、沒有去新增
4预明、是否有新增方法:有新增然后去調(diào)用缩赛、沒有新增去轉(zhuǎn)發(fā)
5、是否有消息轉(zhuǎn)發(fā):有取調(diào)用撰糠、沒有就崩潰

// objc_msgSend:(類/實(shí)例, 方法名稱) ;
// 本質(zhì)是消息機(jī)制:(消息接收者, 消息名稱)

在OC中方法調(diào)用的是通過Runtime實(shí)現(xiàn)的峦筒,Runtime進(jìn)行方法調(diào)用本質(zhì)上是發(fā)送消息,通過objc_msgSend()函數(shù)進(jìn)行消息發(fā)送窗慎。
給實(shí)例發(fā)送一個(gè)消息:

  1. 判斷receiver == nil
  2. 通過isa找到類
  3. 從類的緩存里查找IMP物喷,去類的方法表中尋找
  4. 通過superclass去找父類
  5. 通過父類去找IMP
  6. 循環(huán)4,5

消息轉(zhuǎn)發(fā)流程圖

消息轉(zhuǎn)發(fā)流程圖.png


1.0遮斥、方法的實(shí)現(xiàn)
 * 獲取實(shí)例方法:class_getInstanceMethod(Class cls, SEL name)
 * 設(shè)置類方法:   class_getClassMethod(Class cls, SEL name)
 * 獲取方法指針:method_getImplementation(Method m) 
 * 設(shè)置方法指針:method_setImplementation(Method m, IMP imp) (方法可以重新指向)
 * 設(shè)置對(duì)象的類:object_setClass(id obj, Class cls) 
1峦失、消息發(fā)送(方法調(diào)用的本質(zhì))
 * 注冊(cè)方法:sel_registerName(const char *str),sel_registerName("new")
 * 消息發(fā)送:objc_msgSend(id self, SEL op, ...)
 * 創(chuàng)建一類:objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) 
    objc_msgSend(消息接受者术吗,消息名稱)
    objc_msgSend(person, sel_registerName("walk"));
    objc_msgSend(objc_getClass("TZPerson"), sel_registerName("addRun"));
    Class TZCat = objc_allocateClassPair([NSObject class], "TZCat", 0);
2尉辑、動(dòng)態(tài)方法解析
  • 對(duì)象在收到無法解讀的消息后:調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel來動(dòng)態(tài)為其 添加實(shí)例方法,來處理該選擇较屿。
  • 如果尚未實(shí)現(xiàn)的方法是類方法:調(diào)用+ (BOOL)resolveClassMethod:(SEL)sel來動(dòng)態(tài)為其 添加類方法
//#pragma mark --- 實(shí)例方法 的轉(zhuǎn)換:+ (BOOL)resolveInstanceMethod:(SEL)sel
//#pragma mark --- 實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"---resolveInstanceMethod:%s", __func__);
    // 元類 
    // 實(shí)例對(duì)象隧魄、類對(duì)象卓练、元類對(duì)象
    //例6.2.1:無方法,構(gòu)建一個(gè)方法购啄,并去調(diào)用實(shí)現(xiàn)
    if (sel == @selector(walk)) {
        //創(chuàng)建實(shí)例方法(向?qū)嵗l(fā)送消息襟企,在類上添加方法)
        Method runMethod = class_getInstanceMethod(self, @selector(run));
        //方法指針
        IMP runIMP = method_getImplementation(runMethod);
        //描述方法參數(shù)的類型
        const char* types = method_getTypeEncoding(runMethod);
        NSLog(@"---(實(shí)例方法)types:%s", types); //v16@0:8  參數(shù)字節(jié)數(shù)16 @從第幾個(gè)字節(jié)開始 :表示第8個(gè)       
        return class_addMethod(self, sel, runIMP, types);
    }
    //有方法,去調(diào)用實(shí)現(xiàn)狮含,
    return [super resolveInstanceMethod:sel];
}


//#pragma mark --- 類方法 的轉(zhuǎn)換:+ (BOOL)resolveClassMethod:(SEL)sel
//#pragma mark --- 類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"---resolveClassMethod:%s", __func__);
    //例6.2.2:(類方法)無方法顽悼,構(gòu)建一個(gè)方法,并去調(diào)用實(shí)現(xiàn)
    if (sel == @selector(walk)) {
        //創(chuàng)建類方法(向類發(fā)送消息几迄,在元類上添加方法)
        Method runMethod = class_getInstanceMethod(object_getClass(self), @selector(run));
        //方法指針
        IMP runIMP = method_getImplementation(runMethod);
        //描述方法參數(shù)的類型
        const char* types = method_getTypeEncoding(runMethod);
        NSLog(@"---(類方法)types:%s", types); //v16@0:8  參數(shù)字節(jié)數(shù)16 @從第幾個(gè)字節(jié)開始 :表示第8個(gè)       
        return class_addMethod(object_getClass(self), sel, runIMP, types);
    }
    //有方法蔚龙,去調(diào)用實(shí)現(xiàn),
    return [super resolveClassMethod:sel];
}
3映胁、消息轉(zhuǎn)發(fā)

掛載到其他類的方法上木羹。
是否有消息轉(zhuǎn)發(fā):有就去調(diào)用、沒有就崩潰解孙。
既然已經(jīng)問過了汇跨,沒有新增方法,那就問問有沒有別人能夠幫忙處理一下:

\\實(shí)例方法 處理
- (id) forwardingTargetForSelector:(SEL)aSelector
- (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

\\類方法 處理
+ (id) forwardingTargetForSelector:(SEL)aSelector
+ (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector
+ (void)forwardInvocation:(NSInvocation *)anInvocation

源碼如下:

# ------------------------- (實(shí)例:消息轉(zhuǎn)發(fā)) ---------------------------
# 7.1: 實(shí)例方法(方法掛載妆距,掛載到其他類上)
# 方式一
- (id) forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(walk7)) {
        return [TZDog new];
    }    
    return [super forwardingTargetForSelector:aSelector];
}

# 方式二
# 方法名注冊(cè) 
- (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(walk7)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
# 消息轉(zhuǎn)發(fā):實(shí)例方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //NSLog(@"%s", __func__);
    //[anInvocation invokeWithTarget:[TZDog new]];   // 轉(zhuǎn)發(fā)給 其他
    // 轉(zhuǎn)發(fā)給 自己
    anInvocation.selector = @selector(run);
    anInvocation.target = self;
    [anInvocation invoke];
}

## ------------------------- (類:消息轉(zhuǎn)發(fā)) ---------------------------
##  7.2:類方法 消息轉(zhuǎn)發(fā)
##  方式一
+ (id) forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(walk7)) {
        //return [TZDog new]; //可以轉(zhuǎn)發(fā)到:類的實(shí)例方法
         return [TZDog class];//可以轉(zhuǎn)發(fā)到:類方法 
     }    
    return [super forwardingTargetForSelector:aSelector];
}

##  方式二
# 方法名注冊(cè) 
+ (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(walk7)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
} 
# 消息轉(zhuǎn)發(fā):類方法
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@" 轉(zhuǎn)發(fā)給 其他= %s", __func__);   
    //[anInvocation invokeWithTarget:[TZDog new]];   //可以轉(zhuǎn)發(fā)到:類的實(shí)例方法
    //[anInvocation invokeWithTarget:[TZDog class]]; //可以轉(zhuǎn)發(fā)到:類方法

    // 轉(zhuǎn)發(fā)給 自己
    anInvocation.selector = @selector(run);
    anInvocation.target = self;
    [anInvocation invoke];
}
# ------------------------- 結(jié)束(消息轉(zhuǎn)發(fā)) ---------------------------


三穷遂、Runtime 實(shí)現(xiàn)KVO

KVO的實(shí)現(xiàn)依賴于 Objective-C 強(qiáng)大的 Runtime,當(dāng)觀察某對(duì)象 People 時(shí)娱据,KVO 機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象people當(dāng)前類的子類蚪黑,并為這個(gè)新的子類重寫了被觀察屬性 keyPathsetter方法。setter 方法隨后負(fù)責(zé)通知觀察對(duì)象屬性的改變狀況中剩。

Apple 使用了isa-swizzling 來實(shí)現(xiàn) KVO 忌穿。當(dāng)觀察對(duì)象people時(shí),KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:NSKVONotifying_People的新類结啼,該類繼承自對(duì)象People 的本類掠剑,且 KVO 為 NSKVONotifying_People 重寫觀察屬性的 setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后郊愧,通知所有觀察對(duì)象屬性值的更改情況朴译。

NSKVONotifying_People 類剖析
NSLog(@"self->isa: %@",self->isa);  
NSLog(@"self class: %@",[self class]);  

# 在建立KVO監(jiān)聽前,打印結(jié)果為:
self->isa: People
self class: People

# 在建立KVO監(jiān)聽之后属铁,打印結(jié)果為:
self->isa: NSKVONotifying_People
self class: People


子類setter方法剖析

KVO 的鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:

  • willChangeValueForKey: 被觀察屬性發(fā)生改變之前被調(diào)用眠寿,通知即將改變、
  • didChangeValueForKey: 被觀察屬性發(fā)生改變之后被調(diào)用焦蘑,通知已經(jīng)變更盯拱。

在存取數(shù)值的前后分別調(diào)用 這2 個(gè)方法,且重寫觀察屬性的setter 方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。

KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

- (void)setName:(NSString *)newName { 
      [self willChangeValueForKey:@"name"];    # KVO 在調(diào)用存取方法之前總調(diào)用 
      [super setValue:newName forKey:@"name"]; # 調(diào)用父類的存取方法 
      [self didChangeValueForKey:@"name"];     # KVO 在調(diào)用存取方法之后總調(diào)用
}


四狡逢、Runtime的使用

1宁舰、關(guān)聯(lián)對(duì)象: 給分類添加屬性,AssociatedObject(提高性能奢浑,避免不使用的 繼承類屬性 初始化)

2蛮艰、遍歷類的所有成員變量:修改textfield的占位文字顏色、字典轉(zhuǎn)模型Model殷费、自動(dòng)歸檔解檔

3印荔、交換方法實(shí)現(xiàn):(交換系統(tǒng)的方法低葫,埋點(diǎn)详羡、防逆向hook)

4、利用消息轉(zhuǎn)發(fā)機(jī)制:解決 「方法找不到」的異常問題
......

1嘿悬、實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和自動(dòng)解檔

原理描述:用 Runtime提供的函數(shù)遍歷Model自身所有屬性实柠,并對(duì)屬性進(jìn)行encodedecode操作。
核心方法:在Model的基類中重寫方法:

引用:
1善涨、NSCoding協(xié)議
2窒盐、 知識(shí)小結(jié)三:NSCoding理解

NSArchiver、NSUnarchiver钢拧、NSKeyedArchiver蟹漓、NSKeyUnarchiver和NSPortCoder。NSCoder具體的子類統(tǒng)一稱作:編碼器類源内。
協(xié)議中只有兩個(gè)方法葡粒,都是@require必須實(shí)現(xiàn)的方法
-(void)encodeWithCoder:(NSCoder *)aCoder
-(id)initWithCoder:(NSCoder *)aDecoder

# 解檔(獲取內(nèi)容數(shù)據(jù))
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}
# 歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}
2、實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換( MJExtension )

原理描述:用Runtime提供的函數(shù)遍歷Model自身所有屬性膜钓,如果屬性在json中有對(duì)應(yīng)的值嗽交,則將其賦值。
核心方法:在NSObject 的分類中添加方法

- (instancetype)initWithDict:(NSDictionary *)dict {

    if (self = [self init]) {
        #//(1)獲取類的屬性及屬性對(duì)應(yīng)的類型
        NSMutableArray * keys = [NSMutableArray array];
        NSMutableArray * attributes = [NSMutableArray array];
        /** 例子
         * name = value3 attribute = T@"NSString",C,N,V_value3
         * name = value4 attribute = T^i,N,V_value4
         */
        unsigned int outCount;
        objc_property_t * properties = class_copyPropertyList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            objc_property_t property = properties[i];
            #// 通過property_getName函數(shù)獲得屬性的名字
            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [keys addObject:propertyName];
            #// 通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            [attributes addObject:propertyAttribute];
        }
        #// 立即釋放properties指向的內(nèi)存
        free(properties);

        #//(2)根據(jù)類型給屬性賦值
        for (NSString * key in keys) {
            if ([dict valueForKey:key] == nil) continue;
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
    return self;

}




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颂斜,一起剝皮案震驚了整個(gè)濱河市夫壁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沃疮,老刑警劉巖盒让,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異司蔬,居然都是意外死亡糯彬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門葱她,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撩扒,“玉大人,你說我怎么就攤上這事〈曜唬” “怎么了炒辉?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)泉手。 經(jīng)常有香客問我黔寇,道長(zhǎng),這世上最難降的妖魔是什么斩萌? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任缝裤,我火速辦了婚禮,結(jié)果婚禮上颊郎,老公的妹妹穿的比我還像新娘憋飞。我一直安慰自己,他們只是感情好姆吭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布榛做。 她就那樣靜靜地躺著,像睡著了一般内狸。 火紅的嫁衣襯著肌膚如雪检眯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天昆淡,我揣著相機(jī)與錄音锰瘸,去河邊找鬼。 笑死昂灵,一個(gè)胖子當(dāng)著我的面吹牛避凝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倔既,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼恕曲,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了渤涌?” 一聲冷哼從身側(cè)響起佩谣,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎实蓬,沒想到半個(gè)月后茸俭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡安皱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年调鬓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酌伊。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腾窝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虹脯,我是刑警寧澤驴娃,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站循集,受9級(jí)特大地震影響唇敞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咒彤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一疆柔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镶柱,春花似錦旷档、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽向楼。三九已至查吊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湖蜕,已是汗流浹背逻卖。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昭抒,地道東北人评也。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像灭返,于是被迫代替她去往敵國(guó)和親盗迟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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

  • Runtime Runtime是什么 Runtime 又叫運(yùn)行時(shí)熙含,是一套底層的 C 語言 API罚缕,其為 iOS 內(nèi)...
    b485c88ab697閱讀 8,377評(píng)論 3 48
  • 一、基礎(chǔ)知識(shí)點(diǎn) 設(shè)計(jì)模式是什么怎静? 你知道哪些設(shè)計(jì)模式邮弹,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn)蚓聘,就是用比較成熟的邏輯去...
    軟件iOS開發(fā)閱讀 1,274評(píng)論 0 26
  • 作為一個(gè)開發(fā)者腌乡,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS交流群:638302184夜牡,不管你是小白...
    iOS開發(fā)之家閱讀 3,570評(píng)論 0 18
  • 一与纽、基礎(chǔ)知識(shí)點(diǎn) 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述急迂? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn)硝岗,就是用比較成熟的邏...
    iOS泡泡閱讀 235評(píng)論 0 1
  • 夜鶯2517閱讀 127,717評(píng)論 1 9