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

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

可以看到如下的結(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)存地址

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

沒有設(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了

其他的不一一驗(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)如圖所示:

我們重點(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>

當(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é)果為:

-
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是什么?

可以看到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)表

- 重點(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),查看效果




可以看到 開始分配的數(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);
}

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

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

由此可見以上所說的結(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é)果是:

可以看到真是我們的的方法名字(但是這樣打印是不準(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(@"+++++++++++++");

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

相當(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è)簡單的流程圖是這樣的

也就是說消息的一個(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ì)打印如下的方法

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é)果為:

其中第一和第二都能很簡單的知道為什么,但是第三和第四是為什么
按照我的個(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))

整理得到:
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)原理

通過注釋中可以發(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(@"---------------");
}
我們打印可以看出

可以看到我們按照8個(gè)字節(jié)往上找,我們找打了super viewdidload的底層實(shí)現(xiàn),我們知道他的大概實(shí)現(xiàn)為:
struct ss = {
id self,
[ViewController class]
};
objc_MegSendSuper(ss,sel_registerName("viewDidLoad"));

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

所以我們獲取第三個(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
可以看到如下代碼:


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é)果

為了弄清除這個(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é)果為:

先看下我們的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,我們以前說過這樣的一幅圖

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

runtime的運(yùn)用(個(gè)人覺得很重要)
- 類的相關(guān)的api
1.1 object_setClass :切換isa的指向
- 類的相關(guān)的api
DGPerson *person = [[DGPerson alloc] init];
[person test];
// 將person的isa指針指向DGCar
object_setClass(person, [DGCar class]);
[person test];

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));

解釋:值得說明的是原類對(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);

- 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);

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);

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);

- 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);

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);

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];

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];

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];

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

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

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;
}
- 方法的替換,比如我們數(shù)組中添加nil的值就會(huì)crash
我們可以添加給數(shù)組添加一個(gè)分類,防止插入空的值出現(xiàn)crash
例如如下的代碼:
- 方法的替換,比如我們數(shù)組中添加nil的值就會(huì)crash
id obj = nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:obj];

如果我們這樣寫一個(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é)果

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

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

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

所以現(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ì)大家有幫助。



