runtime的底層原理和使用

先來了解一下isa的組成

我們?nèi)ミ@個(gè)網(wǎng)站(https://opensource.apple.com/tarballs/objc4/)搜索objc4,然后下載最新的壓縮文件,這個(gè)就是蘋果開源的部分的底層代碼(所以我們不能說蘋果是完全閉元的),如圖所示:

image.png

解壓 打開工程搜索isa_t 如圖:
image.png

可以看到如下的結(jié)構(gòu),代碼為:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        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;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};

其中我只分析arm64的 x86_64的是模擬器 或者mac的 我們不分析了 ,其中共用體里面有一個(gè)結(jié)構(gòu)體,可以看到 結(jié)構(gòu)體中加起來正好是64 也就說 我們的一個(gè)對(duì)象的isa中存放這些東西以及他們的內(nèi)存分配情況,我們知道這個(gè)結(jié)構(gòu)體是用來更高好的做解讀說明的,也就是isa中存放這些東西 ,那么他的每一個(gè)東西都是干什么用的呢 我會(huì)具體的解釋每一個(gè)的作用

        uintptr_t nonpointer        : 1;// 存儲(chǔ)著class meta-class的對(duì)象的內(nèi)存地址,0 :代表普通 1:代表優(yōu)化過的。
        uintptr_t has_assoc         : 1;// 是否有沒有設(shè)置過關(guān)聯(lián)對(duì)象 ,如果沒有設(shè)置過關(guān)聯(lián)對(duì)象 釋放的就會(huì)更快。(0:代表沒有1:代表有)
        uintptr_t has_cxx_dtor      : 1;// 是否有c++的析構(gòu)函數(shù),如果沒有釋放的更快。(0:代表沒有1:代表有)
        uintptr_t shiftcls          : 33; // 這33為 存儲(chǔ)的是對(duì)象的內(nèi)存地址信息。
        uintptr_t magic             : 6; // 這6為用于調(diào)試時(shí)是否為完成初始化。 
        uintptr_t weakly_referenced : 1;// 是否是為被弱引用指向過
        uintptr_t deallocating      : 1; // 對(duì)象是否正在釋放
        uintptr_t has_sidetable_rc  : 1;// 里面引用計(jì)數(shù)是否過大無法存儲(chǔ)在isa中,如果為1 則證明過大,那么引用計(jì)數(shù)會(huì)存在SideTable的類中
        uintptr_t extra_rc          : 19;// 存儲(chǔ)的是引用計(jì)數(shù)器減1

為了驗(yàn)證我的結(jié)論,我拿出一個(gè)例子來進(jìn)行驗(yàn)證
代碼1為:

 DGPerson *person = [[DGPerson alloc] init];
  NSLog(@"-------------");

打印person的內(nèi)存地址


image.png

將我們的內(nèi)存地址放到我們的系統(tǒng)自帶的計(jì)算器中 可以看到


image.png

沒有設(shè)置關(guān)聯(lián)對(duì)象的這個(gè)位置是0,接下來設(shè)置一下關(guān)聯(lián)對(duì)象
代碼2為:
DGPerson *person = [[DGPerson alloc] init];
    objc_setAssociatedObject(person, @"nameKey", @"asdasdasdasd", OBJC_ASSOCIATION_COPY_NONATOMIC);

可以看到 第二位為1了


image.png

其他的不一一驗(yàn)證了

了解一下class的組成以及每一部分的作用

  • 大家我們上面所說的底層的代碼。搜索objc_class,如圖所示,找到這個(gè)文件objc-runtime-new.h文件(運(yùn)行時(shí)的文件)


    image.png

    經(jīng)過精簡:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;  // 用于指向父類的指針
    cache_t cache;             // 緩存方法,為了下次快速查找
    class_data_bits_t bits; // 用于獲取具體的類信息
}

其中superclass 是如果在當(dāng)前的類對(duì)象中找不到就通過superclass到父類中去查找。
cache_t的結(jié)構(gòu)可以看到為:

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}
struct bucket_t {
    cache_key_t _key;
    IMP _imp;
}

其中_buckets存儲(chǔ)的是一個(gè)個(gè)的bucket_t,而_mask是散列表的長度-1,(比如散列表的長度是10,那么他就是9)_occupied是已經(jīng)緩存的方法的個(gè)數(shù)。cache_t 是通過散列表(哈希表)的方式進(jìn)行緩存的,這樣的做的目的是更加快速找到找到我們?cè)?jīng)緩存的方法。bucket_t中存在一個(gè)key和imp,其中SEL作為key,而imp為方法的地址 。比如我們下面的代碼:

   DGPerson *person = [[DGPerson alloc] init];
   [person test];
   
   [person test];
   [person test];
   [person test];

這樣的方法 在第一次person 第一次執(zhí)行test方法的時(shí)候是按部就班的執(zhí)行,先去當(dāng)前類中查找,如果找不到就到父類中查找,以此類推。但是第二次在調(diào)用的時(shí)候就會(huì)就直接從緩存中查找了 那樣的話查找的速度就更加的快了。

  • class_data_bits_t這個(gè)&上FAST_DATA_MASK就會(huì)獲得這個(gè)class_rw_t,他的樣子為:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

對(duì)其中重點(diǎn)的東西進(jìn)行說明:methods 就是方法列表,properties屬性列表,protocols協(xié)議列表。其中methods是一個(gè)二維的數(shù)組,methods存放的是method_list_t, method_list_t中存放的是method_t ,結(jié)構(gòu)如圖所示:


