一、runtime簡(jiǎn)介
- RunTime簡(jiǎn)稱運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制。
- 對(duì)于C語言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)。
- 對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來調(diào)用。
- 事實(shí)證明:
- 在編譯階段,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過就不會(huì)報(bào)錯(cuò)。
- 在編譯階段,C語言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)。
二、Runtime數(shù)據(jù)結(jié)構(gòu)

- runtime的數(shù)據(jù)結(jié)構(gòu):
- objc_object,
- isa,
- objc_class,
- superClass
- cache_t,
- class_data_bits_t,
2.1.objc_object

我們平時(shí)使用的所有對(duì)象,都是id類型的,id類型對(duì)象對(duì)應(yīng)到runtime當(dāng)中,代表的就是objc_object結(jié)構(gòu)體
objc_object主要包含以下幾個(gè)成員部分:
1.isa_t:公用體
2.isa操作相關(guān):比如通過objc_object結(jié)構(gòu)體來獲取它的isa所指向的類對(duì)象,包括類對(duì)象的isa獲取它的元類對(duì)象,以及便利方法
3.弱引用相關(guān):
4.關(guān)聯(lián)對(duì)象相關(guān):
5.內(nèi)存管理相關(guān):
2.2、objc_class

objc_class (class) 繼承自objc_object,主要包含以下幾個(gè)成員部分
1.Class superClass:指針,指向objc_object
2.cache_t cache: 方法緩存的一個(gè)結(jié)構(gòu),我們?cè)谶M(jìn)行消息傳遞的過程當(dāng)中會(huì)使用到方法緩存的這個(gè)結(jié)構(gòu)
3.class_data_bits_t bits:變量、屬性、方法
2.2.1、Class superClass:
objc_class擁有一個(gè)superClass指針,所指向的類型也是Class,例如,如果一個(gè)類對(duì)象的話,superClass所指向的就是它的父類對(duì)象,也就是我們平時(shí)說的類與父類的關(guān)系就是通過objc_class中Class superClass這個(gè)成員變量來定義的
2.2.2、cache_t
用于快速查找方法執(zhí)行函數(shù)
是可增量打擴(kuò)展的哈希表結(jié)構(gòu)
是局部性原理的最佳應(yīng)用
cache_t的數(shù)據(jù)結(jié)構(gòu)說明

是一張由bucket_t組成的Hash表
cache_t的成員變量:key,IMP
key對(duì)應(yīng)OC語言中的selecter
IMP可以理解為無類型的函數(shù)指針
struct cache_t {
struct bucket_t *_buckets; // 一個(gè)散列表,用來方法緩存,bucket_t類型,包含key以及方法實(shí)現(xiàn)IMP
mask_t _mask; // 分配用來緩存bucket的總數(shù)
mask_t _occupied; // 表明目前實(shí)際占用的緩存bucket的個(gè)數(shù)
}
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
2.2.3、class_data_bits_t
class_data_bits_t:主要是對(duì)class_rw_t的封裝
class_rw_t:代表了類相關(guān)的讀寫信息、對(duì)class_ro_t的封裝
class_ro_t:代表了類相關(guān)的只讀信息
class_rw_t

class_ro_t

method_t

2.3、isa指針
共用體isa_t

isa指向
對(duì)象,其指向類對(duì)象
類對(duì)象,其指向元類對(duì)象


isa指針是什么含義?
isa指針有指針型的isa和非指針型的isa,指針型isa的值代表class的地址,非指針型isa的值的部分代表class的地址。
isa指向
關(guān)于對(duì)象,其指向類對(duì)象
調(diào)用實(shí)例方法的時(shí)候,實(shí)際是通過對(duì)象的isa指針到對(duì)象的類對(duì)象中進(jìn)行方法查找
關(guān)于類對(duì)象,其指向元類對(duì)象
調(diào)用類方法的時(shí)候,實(shí)際是通過類的isa指針到它的元類對(duì)象中進(jìn)行方法查找class這么一個(gè)類,是否是一個(gè)對(duì)象?
class也是一個(gè)對(duì)象,被稱為類對(duì)象,因?yàn)閏lass是繼承自objc_object的。
主要包含以下幾個(gè)成員部分:
1.class superclass:指向父類
2.cache_t:表達(dá)方法緩存的數(shù)據(jù)結(jié)構(gòu),在消息傳遞的時(shí)候會(huì)用到
3.class_data_bits_t:變量、屬性、方法
三、類對(duì)象與元類對(duì)象
- 對(duì)象、類對(duì)象、元類對(duì)象
類對(duì)象存儲(chǔ)實(shí)例方法列表等信息。
元類對(duì)象存儲(chǔ)類方法列表等信息。

