Runtime

一、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_rw_t

class_ro_t


class_ro_t

method_t


method_t
2.3、isa指針

共用體isa_t

共用體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ā)流程
  • 實(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
最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 對(duì)于從事 iOS 開發(fā)人員來說,所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,799評(píng)論 7 64
  • 前言: 關(guān)于Runtime的資料網(wǎng)上一搜很多,但總是寫的只言片語,不太全面。最近花了一個(gè)星期的時(shí)間重新學(xué)習(xí)Runt...
    小霍同學(xué)閱讀 1,120評(píng)論 0 2
  • 1. runtime的簡(jiǎn)介runtime是一套比較底層的純C語言API, 屬于1個(gè)C語言庫(kù), 包含了很多底層的C語...
    凸阿濱閱讀 393評(píng)論 0 0
  • 在我們的心靈深處 總是有一種力量使我們——茫然不安 讓我們無法安靜,這種力量叫浮躁。 ...
    找個(gè)先生閱讀 261評(píng)論 0 0
  • 生命中唯一重要的事情是愛情和工作---弗洛伊德 我已退休,妻子也于三年前去世了,我非常想念她。對(duì)于退休生活,我一直...
    Honey_Jane閱讀 733評(píng)論 0 0

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