image.png

我們重點(diǎn)研究一下ro(只讀)??梢钥吹絚lass_ro_t的結(jié)構(gòu)如下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

其中他也有一個(gè)baseMethodList,他的baseMethodList存放的是method_t,其中class_ro_t中的methodlist 和class_rw_t中的methodlist有什么區(qū)別呢?可以說class_ro_t中的methodlist 是只讀的,是類原始的方法等等,而class_rw_t中的methodlist是整個(gè)的比如后面分類中增加的方法都加入到這里來了,可以說class_rw_t中的methodlist大于等于class_ro_t中的methodlist的方法。

  • method_t的結(jié)構(gòu)以及解釋:
struct method_t {
    SEL name; // 函數(shù)名字
    const char *types; // 編碼(返回值類型、參數(shù)類型)
    IMP imp;// 指向函數(shù)的指針(函數(shù)的地址)
};

解釋:
1.其中imp是指向函數(shù)的指針,代表著具體函數(shù)的實(shí)現(xiàn)。
2.SEL是函數(shù)的方法選擇器
可以通過一下幾種方法生成

    SEL method1 = @selector(name);
    SEL method2 = sel_registerName("name");
    NSLog(@"method1 : %p -- method2:%p",method1,method2);

而且不同類中只要方法的名字相同生成的方法選擇器是相同的,比如以上的打?。?/p>

image.png

當(dāng)然你可以試試不同的類 我這里不試了 因?yàn)榇_實(shí)是這樣的。
還可以通過以下的或者相應(yīng)的字符串

    SEL method1 = @selector(name);
    SEL method2 = sel_registerName("name");
    NSString *methodName1 = NSStringFromSelector(method1);
    const char *methodName2 = sel_getName(method2);
    NSLog(@"methodName1 --- %@ //// methodName2 ---- %s",methodName1,methodName2);

可以看到打印的結(jié)果為:


image.png
  • types編碼他的格式為:


    image.png

    下面我們通過代碼看下person中的types的類型

    DGPerson *person = [[DGPerson alloc] init];
    DG_objc_class *personStruct = (__bridge DG_objc_class *)[DGPerson class];
    class_rw_t *rwStruct = personStruct->data();
      NSLog(@"---------");
其中person中的方法為:
      - (void)test;

其中DG_objc_class是c++的一個(gè)文件,相關(guān)的修改內(nèi)容如下(就是一個(gè).h文件,使用需要將你的controller改為controller.mm)

//
//  DGPersonInfo.h
//  verification_Isa
//
//  Created by apple on 2018/8/1.
//  Copyright ? 2018年 apple. All rights reserved.
//
#import <Foundation/Foundation.h>

#ifndef DGPersonInfo_h
#define DGPersonInfo_h


# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

#else
#error unknown architecture
#endif

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    IMP imp(SEL selector)
    {
        mask_t begin = _mask & (long long)selector;
        mask_t i = begin;
        do {
            if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {
                return _buckets[i]._imp;
            }
        } while ((i = cache_next(i, _mask)) != begin);
        return NULL;
    }
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成員變量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 屬性列表
    const protocol_list_t * protocols;  // 協(xié)議列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC對(duì)象 */
struct DG_objc_object {
    void *isa;
};

/* 類對(duì)象 */
struct DG_objc_class : DG_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    DG_objc_class* metaClass() {
        return (DG_objc_class *)((long long)isa & ISA_MASK);
    }
};
#endif /* DGPersonInfo_h */

通過打印可以看到types為:v16@0:8
解釋,其實(shí)test的真正為-(void)test:(id)self cmd:(SEL)cmd.
1.v:返回值為(void)
2.整個(gè)對(duì)象占用16個(gè)字節(jié)的空間
3.@:id
4.從第0位開始(id )我們知道占用8位
5.:返回值是SEL
6.8從第八位開始,占用8個(gè)字節(jié)。
假如我們修改test方法改為- (int)test:(NSString *)name,那么我們知道其實(shí)他的真正為:- (int)test:(id)self cmd:(SEL)cmd name:(NSString *)name,其中前兩個(gè)參數(shù)是系統(tǒng)默認(rèn)給我們加上去的。
那么我們看一下types是什么?


image.png

可以看到types為“i24@0:8@16”
解釋:
1.i:返回的是int類型
2.24 共占用24個(gè)字節(jié)
3.@參數(shù)id類型
4.0:從第0個(gè)字節(jié)開始
5.:SEL參數(shù)類型
6.8從第8個(gè)字節(jié)開始(因?yàn)閟elf是一個(gè)對(duì)象,所以占用8個(gè)字節(jié))
7.@參數(shù)nsstring類型
8.16從第16個(gè)字節(jié)開始,那么他占用的是8個(gè)字節(jié)(24-16)
我在網(wǎng)上找了一篇文章,找到了type encoding的對(duì)應(yīng)表