如果我們調(diào)用的一個(gè)類方法沒有對(duì)應(yīng)的實(shí)現(xiàn),但是有同名的實(shí)例方法的實(shí)現(xiàn),這個(gè)時(shí)候會(huì)不會(huì)發(fā)生崩潰?會(huì)不會(huì)產(chǎn)生實(shí)際的調(diào)用?
由于根元類的對(duì)象superclass指針指向根類對(duì)象,當(dāng)我們?cè)谠悓?duì)象中去找類方法列表沒有查找到的時(shí)候,根元類就會(huì)順著isa指針去實(shí)例方法列表中查找,那么如果有同名方法,就會(huì)執(zhí)行同名的實(shí)例方法。實(shí)例方法的消息傳遞過程:
如果調(diào)用了實(shí)例對(duì)象方法,那么系統(tǒng)首先會(huì)根據(jù)當(dāng)前實(shí)例的isa指針找到它的類對(duì)象,然后在它的類對(duì)象當(dāng)中遍歷方法列表,去查找同名的方法實(shí)現(xiàn),如果沒有查找到,就會(huì)順著superclass指針的指向查找父類的類對(duì)象的方法列表,然后如果沒有,再順著superclass指針向根類對(duì)象查找方法列表,還沒有查找到,就會(huì)走到消息的轉(zhuǎn)發(fā)流程。類方法的消息傳遞過程:
如果調(diào)用類方法,就會(huì)通過類對(duì)象的isa指針,找到它的元類對(duì)象遍歷方法列表,去查找同名的方法實(shí)現(xiàn),如果沒有查找到,就會(huì)順著superclass指針的指向查找父元類的元類對(duì)象的方法列表,然后如果沒有,再順著superclass指針向根元類對(duì)象查找方法列表,還沒有查找到,就會(huì)順著superclass指針的指向找到根類(Root class)方法列表,還是找不到,就會(huì)走到消息的轉(zhuǎn)發(fā)流程。
在遍歷類方法和實(shí)例方法當(dāng)中的區(qū)別在于,類對(duì)象方法在查找到根元類對(duì)象方法列表的時(shí)候,最終會(huì)找到根類方法列表。
- 類對(duì)象和元類對(duì)象分別是什么?類對(duì)象和元類對(duì)象之間有什么區(qū)別?
實(shí)例對(duì)象、類對(duì)象、元類、根元類、NSObject之間有這樣一種關(guān)系:
實(shí)例對(duì)象是由類對(duì)象初始化來的
類對(duì)象由元類初始化而來
元類是一種虛擬的類,由系統(tǒng)幫我創(chuàng)建,不用手動(dòng)創(chuàng)建
元類由根元類初始化而來
根元類由NSObject初始化而來
四、消息傳遞
4.1、消息傳遞的第一個(gè)函數(shù)
void objc_msgSend(void /* id self, SEL ор, ...* )
[self class] <--> objc_msgSend(self, @selector(class))
objc_msgSend這個(gè)函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)id類型的self對(duì)象,第二個(gè)參數(shù)是SEL類型的方法選擇器名稱,后面才是消息傳遞的真正的方法參數(shù),對(duì)于任何一個(gè)消息傳遞的[self class]通過編譯器,都會(huì)轉(zhuǎn)換成關(guān)于objc_msgSend(self, @selector(class))這樣的函數(shù)調(diào)用,第一個(gè)參數(shù)是消息傳遞的接收者self,第二個(gè)參數(shù)就是傳遞的消息名稱或者說選擇器,從中可以看出,對(duì)于消息傳遞,實(shí)際上是轉(zhuǎn)化成函數(shù)調(diào)用,這一步驟是發(fā)生在編譯器層面的。
4.2、消息傳遞的第二個(gè)函數(shù)
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */)
其中兩個(gè)固定的參數(shù),第一個(gè)參數(shù)是objc_super結(jié)構(gòu)體類型的super指針,第二個(gè)參數(shù)SEL類型的方法選擇器,后面才是消息傳遞的真正的方法參數(shù)。
struct objc_super {
// Specifies an instance?f a class.
__unsafe_unretained id receiver;
};
objc_super結(jié)構(gòu)體當(dāng)中包含一個(gè)叫receiver的成員變量,這個(gè)接收者實(shí)際上就是當(dāng)前對(duì)象,因?yàn)閟uper這個(gè)關(guān)鍵字實(shí)際上是一個(gè)編譯器關(guān)鍵字,經(jīng)過編譯器編譯之后,它實(shí)際上會(huì)給我們解析成objc_super結(jié)構(gòu)體類型的指針super。這個(gè)結(jié)構(gòu)體中的成員變量receiver就是當(dāng)前對(duì)象。
[super class] <--> objc_msgSendSuper(super, @sletor(class))
[super class]經(jīng)過編譯之后轉(zhuǎn)變成了objc_msgSendSuper(super, @sletor(class)),第一個(gè)參數(shù)是super,第二個(gè)參數(shù)是方法選擇器,這個(gè)super里面實(shí)際上包含了receiver,就是當(dāng)前對(duì)象。
所以,不論是調(diào)用[super class]還是[self class],實(shí)際上,這條消息的接收者都是當(dāng)前對(duì)象。
#import "Mobile.h"
@interface Phone : Mobile
@end
@implementation Phone
- (id)init{
self = [super init];
if (self) {
NSLog (@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
對(duì)于[self class],它會(huì)被轉(zhuǎn)化成objc_msgSend的函數(shù)調(diào)用,objc_msgSend的第一個(gè)參數(shù)就是消息傳遞的接收者self,即當(dāng)前對(duì)象;[super class]會(huì)轉(zhuǎn)化成objc_msgSendSuper的函數(shù)調(diào)用,objc_msgSendSuper的第一個(gè)函數(shù)雖然是super,但是super這個(gè)結(jié)構(gòu)體包著的一個(gè)receiver的當(dāng)前對(duì)象,所以,無論轉(zhuǎn)換成哪個(gè)函數(shù),它們的接收者,都是當(dāng)前對(duì)象。
所以,打印結(jié)果都是Phone。
objc_msgSend和objc_msgSendSuper有什么區(qū)別?
4.3、消息傳遞流程和機(jī)制:

消息傳遞的過程
第一步:緩存查找:
在調(diào)用一個(gè)方法的時(shí)候,先會(huì)查找緩存,看緩存當(dāng)中是否有對(duì)應(yīng)選擇器名稱的方法實(shí)現(xiàn),如果有,那么通過函數(shù)指針調(diào)用函數(shù),完成一次消息傳遞;
第二步:當(dāng)前類查找
如果緩存沒有對(duì)應(yīng)選擇器名稱的方法實(shí)現(xiàn),那么會(huì)根據(jù)當(dāng)前實(shí)例的isa指針去查找當(dāng)前類對(duì)象的方法列表看是否有同樣名稱的方法,如果找到,那么通過函數(shù)指針調(diào)用函數(shù),結(jié)束消息傳遞流程。當(dāng)前類查找中,對(duì)于已排序好的列表,采用二分查找算法查找方法對(duì)應(yīng)執(zhí)行函數(shù)。對(duì)于沒有排序的列表,采用一般遍歷查找方法對(duì)應(yīng)執(zhí)行函數(shù)。
第三步:逐級(jí)父類查找
如果當(dāng)前類方法列表當(dāng)中沒有對(duì)應(yīng)名稱的方法,那么就會(huì)逐級(jí)父類方法列表當(dāng)中查找,那么這個(gè)過程,實(shí)際上就是通過當(dāng)前類對(duì)象的superclass指針去查找它的父類的方法列表,如果在它的父類方法列表當(dāng)中沒有查到,就會(huì)根據(jù)父類的superclass指針再往上查找,直到nil為止。如果在某一個(gè)父類的方法列表當(dāng)中查找到了選擇器同名的方法,那么根據(jù)函數(shù)指針去調(diào)用這個(gè)函數(shù)的實(shí)現(xiàn),然后結(jié)束消息傳遞流程。
第四步:消息轉(zhuǎn)發(fā)
如果在父類方法列表當(dāng)中,一直查到根類對(duì)象,比如NSObject,仍然沒有查找到同名的方法的實(shí)現(xiàn),那么就會(huì)進(jìn)入轉(zhuǎn)發(fā)流程,然后結(jié)束消息傳遞流程。方法緩存查找:哈希查找
在緩存查找方法過程當(dāng)中,實(shí)際上就是根據(jù)給定的選擇器,來查找它對(duì)應(yīng)的方法實(shí)現(xiàn),我們給定的選擇器因子,就是要到bucket_t數(shù)組當(dāng)中把對(duì)應(yīng)的bucket_t給找出來。
這個(gè)過程,大概是這樣的,首先,我們根據(jù)給定的方法選擇器,通過一個(gè)函數(shù)來映射出對(duì)應(yīng)的bucket_t在數(shù)組中的位置,這一步驟,其實(shí)就是哈希查找。哈希查找,就是通過我們給定的一個(gè)值,比如這個(gè)方法選擇器,然后經(jīng)過哈希函數(shù)的算法,算出這個(gè)值,實(shí)際上就是這個(gè)給定值在對(duì)應(yīng)數(shù)組當(dāng)中對(duì)應(yīng)的索引位置,我們通過這個(gè)哈希函數(shù)在緩存查找當(dāng)中,這個(gè)哈希查找表達(dá)式實(shí)際上就是根據(jù)選擇器因子和一個(gè)對(duì)應(yīng)的mask做位與操作來進(jìn)行計(jì)算對(duì)應(yīng)的bucket_t在數(shù)組當(dāng)中的索引位置,這個(gè)mask實(shí)際上也是bucket_t結(jié)構(gòu)體的成員變量,通過這個(gè)哈希函數(shù)算法,就可以找到給定選擇器因子所對(duì)應(yīng)函數(shù)的實(shí)現(xiàn)在數(shù)組列表中的索引位置。使用哈希查找的關(guān)鍵就是解決查找效率的問題。查找到選擇器因子所對(duì)應(yīng)bucket_t之后就可以提取它對(duì)應(yīng)的IMP函數(shù)指針,然后返回給調(diào)用方就可以了。當(dāng)前類查找
對(duì)于已排序好的列表,采用二分查找算法查找方法對(duì)應(yīng)執(zhí)行函數(shù)。
對(duì)于沒有排序的列表,采用一般遍歷查找方法對(duì)應(yīng)執(zhí)行函數(shù)。-
父類逐級(jí)查找
逐級(jí)父類方法查找,就是根據(jù)superclass指針,一級(jí)一級(jí)往上查找,查找它的父類,遍歷每個(gè)父類,對(duì)于每個(gè)父類中的方法查找也是同樣兩個(gè)步驟,首先查找父類的緩存,緩存中沒有,再查找父類對(duì)應(yīng)的方法列表,已排序好的列表,采用二分查找算法查找,沒有排序的列表,采用一般遍歷查找方法查找。
父類逐級(jí)查找 消息傳遞流程三大特點(diǎn):
1.緩存查找:哈希查找
2.當(dāng)前類查找:對(duì)于已排序好的列表,采用二分查找算法查找方法對(duì)應(yīng)執(zhí)行函數(shù),對(duì)于沒有排序的列表,采用一般遍歷查找方法對(duì)應(yīng)執(zhí)行函數(shù)
3.父類逐級(jí)查找:根據(jù)superclass指針,一級(jí)一級(jí)往上查找,查找它的父類,遍歷每個(gè)父類,對(duì)于每個(gè)父類中的方法查找也是同樣兩個(gè)步驟,首先查找父類的緩存,緩存中沒有,再查找父類對(duì)應(yīng)的方法列表,已排序好的列表,采用二分查找算法查找,沒有排序的列表,采用一般遍歷查找方法查找。
4.4、消息轉(zhuǎn)發(fā)
4.4.1、實(shí)例方法的消息轉(zhuǎn)發(fā)流程

- 實(shí)例方法的消息轉(zhuǎn)發(fā)流程
第一次機(jī)會(huì):
首先系統(tǒng)會(huì)回調(diào)一個(gè)resolveInstanceMethod方法(如果是類方法,回調(diào)的是resolveClassMethod方法),resolveInstanceMethod方法有一個(gè)參數(shù),是方法的選擇器,也就是SEL類型的參數(shù),返回值是一個(gè)BOOL類型的,相當(dāng)于告訴系統(tǒng),我們要不要解決當(dāng)前實(shí)例方法的實(shí)現(xiàn),這個(gè)方法是一個(gè)類方法,不是實(shí)例方法,要注意,如果這一步返回的是YES,或者說我們給予的這個(gè)方法選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)了,相當(dāng)于通知系統(tǒng)當(dāng)前消息已處理,然后結(jié)束消息轉(zhuǎn)發(fā)流程;
第二次機(jī)會(huì):
如果resolvelnstanceMethod方法返回NO的話,系統(tǒng)會(huì)給我們第二次機(jī)會(huì)來處理這條消息,這個(gè)時(shí)候會(huì)回調(diào)forwardingTargetForSelector:方法,同樣這個(gè)方法的參數(shù)也是SEL類型的方法選擇器,返回值是一個(gè)id類型的,相當(dāng)于告訴系統(tǒng),這個(gè)選擇器,或者說這次實(shí)例方法的調(diào)用,應(yīng)該由哪個(gè)對(duì)象來處理,轉(zhuǎn)發(fā)對(duì)象是誰,如果我們指定了一個(gè)轉(zhuǎn)發(fā)目標(biāo)的話,系統(tǒng)會(huì)把這條消息轉(zhuǎn)發(fā)給我們返回的轉(zhuǎn)發(fā)目標(biāo),同時(shí)會(huì)結(jié)束當(dāng)前消息的轉(zhuǎn)發(fā)流程。
第三次機(jī)會(huì):
如果說在第二次機(jī)會(huì)的時(shí)候,我們?nèi)匀粵]有給它返回一個(gè)轉(zhuǎn)發(fā)目標(biāo)的情況下,系統(tǒng)會(huì)給我們第三次處理這條消息的機(jī)會(huì),也是最后一次機(jī)會(huì),首先系統(tǒng)會(huì)調(diào)用methodSignatureForSelector:方法,這個(gè)方法的參數(shù)仍然是是SEL類型的方法選擇器,方法的返回值是一個(gè)methodSignature對(duì)象,這個(gè)對(duì)象實(shí)際上是對(duì)于這個(gè)方法選擇器的返回值的的類型以及它的參數(shù)個(gè)數(shù)和參數(shù)類型的一個(gè)封裝,此時(shí),如果返回了一個(gè)方法簽名的話,系統(tǒng)會(huì)調(diào)用forwardInvocation:,如果forwardInvocation能夠處理這條消息的話,消息轉(zhuǎn)發(fā)流程就結(jié)束,如果methodSignatureForSelector方法返回的是一個(gè)nil,或者forwardInvocation沒有辦法處理這個(gè)消息的話,就會(huì)標(biāo)記為消息無法處理。
測(cè)試:
第一步:創(chuàng)建一個(gè)RuntimeObject類
RuntimeObject.h
#import <Foundation/Foundation.h>
@interface RuntimeObject : NSObject
- (void)test;
@end
RuntimeObject.m
#import "RuntimeObject.h"
@implementation RuntimeObject
//第一個(gè)函數(shù)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 如果是test方法 打印日志
if (sel == @selector(test)) {
NSLog(@"resolveInstanceMethod:");
return NO;
} else {
// 返回父類的默認(rèn)調(diào)用
return [super resolveInstanceMethod:sel];
}
}
// 第二個(gè)函數(shù)
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector:");
return nil;
}
// 第三個(gè)函數(shù)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
NSLog(@"methodSignatureForSelector:");
// v 代表返回值是void類型的 @代表第一個(gè)參數(shù)類型時(shí)id,即self
// : 代表第二個(gè)參數(shù)是SEL類型的 即@selector(test)
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
} else {
return [super methodSignatureForSelector:aSelector];
}
}
// 第四個(gè)函數(shù)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation:");
}
@end
第一步:編寫測(cè)試代碼
#import "ViewController.h"
#import "RuntimeObject.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RuntimeObject *obj = [[RuntimeObject alloc] init];
// 調(diào)用test方法,只聲明,沒有實(shí)現(xiàn)
[obj test];
}
@end
打印結(jié)果
2020-07-06 01:06:33.735960+0800 RuntimeTest[46347:3419335] resolveInstanceMethod:
2020-07-06 01:06:33.736042+0800 RuntimeTest[46347:3419335] forwardingTargetForSelector:
2020-07-06 01:06:33.736108+0800 RuntimeTest[46347:3419335] methodSignatureForSelector:
2020-07-06 01:06:33.736206+0800 RuntimeTest[46347:3419335] forwardInvocation:
五、runtime作用
1.發(fā)送消息
2.交換方法
3.動(dòng)態(tài)添加方法
4.動(dòng)態(tài)方法解析
5.給分類添加屬性
6.字典轉(zhuǎn)模型
1.發(fā)送消息
- 方法調(diào)用的本質(zhì),就是讓對(duì)象發(fā)送消息。
- objc_msgSend,只有對(duì)象才能發(fā)送消息,因此以objc開頭.
- 使用消息機(jī)制前提,必須導(dǎo)入#import <objc/message.h>
- 消息機(jī)制簡(jiǎn)單使用
// Person.h
// Runtime(消息機(jī)制)
#import <Foundation/Foundation.h>
@interface Person : NSObject
+ (void)eat;
- (void)run:(int)age;
- (void)eat;
@end
// Person.m
// Runtime(消息機(jī)制)
#import "Person.h"
@implementation Person
- (void)eat {
NSLog(@"對(duì)象方法-吃東西");
}
+ (void)eat {
NSLog(@"類方法-吃東西");
}
- (void)run:(int)age {
NSLog(@"%d",age);
}
@end
// ViewController.m
// Runtime(消息機(jī)制)
#import "ViewController.h"
#import "Person.h"
// 運(yùn)行時(shí)使用運(yùn)行時(shí)的步驟
// 第一步:導(dǎo)入<objc/message.h>
// 第二步:Build Setting -> 搜索msg -> 設(shè)置屬性為No
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建person對(duì)象
Person *p = [[Person alloc] init];
// 調(diào)用對(duì)象方法
[p eat];
// OC:運(yùn)行時(shí)機(jī)制,消息機(jī)制是運(yùn)行時(shí)機(jī)制最重要的機(jī)制
// 消息機(jī)制:任何方法調(diào)用,本質(zhì)都是發(fā)送消息
// SEL:方法編號(hào),根據(jù)方法編號(hào)就可以找到對(duì)應(yīng)方法實(shí)現(xiàn)
// performSelector:動(dòng)態(tài)添加方法
[p performSelector:@selector(eat)];
// 運(yùn)行時(shí),發(fā)送消息,誰做事情就那誰
// xcode5之后,蘋果不建議使用底層方法
// xcode5之后,使用運(yùn)行時(shí).
// 讓p發(fā)送消息
// 不帶參數(shù)
objc_msgSend(p, @selector(eat));
// 帶參數(shù)
objc_msgSend(p, @selector(run:),10);
// 調(diào)用類方法的方式:兩種
// 第一種通過類名調(diào)用,類名調(diào)用類方法,本質(zhì)類名轉(zhuǎn)換成類對(duì)象
[Person eat];
// 第二種通過類對(duì)象調(diào)用
[[Person class] eat];
// 獲取類對(duì)象
Class personClass = [Person class];
[personClass performSelector:@selector(eat)];
// 運(yùn)行時(shí)
// 用類名調(diào)用類方法,底層會(huì)自動(dòng)把類名轉(zhuǎn)換成類對(duì)象調(diào)用
// 本質(zhì):讓類對(duì)象發(fā)送消息
objc_msgSend(personClass, @selector(eat));
}
@end
-
消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)
2.交換方法
- 開發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。
- 方式一:繼承系統(tǒng)的類,重寫方法.
- 方式二:使用runtime,交換方法.
// UIImage+Image.h
// Runtime(交換方法)
#import <UIKit/UIKit.h>
@interface UIImage (Image)
+ (__kindof UIImage *)ge_imageNamed:(NSString *)imageName;
@end
// UIImage+Image.m
// Runtime(交換方法)
#import "UIImage+Image.h"
#import <objc/message.h>
@implementation UIImage (Image)
// 不能在分類中重寫系統(tǒng)方法imageNamed,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.
// 在分類里面不能調(diào)用super,分類木有父類
//+ (UIImage *)imageNamed:(NSString *)name
//{
// [super im]
//}
// 利用運(yùn)行時(shí)
// 先寫一個(gè)其他方法,實(shí)現(xiàn)這個(gè)功能
// 既能加載圖片又能打印
+ (UIImage *)ge_imageNamed:(NSString *)imageName
{
// 1.加載圖片
UIImage *image = [UIImage ge_imageNamed:imageName];
// 2.判斷功能
if (image == nil) {
NSLog(@"加載image為空");
}
return image;
}
// 加載這個(gè)分類的時(shí)候調(diào)用
+ (void)load
{
// 交換方法實(shí)現(xiàn),方法都是定義在類里面
// class_getMethodImplementation:獲取方法實(shí)現(xiàn)
// class_getInstanceMethod:獲取對(duì)象
// class_getClassMethod:獲取類方法
// IMP:方法實(shí)現(xiàn)
// imageNamed
// Class:獲取哪個(gè)類方法
// SEL:獲取方法編號(hào),根據(jù)SEL就能去對(duì)應(yīng)的類找方法
Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
// ge_imageNamed
Method ge_imageNamedMethod = class_getClassMethod([UIImage class], @selector(ge_imageNamed:));
// 交換方法實(shí)現(xiàn)
method_exchangeImplementations(imageNameMethod, ge_imageNamedMethod);
}
@end
// ViewController.m
// Runtime(交換方法)
#import "ViewController.h"
//#import "UIImage+Image.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:先搞個(gè)分類,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn),就能調(diào)用imageWithName,間接調(diào)用imageWithName的實(shí)現(xiàn)。
// UIImage *image = [UIImage imageNamed:@"123"];
// 不好的地方
// 1.每次使用,都需要導(dǎo)入頭文件
// 2.當(dāng)一個(gè)項(xiàng)目開發(fā)太久,使用這個(gè)方式不靠譜
// imageNamed:
// 實(shí)現(xiàn)方法:底層調(diào)用ge_imageNamed
// 本質(zhì):交換兩個(gè)方法的實(shí)現(xiàn)imageNamed和ge_imageNamed方法
// 調(diào)用imageNamed其實(shí)就是調(diào)用ge_imageNamed
// 系統(tǒng)imageNamed加載圖片,并不知道圖片是否加載成功
// 交換以后調(diào)用imageNamed的時(shí)候,就知道圖片是否加載
[UIImage imageNamed:@"123"];
}
@end
- 交換原理:
-
交換之前:
-
交換之前:
-
交換之前:
3.動(dòng)態(tài)添加方法
- 開發(fā)使用場(chǎng)景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。
- 經(jīng)典面試題:有沒有使用performSelector,其實(shí)主要想問你有沒有動(dòng)態(tài)添加過方法。
- 簡(jiǎn)單使用
// Person.h
// Runtime(動(dòng)態(tài)添加方法)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
// Person.m
// Runtime(動(dòng)態(tài)添加方法)
#import "Person.h"
#import <objc/message.h>
@implementation Person
// 動(dòng)態(tài)添加方法,首先實(shí)現(xiàn)這個(gè)resolveInstanceMethod
// resolveInstanceMethod調(diào)用:當(dāng)調(diào)用了沒有實(shí)現(xiàn)的方法沒有實(shí)現(xiàn)就會(huì)調(diào)用resolveInstanceMethod
// resolveInstanceMethod作用:就知道哪些方法沒有實(shí)現(xiàn),從而動(dòng)態(tài)添加方法
// sel:沒有實(shí)現(xiàn)方法
// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過來.
// 剛好可以用來判斷,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// NSLog(@"%@",NSStringFromSelector(sel));
// 動(dòng)態(tài)添加eat方法
if (sel == @selector(eat:)) {
// 第一個(gè)參數(shù):cls:給哪個(gè)類添加方法
// 第二個(gè)參數(shù):SEL:添加方法的方法編號(hào)是什么
// 第三個(gè)參數(shù):IMP:方法實(shí)現(xiàn),函數(shù)入口,函數(shù)名
// 第四個(gè)參數(shù):types:方法類型
// v 表示 void
// @ 表示對(duì)象
// : 表示SEL
class_addMethod(self, sel, (IMP)aaaa, "v@:@");
// 處理完
return YES;
}
// 返回系統(tǒng)的方法,因?yàn)樯厦娓牧?,盡量不要修改系統(tǒng)的方法
return [super resolveInstanceMethod:sel];
}
// 默認(rèn)一個(gè)方法都有兩個(gè)參數(shù),self,_cmd,屬于隱式參數(shù)
// self:方法調(diào)用者
// _cmd:調(diào)用方法的編號(hào)
// 定義函數(shù)
// 沒有返回值,參數(shù)(id,SEL)
// void(id,SEL)
void aaaa(id self, SEL _cmd, id param1)
{
NSLog(@"調(diào)用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}
@end
// ViewController.m
// Runtime(動(dòng)態(tài)添加方法)
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// performSelector:動(dòng)態(tài)添加方法
Person *p = [[Person alloc] init];
// 默認(rèn)person,沒有實(shí)現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)。
// 通過運(yùn)行時(shí)后,動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
// 動(dòng)態(tài)添加方法
// 不帶參數(shù)
[p performSelector:@selector(eat)];
// 帶參數(shù)
[p performSelector:@selector(eat:) withObject:@111];
}
@end
4.給分類添加屬性
- 原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。
// NSObject+Objc.h
// Runtime(分類添加屬性)
#import <Foundation/Foundation.h>
@interface NSObject (Objc)
// @property:只會(huì)生成set方法的聲明,不會(huì)實(shí)現(xiàn)
@property (nonatomic, strong) NSString *name;
@end
// NSObject+Objc.m
// Runtime(分類添加屬性)
#import "NSObject+Objc.h"
#import <objc/message.h>
@implementation NSObject (Objc)
// 定義關(guān)聯(lián)的key
//static NSString *_name;
// set方法
- (void)setName:(NSString *)name {
// 添加屬性,跟對(duì)象
// 給某個(gè)對(duì)象產(chǎn)生關(guān)聯(lián),添加屬性
// 第一個(gè)參數(shù):object:給哪個(gè)對(duì)象添加屬性
// 第二個(gè)參數(shù):key:屬性名,根據(jù)key去獲取關(guān)聯(lián)的對(duì)象 ,void * == id
// 第三個(gè)參數(shù):value:關(guān)聯(lián)的值
// 第四個(gè)參數(shù):policy:緩存策略
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// get方法
- (NSString *)name {
// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, @"name");
}
@end
// ViewController.m
// Runtime(分類添加屬性)
#import "ViewController.h"
#import "NSObject+Objc.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSObject *objc = [[NSObject alloc] init];
objc.name = @"123";
NSLog(@"%@",objc.name);
}
@end
5.字典轉(zhuǎn)模型
- 設(shè)計(jì)模型:字典轉(zhuǎn)模型的第一步
- 模型屬性,通常需要跟字典中的key一一對(duì)應(yīng)
- 問題:一個(gè)一個(gè)的生成模型屬性,很慢?
- 需求:能不能自動(dòng)根據(jù)一個(gè)字典,生成對(duì)應(yīng)的屬性。
- 解決:提供一個(gè)分類,專門根據(jù)字典生成對(duì)應(yīng)的屬性字符串。
// NSObject+Property.h
// 自動(dòng)生成屬性代碼
// 通過解析字典自動(dòng)生成屬性代碼
#import <Foundation/Foundation.h>
@interface NSObject (Property)
/// 通過解析字典自動(dòng)生成屬性代碼
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict;
@end
// NSObject+Property.m
// 自動(dòng)生成屬性代碼
// 通過解析字典自動(dòng)生成屬性代碼
#import "NSObject+Property.h"
@implementation NSObject (Property)
/// 通過解析字典自動(dòng)生成屬性代碼
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict {
// 拼接屬性字符串代碼
NSMutableString *strM = [NSMutableString string];
/*********************** 方法1 ***************************/
// 遍歷字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull propertyName, id _Nonnull value, BOOL * _Nonnull stop) {
// NSLog(@"%@ %@",propertyName,[value class]);
// 類型經(jīng)常變,抽出來
NSString *code;
if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, copy) NSString *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
;
}
// 每生成屬性字符串,就自動(dòng)換行。
[strM appendFormat:@"\n%@\n",code];
}];
/*********************** 方法2 ***************************/
// 1.遍歷字典,把字典中的所有key取出來,生成對(duì)應(yīng)的屬性代碼
// [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// // 類型經(jīng)常變,抽出來
// NSString *type;
// if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
// type = @"NSString";
// }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
// type = @"NSArray";
// }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
// type = @"int";
// }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
// type = @"NSDictionary";
// }
// // 屬性字符串
// NSString *str;
// if ([type containsString:@"NS"]) {
// str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
// }else{
// str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
// }
// // 每生成屬性字符串,就自動(dòng)換行。
// [strM appendFormat:@"\n%@\n",str];
// }];
// 把拼接好的字符串打印出來,就好了。
NSLog(@"strM = %@",strM);
}
@end
// ViewController.m
// Runtime(自動(dòng)生成屬性代碼)
#import "ViewController.h"
#import "NSObject+Property.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 解析Plist
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *dictArr = dict[@"statuses"];
// 設(shè)計(jì)模型屬性代碼,生成打印后賦值粘貼到模型即可
[NSObject createPropertyCodeWithDict:dictArr[0]];
}
@end
- 字典轉(zhuǎn)模型的方式一:KVC
// Status.m
// 字典轉(zhuǎn)模型KVC實(shí)現(xiàn)
#import "Status.h"
@implementation Status
// 字典轉(zhuǎn)模型 - 模型的屬性名跟字典一一對(duì)應(yīng)
+ (Status *)statusWithDict:(NSDictionary *)dict {
Status *status = [[self alloc] init];
// KVC
[status setValuesForKeysWithDictionary:dict];
return status;
}
@end
- KVC字典轉(zhuǎn)模型弊端:必須保證,模型中的屬性和字典中的key一一對(duì)應(yīng)。
- 如果不一致,就會(huì)調(diào)用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:] 報(bào)key找不到的錯(cuò)。
- 分析:模型中的屬性和字典的key不一一對(duì)應(yīng),系統(tǒng)就會(huì)調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)。
- 解決:重寫對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋, 就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。
// Status.m
// 轉(zhuǎn)模型KVC實(shí)現(xiàn)
#import "Status.h"
@implementation Status
// 字典轉(zhuǎn)模型 - 模型的屬性名跟字典一一對(duì)應(yīng)
+ (Status *)statusWithDict:(NSDictionary *)dict
{
Status *status = [[self alloc] init];
// KVC
[status setValuesForKeysWithDictionary:dict];
return status;
}
// 解決KVC報(bào)錯(cuò)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"id"]) {
_ID = [value integerValue];
}
// key:沒有找到key
// value:沒有找到key對(duì)應(yīng)的值
NSLog(@"沒有找到key = %@,沒有找到key對(duì)應(yīng)的值 = %@",key,value);
}
@end
// ViewController.m
// Runtime(字典轉(zhuǎn)模型KVC實(shí)現(xiàn))
#import "ViewController.h"
#import "NSObject+Property.h"
#import "Status.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 解析Plist
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *dictArr = dict[@"statuses"];
// 設(shè)計(jì)模型屬性代碼,生成打印后賦值粘貼到模型即可
// [NSObject createPropertyCodeWithDict:dictArr[0]];
NSMutableArray *statuses = [NSMutableArray array];
for (NSDictionary *dict in dictArr) {
// 字典轉(zhuǎn)模型
Status *status = [Status statusWithDict:dict];
[statuses addObject:status];
}
NSLog(@"%@",statuses);
}
@end
- 字典轉(zhuǎn)模型的方式二:Runtime
- 思路:利用運(yùn)行時(shí),遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對(duì)應(yīng)的值,給模型的屬性賦值。
- 步驟:提供一個(gè)NSObject分類,專門字典轉(zhuǎn)模型,以后所有模型都可以通過這個(gè)分類轉(zhuǎn)。
// User.h
// Runtime(字典轉(zhuǎn)模型)
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *profile_image_url;
@property (nonatomic, assign) BOOL vip;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int mbrank;
@property (nonatomic, assign) int mbtype;
@end
// User.m
// Runtime(字典轉(zhuǎn)模型)
#import "User.h"
@implementation User
@end
// Status.h
// Runtime(字典轉(zhuǎn)模型)
#import <Foundation/Foundation.h>
@class User;
@interface Status : NSObject
// 寫一段程序自動(dòng)生成屬性代碼
@property (nonatomic, assign) NSInteger ID;
// 解析字典自動(dòng)生成屬性代碼
@property (nonatomic, strong) NSString *source;
@property (nonatomic, assign) NSInteger reposts_count;
@property (nonatomic, strong) NSArray *pic_urls;
@property (nonatomic, strong) NSString *created_at;
@property (nonatomic, assign) int attitudes_count;
@property (nonatomic, strong) NSString *idstr;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, assign) int comments_count;
@property (nonatomic, strong) User *user;
@property (nonatomic, strong) NSDictionary *retweeted_status;
@end
// Status.m
// Runtime(字典轉(zhuǎn)模型)
#import "Status.h"
@implementation Status
@end
// NSObject+Model.h
// Runtime(字典轉(zhuǎn)模型)
// Runtime字典轉(zhuǎn)模型分類
#import <Foundation/Foundation.h>
@interface NSObject (Model)
+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end
// NSObject+Model.m
// Runtime(字典轉(zhuǎn)模型)
// Runtime字典轉(zhuǎn)模型分類
#import "NSObject+Model.h"
#import <objc/message.h>
/*
Ivar ivar1;
Ivar ivar2;
Ivar ivar3;
Ivar a[] = {ivar3,ivar1,ivar2};
Ivar *ivar = &a;
*/
@implementation NSObject (Model)
+ (instancetype)modelWithDict:(NSDictionary *)dict{
// 1.創(chuàng)建對(duì)應(yīng)類的對(duì)象
id objc = [[self alloc] init];
// 2.利用runtime給對(duì)象中的成員屬性賦值
// runtime:遍歷模型中所有成員屬性,去字典中查找
// 屬性定義在哪,定義在類,類里面有個(gè)屬性列表(數(shù)組)
// 遍歷模型所有成員屬性
// ivar:成員屬性
// class_copyIvarList:把成員屬性列表復(fù)制一份給你
// Ivar *:指向Ivar指針
// Ivar *:指向一個(gè)成員變量數(shù)組
// class:獲取哪個(gè)類的成員屬性列表
// count:成員屬性總數(shù)
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0 ; i < count; i++) {
// 獲取成員屬性
Ivar ivar = ivarList[I];
// 獲取成員名
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲取key
NSString *key = [propertyName substringFromIndex:1];
// user value:字典
// 獲取字典的value
id value = dict[key];
// 給模型的屬性賦值
// value:字典的值
// key:屬性名
// 獲取成員屬性類型
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// user:NSDictionary
// 二級(jí)轉(zhuǎn)換
// 值是字典,成員屬性的類型不是字典,才需要轉(zhuǎn)換成模型
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
// 字典轉(zhuǎn)模型
// 獲取模型的類對(duì)象,調(diào)用modelWithDict
// 模型的類名已知,就是成員屬性的類型
// 需要字典轉(zhuǎn)換成模型
// 轉(zhuǎn)換成哪個(gè)類型
// NSLog(@"轉(zhuǎn)換成哪個(gè)類型 = %@",propertyType);
/*********** *********** 字符串截取 *********** ***********/
// 字符串截取
// 生成的是這種@"@\"User\"" 類型 -> @"User" 在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思,不占用字符
// @"@\"User\"" User
// \":算一個(gè)字符
NSRange range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location + range.length];
// User\"";
// 裁剪到哪個(gè)角標(biāo),不包括當(dāng)前角標(biāo)
range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringToIndex:range.location];
/*********** *********** 字符串截取 *********** ***********/
// 獲取需要轉(zhuǎn)換類的類對(duì)象
// 根據(jù)字符串類名生成類對(duì)象
Class modelClass = NSClassFromString(propertyType);
// 有對(duì)應(yīng)的模型才需要轉(zhuǎn)
if (modelClass) {
// 字典轉(zhuǎn)模型
value = [modelClass modelWithDict:value];
}
}
// // 三級(jí)轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型.
// // 判斷值是否是數(shù)組
// if ([value isKindOfClass:[NSArray class]]) {
// // 判斷對(duì)應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
// if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// // 轉(zhuǎn)換成id類型,就能調(diào)用任何對(duì)象的方法
// id idSelf = self;
// // 獲取數(shù)組中字典對(duì)應(yīng)的模型
// NSString *type = [idSelf arrayContainModelClass][key];
// // 生成模型
// Class classModel = NSClassFromString(type);
// NSMutableArray *arrM = [NSMutableArray array];
// // 遍歷字典數(shù)組,生成模型數(shù)組
// for (NSDictionary *dict in value) {
// // 字典轉(zhuǎn)模型
// id model = [classModel modelWithDict:dict];
// [arrM addObject:model];
// }
// // 把模型數(shù)組賦值給value
// value = arrM;
// }
// }
// 有值,才需要給模型的屬性賦值
if (value) {
// KVC賦值:不能傳空
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
// ViewController.m
// Runtime(字典轉(zhuǎn)模型)
#import "ViewController.h"
#import "Status.h"
#import "User.h"
#import "NSObject+Model.h"
@interface ViewController ()
@end
@implementation ViewController
/*
KVC:遍歷字典中所有key,去模型中查找有沒有對(duì)應(yīng)的屬性名,沒有對(duì)應(yīng)的key就會(huì)報(bào)錯(cuò)
runtime:遍歷模型中所有屬性名,去字典中查找,這樣不會(huì)像KVC那樣報(bào)錯(cuò)
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 解析Plist
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *dictArr = dict[@"statuses"];
NSMutableArray *statuses = [NSMutableArray array];
// 遍歷字典數(shù)組
for (NSDictionary *dict in dictArr) {
// Runtime(字典轉(zhuǎn)模型)
Status *status = [Status modelWithDict:dict];
[statuses addObject:status];
User *user = status.user;
NSString *name = user.name;
NSLog(@"name = %@",name);
}
// NSLog(@"%@",statuses);
}
@end