image.png
  • 重點(diǎn)研究一下cache_t 緩存的方法和散列表的算法原理。
    1.散列表的算法的大致說明:
    首先我們將一個(gè)方法添加進(jìn)去緩存的數(shù)組,但是他怎么放的呢,也就說這個(gè)方法所在的數(shù)組的index是多少呢 ,它是@selector(方法名字)&MASK = index,開始的時(shí)候他會(huì)默認(rèn)開啟一個(gè)數(shù)組的空間,然后讓第一次調(diào)用的方法緩存進(jìn)去我們的數(shù)組中,依次類推。比如我們的開始數(shù)組的個(gè)數(shù)是10,但是我們緩存的方法是3個(gè)那么還有7個(gè)位置是空的 那么他就會(huì)將那些位置置為null,他下一個(gè)在一次調(diào)用同樣的方法時(shí)候,他就會(huì)通過@selector(方法名字)&MASK = index 這樣直接去拿數(shù)組的index,這樣的操作雖然犧牲了一些空間 但是確實(shí)提高了很大的效率 不用便利查找了。但是存在
    問題1.有可能我們生成的下標(biāo)是重復(fù)的。
    問題2.如果有一天我們的緩存的方法變成了20 那么我們之前的數(shù)組空間不夠了怎么辦。
    問題1解決:他每一次拿出來的東西會(huì)判斷是否等于我們的方法名字,如果發(fā)現(xiàn)發(fā)現(xiàn)等于直接取出,如果不等于直接去前一個(gè)以此類推,如果發(fā)現(xiàn)第0個(gè)還不是 那就取數(shù)組最后一個(gè) 然后在往前找一次類推。當(dāng)然存儲(chǔ)的時(shí)候他會(huì)判斷當(dāng)前設(shè)置的下標(biāo)所對(duì)應(yīng)的對(duì)象是否有值,有值的話就會(huì)往前存儲(chǔ)依次類推,如果發(fā)現(xiàn)第0個(gè)都有值 那么就設(shè)置設(shè)置最后一個(gè)值 依次類推。
    問題2解決:當(dāng)有一天他發(fā)現(xiàn)數(shù)組的空間小于要緩存的方法的個(gè)數(shù),那么他會(huì)重新計(jì)算分配數(shù)組空間 以及重新緩存方法。
  • 我們具體查看一下緩存的方法 以及驗(yàn)證我以上說的結(jié)論
    先說名cache_t的結(jié)構(gòu)如下:
struct cache_t {
    struct bucket_t *_buckets; // 方法的key和imp
    mask_t _mask;// mask做&操作
    mask_t _occupied;//已經(jīng)緩存的方法的個(gè)數(shù)
}
struct bucket_t {
    cache_key_t _key;
    IMP _imp;
}

代碼中全部都是繼承關(guān)系,我們看下具體執(zhí)行的結(jié)果

        DGChinesePerson *chinesePerson = [[DGChinesePerson alloc] init];
        DG_objc_class *chinesePersonStruct = (__bridge DG_objc_class *)[DGChinesePerson class];
        
        [chinesePerson DGChinesePersonTest];
        [chinesePerson DGYellowPersonTest];
        [chinesePerson personTest];
        NSLog(@"---------------");

我們分別在每一個(gè)方法執(zhí)行的地方打上一個(gè)斷點(diǎn),查看效果


image.png

image.png

image.png

image.png

可以看到 開始分配的數(shù)組的個(gè)數(shù)是4(mask+1),mask是3 當(dāng)?shù)降谝粋€(gè)方法的時(shí)候occupied為1,因?yàn)榇藭r(shí)執(zhí)行了alloc方法,到最后一個(gè)方法執(zhí)行的時(shí)候數(shù)組重新分配變成了8((mash= k)+1),可以驗(yàn)證我以上的結(jié)論了。
下面我們來看下具體的緩存的方法,我循環(huán)便利打印出來
打印的代碼為:

        DGChinesePerson *chinesePerson = [[DGChinesePerson alloc] init];
        DG_objc_class *chinesePersonStruct = (__bridge DG_objc_class *)[DGChinesePerson class];
        cache_t cacheMethods = chinesePersonStruct->cache;
        
        [chinesePerson DGChinesePersonTest];
        [chinesePerson DGYellowPersonTest];
        [chinesePerson personTest];
        NSLog(@"---------------");
        bucket_t *bucketLists = cacheMethods._buckets;
        for (int index = 0; index <= cacheMethods._mask; index++) {
            bucket_t bucket = bucketLists[index];
            NSLog(@"_key : %s --- _imp : %p",bucket._key,bucket._imp);
        }
image.png

可以看到我們緩存中并沒有看到persontest這個(gè)方法 其實(shí)這不是結(jié)論錯(cuò)誤是因?yàn)槲覀儷@取的地方不對(duì),假如代碼這樣修改為:


image.png

可以看到結(jié)果為:


image.png

由此可見以上所說的結(jié)論是毫無問題的
下面我們執(zhí)行這段代碼:
       DGChinesePerson *chinesePerson = [[DGChinesePerson alloc] init];
        DG_objc_class *chinesePersonStruct = (__bridge DG_objc_class *)[DGChinesePerson class];
        cache_t cacheMethods = chinesePersonStruct->cache;
        
        [chinesePerson DGChinesePersonTest];
        [chinesePerson DGYellowPersonTest];
        NSLog(@"---------------");
        bucket_t *bucketLists = cacheMethods._buckets;
        bucket_t testBucket = bucketLists[(long long)@selector(DGChinesePersonTest) & cacheMethods._mask];
        NSLog(@"_key : %s --- _imp : %p",testBucket._key,testBucket._imp);

打印結(jié)果是:


image.png

可以看到真是我們的的方法名字(但是這樣打印是不準(zhǔn)確的,應(yīng)該明白只不過是趕巧而已)
我們可以將以上程序修改為,這樣打印的一定是準(zhǔn)確的

        DGChinesePerson *chinesePerson = [[DGChinesePerson alloc] init];
        DG_objc_class *chinesePersonStruct = (__bridge DG_objc_class *)[DGChinesePerson class];
        cache_t cacheMethods = chinesePersonStruct->cache;
        [chinesePerson DGChinesePersonTest];
        [chinesePerson DGYellowPersonTest];
        NSLog(@"---------------");
        IMP methodImp = cacheMethods.imp(@selector(DGChinesePersonTest));
        NSLog(@"methodImp -- %p",methodImp);
        NSLog(@"+++++++++++++");
image.png

為什么是準(zhǔn)確的,因?yàn)槲业念^文件中這樣書寫


image.png

相當(dāng)于按照散列表的算法進(jìn)行書寫,這樣拿到的一定不為null

進(jìn)入正題 消息機(jī)制

  • ios的消息機(jī)制分為三個(gè)主要的步驟:
    1.消息發(fā)送
    2.消息方法的動(dòng)態(tài)解析
    3.消息的轉(zhuǎn)發(fā)
  • 消息發(fā)送:
    他的大致過程為:首先他會(huì)判斷receiver是否為空,如果為空則直接返回。如果不為空到當(dāng)前類的方法的緩存中去查找,如果找到了直接返回方法,如果沒有找到到當(dāng)前類的方法中繼續(xù)查找,如果找到了返回方法并且存儲(chǔ)在當(dāng)前類的緩存方法中。如果沒有找到通過superclass指針到當(dāng)前類的父類中中的緩存方法中去查找,如果找到了方法那么返回方法并且緩存進(jìn)當(dāng)前類對(duì)象的緩存方法中。如果沒有找到找當(dāng)前類對(duì)象的class_rw_t的方法列表中查找,找到了緩存進(jìn)方法列表中并且返回方法。如果沒有找到繼續(xù)通過superclass的指針到其父類的父類中查找,依次類推。
    用一張圖形象的表示為:
    image.png

    其中如果找class_rw_t的方法列表中存在的方法是有順序的采用的是二分查找方法。如果不是有序的那么采用的是普通的便利查找。
  • 如果以上都找不到方法,就會(huì)進(jìn)入動(dòng)態(tài)的方法的解析階段,我們可以在此階段動(dòng)態(tài)的添加方法的實(shí)現(xiàn)。比如
    代碼1:
person中添加如下方法,并沒有添加實(shí)現(xiàn)
- (void)test;
我在person中添加另一個(gè)方法的實(shí)現(xiàn)比如
- (void)otherTest{
    
    NSLog(@"------------");
    
}
我想動(dòng)態(tài)的添加方法的實(shí)現(xiàn)可以如下操作:
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(otherTest));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
   return [super resolveInstanceMethod:sel];
}

其中resolveInstanceMethod這個(gè)方法是在消息發(fā)送的過程中找不到當(dāng)前方法的實(shí)現(xiàn)才會(huì)調(diào)用這個(gè)方法,來動(dòng)態(tài)的找方法的實(shí)現(xiàn)。
代碼二:也可以通過這種方法來實(shí)現(xiàn):

void otherTest(id self,SEL _cmd){
    
    NSLog(@"------------");
    
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        class_addMethod(self, sel, (IMP)otherTest, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

當(dāng)然如果是類對(duì)象,那么就需要實(shí)現(xiàn)這個(gè)方法

void otherTest(id self,SEL _cmd){
    
    NSLog(@"------------");
    
}
+(BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(test)) {
        class_addMethod(object_getClass(self), sel, (IMP)(otherTest), "v@:");
    }
    return [super resolveClassMethod:sel];
    
}

需要注意兩點(diǎn):
1.實(shí)例方法實(shí)現(xiàn)的是實(shí)例方法,類方法實(shí)現(xiàn)的是類方法。
2.還有其中參數(shù)傳遞的時(shí)候是object_getClass(self) 也就是他的原類的對(duì)象。

消息的轉(zhuǎn)發(fā)

當(dāng)消息的發(fā)送和消息的動(dòng)態(tài)的方法的實(shí)現(xiàn)都找不到方法的時(shí)候也就是進(jìn)入到了消息的轉(zhuǎn)發(fā)的階段
他會(huì)實(shí)現(xiàn)這個(gè)方法:

-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [[DGStudent alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
其中DGStudent的內(nèi)部實(shí)現(xiàn)了test方法
@interface DGStudent : NSObject
- (void)test;
@end
@implementation DGStudent
- (void)test{
    NSLog(@"我的打印是 --- %s",__func__);
}
@end

假如這個(gè)方法不實(shí)現(xiàn),他會(huì)調(diào)用哪個(gè)方法呢?他會(huì)實(shí)現(xiàn)這個(gè)方法,就是

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

這個(gè)方法是返回一個(gè)方法的types,要想實(shí)現(xiàn)該方法需要結(jié)合下面的方法一起實(shí)現(xiàn)
所以結(jié)合起來的是這樣實(shí)現(xiàn)的:

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[DGStudent alloc] init]];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
   return [super methodSignatureForSelector:aSelector];
}
總結(jié):小的轉(zhuǎn)發(fā)的流程是首先進(jìn)入這個(gè)方法:forwardingTargetForSelector:(SEL)aSelector如果這個(gè)方法返回了那么就去返回方法的實(shí)現(xiàn),如果返回為nil那么就去找這個(gè)方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector當(dāng)然需要-(void)forwardInvocation:(NSInvocation *)anInvocation這個(gè)方法的配合實(shí)現(xiàn)。如果這個(gè)方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector也沒有返回那么就去找這個(gè)方法doesNotRecognizeSelector的這個(gè)方法。也就是報(bào)錯(cuò)(方法找不到實(shí)現(xiàn)的)。

一個(gè)簡單的流程圖是這樣的


image.png

也就是說消息的一個(gè)發(fā)送過程就是以上的步驟。

  • 消息轉(zhuǎn)發(fā)的用處:
    我們知道當(dāng)報(bào)方法找不到這個(gè)錯(cuò)誤的話就會(huì)顯示錯(cuò)誤,也就是程序就會(huì)crash,為了降低crash的出錯(cuò)率 ,我簡單寫一個(gè)小的程序,比如person類,當(dāng)出現(xiàn)了幾個(gè)方法找不到的時(shí)候我們?cè)鯓幽苷业剿?br> 代碼如下:
#import "DGPerson.h"
#import <objc/runtime.h>
@implementation DGPerson

- (void)test{
    NSLog(@"%s",__func__);
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if ([self respondsToSelector:aSelector]) {
        
        return [NSMethodSignature methodSignatureForSelector:aSelector];
    }
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%@ -- 哪個(gè)方法 %@ 沒有實(shí)現(xiàn)",anInvocation.target,NSStringFromSelector(anInvocation.selector));
    
}
@end

#import <Foundation/Foundation.h>

@interface DGPerson : NSObject
-(void)test;
-(void)run;
-(void)eat;
@end

當(dāng)我們調(diào)用這三個(gè)方法的時(shí)候,他會(huì)打印如下的方法


image.png

super的理解

看看下面的打印,

#import "DGStudent.h"

@implementation DGStudent

-(void)handleAction{
    [super handleAction];
}
-(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
其中的繼承的關(guān)系為 student繼承person,person繼承object

打印的結(jié)果為:


image.png

其中第一和第二都能很簡單的知道為什么,但是第三和第四是為什么
按照我的個(gè)人的理解應(yīng)該是person 和object但是為什么student 和person呢
先解釋一下super,其中實(shí)現(xiàn)這樣的方法

#import "DGStudent.h"
@implementation DGStudent
-(void)handleAction{
   [super handleAction];
}
end

看一下他的底層的代碼的實(shí)現(xiàn)

static void _I_DGStudent_handleAction(DGStudent * self, SEL _cmd)
 {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super)
{      
        (id)self,
        (id)class_getSuperclass(objc_getClass("DGStudent"))
},
        sel_registerName("handleAction"));
}

相當(dāng)于是一個(gè)結(jié)構(gòu)體,我們知道student 那么就相當(dāng)于這樣寫

static void _I_DGStudent_handleAction(DGStudent * self, SEL _cmd)
 {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super)
{      
        (id)self,
        DGPerson
},
        handleAction方法
}

我們看下__rw_objc_super這個(gè)結(jié)構(gòu)體里面是怎樣組成的(到我們的底層代碼庫中查找發(fā)現(xiàn))


image.png

整理得到:

struct objc_super {
   __unsafe_unretained _Nonnull id receiver;
   __unsafe_unretained _Nonnull Class super_class;
}

在看一下我們的handleAction的底層實(shí)現(xiàn),發(fā)現(xiàn)他的receiver我們傳遞的是self而super_class我們傳遞的是DGPerson,在看一下底層的這方法的objc_msgSendSuper的實(shí)現(xiàn)原理


image.png

通過注釋中可以發(fā)現(xiàn),他的super的實(shí)現(xiàn)是從當(dāng)前類中的父類開始查找方法的實(shí)現(xiàn),但是對(duì)象傳遞的的接受者還是傳遞的當(dāng)前的對(duì)象。
下面回到當(dāng)前的問題 為什么[super class] 打印的是student 按照剛才的分析不應(yīng)該是person嗎 ?但是我們還是忽略了class的這個(gè)方法,因?yàn)閏lass的這個(gè)方法是nsobject的方法 他的偽代碼大致為:

-(Class)class{
    return object_getClass(self);
}

那就是說明返回對(duì)象本身
而superclass的偽代碼的大致實(shí)現(xiàn)為:

-(Class)superClass {
   return class_getSuperclass(object_getClass(self));
}

那么他應(yīng)該返回的就是person。

  • 繼續(xù)對(duì)super進(jìn)行深入的探究,剛剛我們探究得到super執(zhí)行的是objc_msgSendSuper這個(gè)方法,但是我們看到我們的匯編執(zhí)行的代碼是


    image.png

    首先我們看下如下的代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [DGPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
    NSLog(@"---------------");
}

我們打印可以看出


image.png

可以看到我們按照8個(gè)字節(jié)往上找,我們找打了super viewdidload的底層實(shí)現(xiàn),我們知道他的大概實(shí)現(xiàn)為:

 struct ss = {
        id self,
        [ViewController class]
    };
    objc_MegSendSuper(ss,sel_registerName("viewDidLoad"));
image.png

可以看到打印,但是為什么第三個(gè)就是呢?我大致畫一個(gè)圖分析下


image.png

所以我們獲取第三個(gè)就是viewcontroller 那么說明super底層調(diào)用的就是super2的方法。(lldb中命令 x/4g的意思是16進(jìn)制,打印4個(gè)8個(gè)字節(jié)的)

  • 了解的內(nèi)容(llvm)
    我們oc的語言實(shí)際上是先轉(zhuǎn)換成中間的語言(llvm)然后在轉(zhuǎn)換成底層的匯編語言/機(jī)器語言,下面我們將我們寫的代碼轉(zhuǎn)化成llvm的語言
    我們的代碼為:
void test(int a){
    NSLog(@"%d",a);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 20;
        int c = a + b;
        test(c);
    }
    return 0;
}

我們通過這個(gè)命令進(jìn)行轉(zhuǎn)化:(切換到我們的main. m所在的上層文件夾)

clang -emit-llvm -S main.m

可以看到如下代碼:


image.png

image.png

isMemberOfClass和isKindOfClass的區(qū)別(順便一講,已經(jīng)熟悉的請(qǐng)濾過,本人只是為了做筆記)

  • 實(shí)力對(duì)象開始調(diào)用的時(shí)候
    比如我們看下這個(gè)例子:
        DGPerson *person = [[DGPerson alloc] init];
        NSLog(@"%d",[person isMemberOfClass:[DGPerson class]]);
        NSLog(@"%d",[person isMemberOfClass:[NSObject class]]);
        NSLog(@"%d",[person isKindOfClass:[DGPerson class]]);
        NSLog(@"%d",[person isKindOfClass:[NSObject class]]);

可以看到打印的結(jié)果


image.png

為了弄清除這個(gè)問題我們需要看下底層代碼的實(shí)現(xiàn):

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

因?yàn)槲覀儸F(xiàn)在調(diào)用的是實(shí)例對(duì)象,所以我們看下減號(hào)的方法,可以看到- (BOOL)isMemberOfClass:(Class)cls拿到的是[self class]進(jìn)行比較,也就是我們實(shí)力對(duì)象調(diào)用isMemberOfClass比較的是當(dāng)前對(duì)象的類對(duì)象,如果一致返回的就是yes否則是no,而isKindOfClass通過當(dāng)前類對(duì)象以及當(dāng)前類對(duì)象的父類對(duì)象進(jìn)行比較發(fā)現(xiàn)是當(dāng)前的類對(duì)象或者當(dāng)前類對(duì)象的父類的類對(duì)象那么就返回yes,否則返回no。
所以以上打印的結(jié)果是1 0 1 1
下面我將程序修改為:

        NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
        NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
        NSLog(@"%d", [[DGPerson class] isKindOfClass:[DGPerson class]]);
        NSLog(@"%d", [[DGPerson class] isMemberOfClass:[DGPerson class]]);

可以看到運(yùn)行的結(jié)果為:


image.png

先看下我們的isKindOfClass的類對(duì)象的調(diào)用方法,可以看到他拿到的是當(dāng)前對(duì)象的原類對(duì)象進(jìn)行判斷的,判斷我們傳遞的對(duì)象的原類的對(duì)象,已經(jīng)原類對(duì)象的父類的原類對(duì)象,如果發(fā)現(xiàn)相等就返回yes 否則返回no,而isMemberOfClass是判斷當(dāng)前對(duì)象的原類對(duì)象是不是和我們傳遞進(jìn)來的對(duì)象是否相等,由此可以分析出isMemberOfClass必定都是no,而isKindOfClass當(dāng)我們調(diào)用這句代碼的時(shí)候( [[DGPerson class] isKindOfClass:[DGPerson class]]);)應(yīng)該也是返回no,因?yàn)槲覀冎浪麘?yīng)該等于他的原類對(duì)象,但是我們判斷[[NSObject class] isKindOfClass:[NSObject class]]);為什么就是yes了 因?yàn)閕sKindOfClass他會(huì)去父類中查找一直找找到nsobject的原類的時(shí)候他就會(huì)去找類對(duì)象 類對(duì)象就是nsobject,我們以前說過這樣的一幅圖


image.png

可以看到以上所說,那么現(xiàn)在也就是說任何繼承nsobject對(duì)象的類對(duì)象調(diào)用isKindOfClass如果我們傳遞的是[NSObject class]那么都應(yīng)該返回的是yes,比如
NSLog(@"%d", [[DGPerson class] isKindOfClass:[NSObject class]]);
image.png

runtime的運(yùn)用(個(gè)人覺得很重要)

    1. 類的相關(guān)的api
      1.1 object_setClass :切換isa的指向
    DGPerson *person = [[DGPerson alloc] init];
    [person test];
    // 將person的isa指針指向DGCar
    object_setClass(person, [DGCar class]);
    [person test];
image.png

1.2 object_getClass:獲取isa所指向的類
解釋:
如果傳遞的是實(shí)例對(duì)象那么獲取的是類對(duì)象。
如果傳遞的是類對(duì)象那么獲取的是原類對(duì)象。
1.3 object_isClass 判斷的是否是一個(gè)類對(duì)象:

    DGPerson *person = [[DGPerson alloc] init];
    NSLog(@"%d - %d - %d", object_isClass([DGPerson class]),object_isClass(object_getClass([DGPerson class])),object_isClass(person));
image.png

解釋:值得說明的是原類對(duì)象也是特殊的類對(duì)象,所以第二個(gè)打印的是1
1.4 class_isMetaClass 判斷是不是原類的對(duì)象,不再舉例子 。
1.5 class_getSuperclass()獲取父類,太簡單不在舉例子。
1.6 動(dòng)態(tài)創(chuàng)建一個(gè)類,以及注冊(cè)一個(gè)類 一般的情況下他們是組合一起使用的(其中也包括動(dòng)態(tài)的添加方法和屬性等)
代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];  
    //創(chuàng)建一個(gè)類
    Class animalClass = objc_allocateClassPair([NSObject class], "DGAnimal", 0);
    // 添加屬性(animalClass:類名 4:int占的字節(jié)數(shù) 1:內(nèi)存對(duì)齊默認(rèn)為1 "v@:":types)
    class_addIvar(animalClass, "_age", 4, 1, @encode(int));
    // 添加方法
    class_addMethod(animalClass, @selector(test), (IMP)otherTest, "v@:");
    // 注冊(cè)一個(gè)類 ,添加方法等等的 要在注冊(cè)類的前面執(zhí)行最好
    objc_registerClassPair(animalClass);
    
    // 開始使用(必須還要alloc,否則失?。?    id animal = [[animalClass alloc] init];
    [animal setValue:@10 forKey:@"_age"];
    
    NSLog(@"age = %@",[animal valueForKey:@"_age"]);
    [animal test];
    
}
void otherTest(){
    
    NSLog(@"老夫打印了哈");
}
// 值得非常注意的是,在這個(gè)類不用的時(shí)候需要釋放因?yàn)槭莄語言的 ,所以必須要釋放
// 不在用到這個(gè)類的時(shí)候需要釋放
    objc_disposeClassPair(animalClass);
image.png
  • 2 成員變量相關(guān)的信息
    2.1查詢成員變量的信息(獲取當(dāng)前類的成員變量)
// 獲取成員變量
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([UITextField class], &count);
    for (int index = 0; index < count; index++) {
        Ivar ivar = ivarList[index];
        NSString *name = [NSString stringWithCString: ivar_getName(ivar) encoding:NSUTF8StringEncoding];
        NSLog(@"%@",name);
        
    }
    free(ivarList);
image.png

2.2 獲取一個(gè)成員變量

 // 獲取一個(gè)對(duì)象的一個(gè)成員變量
    Ivar ivar = class_getInstanceVariable([DGPerson class], "_name");
    NSString *nameStr = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
    NSString *typeStr = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
    NSLog(@"nameStr --- %@  type---%@",nameStr,typeStr);
image.png

2.3設(shè)置和獲取成員變量的值

 // 設(shè)置一個(gè)ivar
    DGPerson *person = [[DGPerson alloc] init];
    Ivar nameIvar = class_getInstanceVariable([DGPerson class], "_name");
    Ivar ageIvar = class_getInstanceVariable([DGPerson class], "_age");
    object_setIvar(person, nameIvar, @"ahshdahshdahsd");
    object_setIvar(person, ageIvar, (__bridge id)(void *)10);
    NSLog(@"name = %@ --- age = %d",person.name,person.age);
image.png
  • 3 屬性相關(guān)的
    3.1 property_getName(獲得一個(gè)類的屬性)
    objc_property_t nameProperty = class_getProperty([DGPerson class], "name");
    NSString *nameStr = [NSString stringWithCString:property_getName(nameProperty)   encoding:NSUTF8StringEncoding];
    NSLog(@"nameStr -- %@",nameStr);
image.png

3.2class_copyPropertyList(獲取屬性列表)
與獲取成員變量的方式一樣 不在贅述。
3.3 class_addProperty(動(dòng)態(tài)的添加成員變量)

//動(dòng)態(tài)的添加屬性
    objc_property_attribute_t type = {"T",[[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; // type 我這里是string類型
    objc_property_attribute_t copyShip = { "C",""}; // C = copy
    objc_property_attribute_t nonatomicAttr = {"N",""}; // N = nonatomic
    objc_property_attribute_t nameIvar = { "V",[[NSString stringWithFormat:@"_%@",@"hand"] UTF8String]};
    objc_property_attribute_t attrs[] = {type,copyShip,nonatomicAttr,nameIvar};
    class_addProperty([DGPerson class], [@"hand" UTF8String], attrs, 4);
    
    // 動(dòng)態(tài)的獲取屬性
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([DGPerson class], &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        NSLog(@"屬性 %s ======= 特征 %s\n", property_getName(property), property_getAttributes(property));
    }

3.4 class_replaceProperty(動(dòng)態(tài)的替換成員變量)
與3.3基本類似,不在贅述。
3.5. 獲取屬性的信息(property_getName(<#objc_property_t _Nonnull property#>) property_getAttributes(<#objc_property_t _Nonnull property#>))
在3.3使用過,不在贅述。
4 方法的相關(guān)的操作
4.1class_getClassMethod( 獲取一個(gè)類方法)

 // 獲取一個(gè)實(shí)例的方法
    Method testMethod = class_getClassMethod([DGPerson class], @selector(eat));
    NSString *methodName = NSStringFromSelector(method_getName(testMethod));
    NSLog(@"methodName -- %@",methodName);
image.png

4.2 class_getInstanceMethod (獲取一個(gè)實(shí)例的對(duì)象)
與4.1類似
4.3class_getMethodImplementation(獲取一個(gè)方法的實(shí)現(xiàn))

 IMP eatImp = class_getMethodImplementation([DGPerson class], @selector(eat));

返回是IMP
4.4 method_setImplementation(設(shè)置一個(gè)方法的實(shí)現(xiàn))

    // 設(shè)置一個(gè)方法的實(shí)現(xiàn)
    IMP carTestImp = class_getMethodImplementation([DGCar class], @selector(test));
    Method personTestMethod = class_getInstanceMethod([DGPerson class], @selector(test));
    method_setImplementation(personTestMethod, carTestImp);
    DGPerson *person = [[DGPerson alloc] init];
    [person test];
image.png

4.5 替換方法的實(shí)現(xiàn),這個(gè)很重要我們經(jīng)常使用。

  // 替換方法的實(shí)現(xiàn)
    
    Method personTestMethod = class_getInstanceMethod([DGPerson class], @selector(test));
    Method carTestMethod = class_getInstanceMethod([DGCar class], @selector(test));
    method_exchangeImplementations(personTestMethod, carTestMethod);
    
    DGPerson *person = [[DGPerson alloc] init];
    [person test];
image.png

4.6 class_copyMethodList (copy方法列表)
她的實(shí)現(xiàn)和我們獲取成員變量的形式類似
4.7 class_addMethod(動(dòng)態(tài)的添加方法 )和class_replaceMethod(動(dòng)態(tài)的替換方法)
其中class_addMethod 我們?cè)谙?dòng)態(tài)方法的實(shí)現(xiàn),我們用過,class_replaceMethod與他差不多 只不過一個(gè)是添加 一個(gè)是替換
其中replace還可以這樣用:

class_replaceMethod([DGPerson class], @selector(test), imp_implementationWithBlock(^{
        NSLog(@"123455666");
        
    }), "v@:");
    DGPerson *person = [[DGPerson alloc] init];
    [person test];
image.png

4.8 一些方法的相關(guān)的屬性


image.png

4.9 瑣碎內(nèi)容 了解即可


image.png

5 幾個(gè)例子 (工作中用到的runtime的)
  • 1.字典轉(zhuǎn)化模型(我們給nsobject添加一個(gè)分類)
    // 簡單的實(shí)現(xiàn),要做好了 其實(shí)要考慮的類型應(yīng)該還有很多(參考MJExtension)
+ (instancetype)modelWithJson:(NSDictionary *)dic{
    id obj = [[self alloc] init];
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
     // 開始便利
    for (int index = 0; index < count; index ++) {
        Ivar ivar = ivarList[index];
        NSMutableString *nameStr = [[NSMutableString alloc] initWithString:[NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding]];
        [nameStr deleteCharactersInRange:NSMakeRange(0, 1)];
        if ([dic.allKeys containsObject:nameStr]) {
            
            if (dic[nameStr]) { // 不能設(shè)置空的值
             [obj setValue:dic[nameStr] forKey:nameStr];
            }
        }
    }
    // 釋放
    free(ivarList);    
    return obj;
}
    1. 方法的替換,比如我們數(shù)組中添加nil的值就會(huì)crash
      我們可以添加給數(shù)組添加一個(gè)分類,防止插入空的值出現(xiàn)crash
      例如如下的代碼:
    id obj = nil;
    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObject:obj];
image.png

如果我們這樣寫一個(gè)分類

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class cla = NSClassFromString(@"__NSArrayM"); // 這個(gè)要寫類簇
        Method method1 = class_getInstanceMethod(cla, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cla, @selector(DG_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
        
    });
    
}
-(void)DG_insertObject:(id)anObject atIndex:(NSUInteger)index{
    
    if (!anObject) return;
    [self DG_insertObject:anObject atIndex:index];

}

看下運(yùn)行的結(jié)果


image.png

可以看到不crash了 說明我們的方法替換成功了
其中有一個(gè)塊值得說明


image.png

給人的感覺是死循環(huán)了 其實(shí)不是,我們知道m(xù)ethod 其實(shí)就是我們的底層的method_t 他的結(jié)構(gòu)包括
image.png

也就是他把imp這個(gè)實(shí)現(xiàn)給變化了 那么現(xiàn)在的方法指向?yàn)椋?/p>

image.png

所以現(xiàn)在我們?cè)谡{(diào)用-(void)DG_insertObject:(id)anObject atIndex:(NSUInteger)index正好調(diào)用的是系統(tǒng)的方法,所以不會(huì)出現(xiàn)死循環(huán) 反之調(diào)用系統(tǒng)的方法才會(huì)出現(xiàn)死循環(huán)。
  • 3.我們可以設(shè)置關(guān)聯(lián)對(duì)象 objc_setAssociatedObject
    舉個(gè)例子比如我們我們一個(gè)數(shù)組中包含很多的button,然后我們想做的事情是一個(gè)button對(duì)應(yīng)一個(gè)model,其實(shí)有三個(gè)辦法 ,第一我們打一個(gè)tag,第二寫一個(gè)父類的button,給button增加一個(gè)屬性,第三就是就是運(yùn)用運(yùn)行時(shí)動(dòng)態(tài)的設(shè)置關(guān)聯(lián)屬性。
通過btn傳遞兩個(gè)實(shí)例對(duì)象  firstObject和secondObject
UIButton *btn = // create the button
objc_setAssociatedObject(btn, "firstObject", someObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(btn, "secondObject", otherObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
- (void)click:(UIButton *)sender
{
    id first = objc_getAssociatedObject(btn, "firstObject");
    id second = objc_setAssociatedObject(btn, "secondObject");
    // etc.
}

  • 4.獲取所有成員變量或者屬性,這樣的話我們就可以通過kvc來改變一些屬性,比如我們uitextfield的placeholder的顏色,我們就可以通過運(yùn)行時(shí)的方式進(jìn)行修改。
  • 5 看看那些方法沒有實(shí)現(xiàn),(我們都知道如果方法沒有實(shí)現(xiàn)那就crash),所以我們可以在消息轉(zhuǎn)發(fā)的時(shí)候進(jìn)行攔截,打印出來到時(shí)候上報(bào)我們的服務(wù)器
    比如我在上面說到的消息轉(zhuǎn)發(fā)的用處。

總結(jié):

我目前了解的runtime就這些,希望對(duì)大家有幫助。

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

相關(guān)閱讀更多精彩內(nèi)容

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