iOS runtime 知識(shí)點(diǎn)總結(jié)

1.說(shuō)說(shuō)OC的消息機(jī)制?

OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)

objc_msgSend底層有3大階段

消息發(fā)送(當(dāng)前類(lèi)、父類(lèi)中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)

2. 什么是Runtime?平時(shí)項(xiàng)目中有用過(guò)么?

OC是一門(mén)動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行

OC的動(dòng)態(tài)性就是由Runtime來(lái)支撐和實(shí)現(xiàn)的,Runtime是一套C語(yǔ)言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)

平時(shí)編寫(xiě)的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用

3.與runtime打交道主要有三種方式:

(1)平常使用OC寫(xiě)代碼,當(dāng)代碼中使用到OC的類(lèi)與方法,runtime系統(tǒng)其實(shí)已經(jīng)在隱式的被使用著。

(2)使用NSObject的某些方法如:isKindOfClass:、isMemberOfClass:等等的這些接口時(shí),就是顯示使用runtime接口提供的功能。

(3)runtime提供的一套C語(yǔ)言API。

4.runtime具體應(yīng)用

利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類(lèi)添加屬性

遍歷類(lèi)的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)

交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)

利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題

5.runtime 常見(jiàn)作用

動(dòng)態(tài)交換兩個(gè)方法的實(shí)現(xiàn)?

動(dòng)態(tài)添加屬性?

實(shí)現(xiàn)字典轉(zhuǎn)模型的自動(dòng)轉(zhuǎn)換?

發(fā)送消息?

動(dòng)態(tài)添加方法?

攔截并替換方法?

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

6.runtime 方法調(diào)用流程「消息機(jī)制」

1.OC 在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime 庫(kù)會(huì)根據(jù)對(duì)象的 isa指針找到該對(duì)象對(duì)應(yīng)的類(lèi)或其父類(lèi)中查找方法。。

2.注冊(cè)方法編號(hào)(這里用方法編號(hào)的好處,可以快速查找)。

3.根據(jù)方法編號(hào)去查找對(duì)應(yīng)方法。

4.找到只是最終函數(shù)實(shí)現(xiàn)地址,根據(jù)地址去方法區(qū)調(diào)用對(duì)應(yīng)函數(shù)。

每一個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針,這個(gè)指針是指向它的真實(shí)類(lèi)型,根據(jù)這個(gè)指針就能知道將來(lái)調(diào)用哪個(gè)類(lèi)的方法。

7.isa?

在oc里面有一個(gè)叫 isa 的指針,它指向元類(lèi),元類(lèi)的 isa 又會(huì)指向根元類(lèi),最終指向了 NSObject 的元類(lèi),這樣形成一個(gè)環(huán)。所謂元類(lèi),就是類(lèi)對(duì)象,例如類(lèi)A,實(shí)例化出一個(gè)對(duì)象 a,那么對(duì)象a的isa指針就是指向 A,類(lèi) A 本身也是一個(gè)對(duì)象,叫類(lèi)對(duì)象。



打開(kāi)objc/runtime.h,可以看到class的定義:

struct?objc_class {

  Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE;// 父類(lèi)

const?char *name OBJC2_UNAVAILABLE;// 類(lèi)名

long version OBJC2_UNAVAILABLE;// 類(lèi)的版本信息,默認(rèn)為0

long info OBJC2_UNAVAILABLE;// 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)

long instance_size OBJC2_UNAVAILABLE;// 該類(lèi)的實(shí)例變量大小

struct?objc_ivar_list *ivars?OBJC2_UNAVAILABLE;// 該類(lèi)的成員變量鏈表

struct?objc_method_list **methodLists?OBJC2_UNAVAILABLE;?// 方法定義的鏈表

struct?objc_cache *cache?OBJC2_UNAVAILABLE;// 方法緩存

struct?objc_protocol_list *protocols?OBJC2_UNAVAILABLE;// 協(xié)議鏈表

#endif } OBJC2_UNAVAILABLE;

? ? ? ? 從上述代碼中可以看到 class 的本質(zhì),它是一個(gè)含有 isa 指針的結(jié)構(gòu)體,里面有父類(lèi)、類(lèi)名、方法列表、成員變量列表等變量。也就是說(shuō)通過(guò) runtime 我們可以知道該對(duì)象擁有的方法列表和成員變量列表,runtime 提供了相應(yīng)的 api。?

8.對(duì)象模型

類(lèi)對(duì)象(即類(lèi),這樣稱(chēng)乎是因?yàn)樵贠C中類(lèi)也是一個(gè)對(duì)象),實(shí)例對(duì)象(通過(guò)某個(gè)類(lèi)創(chuàng)建的具體實(shí)例)

Objective-C類(lèi)是由Class類(lèi)型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針。它的定義如下:

typedefstructobjc_class *Class;

查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:

struct objc_class {

? ? Class isa? OBJC_ISA_AVAILABILITY;

#if!__OBJC2__? ? Class super_class? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 父類(lèi)constchar*name? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類(lèi)名longversion? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類(lèi)的版本信息,默認(rèn)為0longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)longinstance_size? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類(lèi)的實(shí)例變量大小structobjc_ivar_list *ivars? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類(lèi)的成員變量鏈表structobjc_method_list **methodLists? OBJC2_UNAVAILABLE;// 方法定義的鏈表structobjc_cache *cache? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 方法緩存structobjc_protocol_list *protocols? ? OBJC2_UNAVAILABLE;// 協(xié)議鏈表#endif

} OBJC2_UNAVAILABLE;

在__OBJC2__以前,我們是可以看到結(jié)構(gòu)體的各個(gè)成員的,下面簡(jiǎn)單描述一下與對(duì)象模型相關(guān)的兩個(gè)字段:

isa:每個(gè)對(duì)象(包括類(lèi)對(duì)象和實(shí)例對(duì)象)中都會(huì)包含它,表明當(dāng)前的對(duì)象是屬于哪個(gè)類(lèi)的。實(shí)例對(duì)象的isa指針指向它的類(lèi)對(duì)象。而類(lèi)對(duì)象的isa指針指向它的元類(lèi)(metaclass)。后面會(huì)介紹到。

super_class:指向它的父類(lèi)的指針。

meta-class是一個(gè)類(lèi)對(duì)象的類(lèi)。

1、當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),會(huì)到這個(gè)實(shí)例對(duì)象所屬的這個(gè)類(lèi)對(duì)象的方法列表中查找方法

2、而向一個(gè)類(lèi)對(duì)象發(fā)送消息時(shí),會(huì)在這個(gè)類(lèi)對(duì)象的meta-class的方法列表中查找。

每個(gè)類(lèi)對(duì)象都會(huì)有一個(gè)單獨(dú)的meta-class。meta-class也是一個(gè)類(lèi),也可以向它發(fā)送一個(gè)消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無(wú)限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類(lèi)的meta-class,以此作為它們的所屬類(lèi)。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類(lèi),而基類(lèi)的meta-class的isa指針是指向它自己。這樣就形成了一個(gè)的閉環(huán)。

假設(shè)有如下代碼:

@interface SuperClass : NSObject@end@interface SubClass : SuperClass@end

@implementation?SubClass

- (void)message

{

}

@end


?對(duì)于NSObject繼承體系來(lái)說(shuō),圖中的Root class即為NSObject.

9.消息機(jī)制

在iOS中,我們要區(qū)分一下方法與函數(shù)。方法是屬于類(lèi)或者對(duì)象的,而函數(shù)則不一定,可以獨(dú)立于類(lèi)與對(duì)象之外。函數(shù)的調(diào)用是一步到位,程序直接跳到函數(shù)的地址去執(zhí)行。而方法的調(diào)用,它是對(duì)類(lèi)和對(duì)象而言,向?qū)?yīng)的類(lèi)或?qū)ο蟀l(fā)送一條消息,通過(guò)runtime系統(tǒng)的消息機(jī)制,找到實(shí)際要調(diào)用的函數(shù)地址,然后跳到地址中執(zhí)行。當(dāng)我們執(zhí)行上一部分Subclass類(lèi)的如下代碼時(shí)

[subClassInstance message]

編譯器調(diào)用的其實(shí)是

objc_msgSend(subClassInstance, selector) //selector參數(shù),編譯器傳遞的會(huì)是sel_registerName("message")。

如果方法帶有參數(shù),那么調(diào)的會(huì)是

objc_msgSend(subClassInstance, selector, arg1, arg2, ...)

對(duì)象模型 objc_class中有一個(gè)屬性

struct objc_method_list **methodLists;這個(gè)里面存儲(chǔ)的是類(lèi)的方法鏈表。鏈表內(nèi)每個(gè)元數(shù)都是一個(gè)方法。那么方法又是什么,我們可以在runtime.h中可以看到,每個(gè)方法其實(shí)也是一個(gè)結(jié)構(gòu)體。

struct objc_method {

? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;  //選擇器,Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字、參數(shù)序列,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類(lèi)型的地址),這個(gè)標(biāo)識(shí)就是SEL

? ? char*method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;  //IMP實(shí)際上是一個(gè)函數(shù)指針,指向方法實(shí)現(xiàn)的地址

}? ?


就上一部分SubClass中的而然,SubClass中的methodLists中就會(huì)有一個(gè)方法。method_name的值為:sel_registerName("message"),method_imp則指向messge方法實(shí)現(xiàn)的地址。(這個(gè)地址是編譯鏈接期已經(jīng)決定了的,這里我還這樣理解,我們可以運(yùn)行時(shí)為類(lèi)添加新的方法,修改方法的實(shí)現(xiàn)把它修改成另一個(gè)已經(jīng)存在的實(shí)現(xiàn)(這就是后面會(huì)說(shuō)到的Method swizzling),但是不能在運(yùn)行時(shí)創(chuàng)建一個(gè)新的實(shí)現(xiàn)。)  

  那么objc_msgSend會(huì)幫我們完成動(dòng)態(tài)綁定的所有事情:通過(guò)傳進(jìn)來(lái)的receiver subClassInstance,及selector找到對(duì)應(yīng)的Method。method中已經(jīng)關(guān)聯(lián)了方法實(shí)現(xiàn)的地址,接下來(lái)就把參數(shù)作為方法實(shí)現(xiàn)的參數(shù)傳進(jìn)去進(jìn)行調(diào)用,把調(diào)用的反回值作為objc_msgSend的返回值。這就是OC中的消息發(fā)送。

下圖演示了這樣一個(gè)消息的基本框架,以下基本來(lái)自官方文檔截圖及翻譯:


當(dāng)消息發(fā)送給一個(gè)對(duì)象時(shí),objc_msgSend通過(guò)對(duì)象的isa指針獲取到類(lèi)的結(jié)構(gòu)體,然后在方法分發(fā)表里面查找方法的selector。如果沒(méi)有找到selector,則通過(guò)objc_msgSend結(jié)構(gòu)體中的指向父類(lèi)的指針找到其父類(lèi),并在父類(lèi)的分發(fā)表里面查找方法的selector。依此,會(huì)一直沿著類(lèi)的繼承體系到達(dá)NSObject類(lèi)。一旦定位到selector,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn),并傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn)。如果最后沒(méi)有定位到selector,則會(huì)走消息轉(zhuǎn)發(fā)流程,這個(gè)我們?cè)谏院笥懻摗?/p>

  現(xiàn)在先回頭看看對(duì)象模型中objc_class中的struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存

上面說(shuō)的是方法第一次被調(diào)用的流程。在我們每次調(diào)用過(guò)一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒(méi)有,才去methodLists中查找方法,走上述流程。所以O(shè)C的對(duì)象模型是消息傳遞機(jī)制中方法查找的基礎(chǔ)。

接下來(lái)我們會(huì)講消息的轉(zhuǎn)發(fā)機(jī)制,如上面所說(shuō):如果最后沒(méi)有定位到selector,則會(huì)走消息轉(zhuǎn)發(fā)流程。實(shí)在沒(méi)必要重復(fù)造輪子,對(duì)于這部分詳情可以參考消息轉(zhuǎn)發(fā),作者已經(jīng)寫(xiě)得夠好了。但為了不跳鏈接也能有個(gè)好的理解,我下面是對(duì)鏈接內(nèi)容的刪簡(jiǎn)版,去掉具體代碼的實(shí)現(xiàn)。


1、當(dāng)沒(méi)有找到SEL的IMP時(shí),resolveInstanceMethod方法就會(huì)被調(diào)用,它給類(lèi)利用class_addMethod添加方法的機(jī)會(huì)。

2、經(jīng)過(guò)resolveInstanceMethod如果對(duì)象還是不能執(zhí)行到對(duì)應(yīng)的IMP那么就進(jìn)入下一個(gè)階段,進(jìn)入到

流程到了這里,系統(tǒng)給了個(gè)將這個(gè)SEL轉(zhuǎn)給其他對(duì)象的機(jī)會(huì)。

3、如果第二步返回的是nil或Self,那就就會(huì)進(jìn)入methodSignatureForSelector這個(gè)函數(shù)和后面的forwardInvocation:是最后一個(gè)尋找IML的機(jī)會(huì)。這個(gè)函數(shù)讓重載方有機(jī)會(huì)拋出一個(gè)函數(shù)的簽名,再由后面的forwardInvocation:去執(zhí)行。

真正執(zhí)行從methodSignatureForSelector:返回的NSMethodSignature。在這個(gè)函數(shù)里可以將NSInvocation多次轉(zhuǎn)發(fā)到多個(gè)對(duì)象中,這也是這種方式靈活的地方。(forwardingTargetForSelector只能以Selector的形式轉(zhuǎn)向一個(gè)對(duì)象)。

具體調(diào)用過(guò)程:

消息機(jī)制是運(yùn)行時(shí)里面最重要的機(jī)制,OC中任何方法的調(diào)用,本質(zhì)都是發(fā)送消息。使用運(yùn)行時(shí),發(fā)送消息需要導(dǎo)入框架<objc/message.h>并且xcode5之后,蘋(píng)果不建議使用底層方法,如果想要使用運(yùn)行時(shí),需要關(guān)閉嚴(yán)格檢查objc_msgSend的調(diào)用,BuildSetting->搜索msg 改為NO。

實(shí)例方法調(diào)用底層實(shí)現(xiàn):

Person *p = [[Person alloc] init];

[p eat];

// 底層會(huì)轉(zhuǎn)化成

//SEL:方法編號(hào),根據(jù)方法編號(hào)就可以找到對(duì)應(yīng)方法的實(shí)現(xiàn)。

[p performSelector:@selector(eat)];

//performSelector本質(zhì)即為運(yùn)行時(shí),發(fā)送消息,誰(shuí)做事情就調(diào)用誰(shuí)

objc_msgSend(p, @selector(eat));

// 帶參數(shù)

objc_msgSend(p, @selector(eat:),10);

類(lèi)方法的調(diào)用底層

// 本質(zhì)是會(huì)將類(lèi)名轉(zhuǎn)化成類(lèi)對(duì)象,初始化方法其實(shí)是在創(chuàng)建類(lèi)對(duì)象。

[Person eat];

// Person只是表示一個(gè)類(lèi)名,并不是一個(gè)真實(shí)的對(duì)象。只要是方法必須要對(duì)象去調(diào)用。

// RunTime 調(diào)用類(lèi)方法同樣,類(lèi)方法也是類(lèi)對(duì)象去調(diào)用,所以需要獲取類(lèi)對(duì)象,然后使用類(lèi)對(duì)象去調(diào)用方法。

Class personclass = [Persion class];

[[Persion class] performSelector:@selector(eat)];

// 類(lèi)對(duì)象發(fā)送消息

objc_msgSend(personclass, @selector(eat));


**@selector (SEL):是一個(gè)SEL方法選擇器。**SEL其主要作用是快速的通過(guò)方法名字查找到對(duì)應(yīng)方法的函數(shù)指針,然后調(diào)用其函數(shù)。SEL其本身是一個(gè)Int類(lèi)型的地址,地址中存放著方法的名字。對(duì)于一個(gè)類(lèi)中。每一個(gè)方法對(duì)應(yīng)著一個(gè)SEL。所以一個(gè)類(lèi)中不能存在2個(gè)名稱(chēng)相同的方法,即使參數(shù)類(lèi)型不同,因?yàn)镾EL是根據(jù)方法名字生成的,相同的方法名稱(chēng)只能對(duì)應(yīng)一個(gè)SEL。

運(yùn)行時(shí)發(fā)送消息的底層實(shí)現(xiàn)每一個(gè)類(lèi)都有一個(gè)方法列表 Method List,保存這類(lèi)里面所有的方法,根據(jù)SEL傳入的方法編號(hào)找到方法,相當(dāng)于value - key的映射。然后找到方法的實(shí)現(xiàn)。去方法的實(shí)現(xiàn)里面去實(shí)現(xiàn)。如圖所示。


我們來(lái)到objc_class中查看,其中包含著類(lèi)的一些基本信息。

struct objc_class {

? Class isa; // 指向metaclass


? Class super_class ; // 指向其父類(lèi)

? const char *name ; // 類(lèi)名

? long version ; // 類(lèi)的版本信息,初始化默認(rèn)為0,可以通過(guò)runtime函數(shù)class_setVersion和class_getVersion進(jìn)行修改、讀取

? long info; // 一些標(biāo)識(shí)信息,如CLS_CLASS (0x1L) 表示該類(lèi)為普通 class ,其中包含對(duì)象方法和成員變量;CLS_META (0x2L) 表示該類(lèi)為 metaclass,其中包含類(lèi)方法;

? long instance_size ; // 該類(lèi)的實(shí)例變量大小(包括從父類(lèi)繼承下來(lái)的實(shí)例變量);

? struct objc_ivar_list *ivars; // 用于存儲(chǔ)每個(gè)成員變量的地址

? struct objc_method_list **methodLists ; // 與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲(chǔ)對(duì)象方法,如CLS_META (0x2L),則存儲(chǔ)類(lèi)方法;

? struct objc_cache *cache; // 指向最近使用的方法的指針,用于提升效率;

? struct objc_protocol_list *protocols; // 存儲(chǔ)該類(lèi)遵守的協(xié)議

}

下面我們就以p實(shí)例的eat方法來(lái)看看具體消息發(fā)送之后是怎么來(lái)動(dòng)態(tài)查找對(duì)應(yīng)的方法的。

實(shí)例方法[p eat];底層調(diào)用[p performSelector:@selector(eat)];方法,編譯器在將代碼轉(zhuǎn)化為objc_msgSend(p, @selector(eat));

在objc_msgSend函數(shù)中。首先通過(guò)p的isa指針找到p對(duì)應(yīng)的class。在Class中先去cache中通過(guò)SEL查找對(duì)應(yīng)函數(shù)method,如果找到則通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行。

若cache中未找到。再去methodList中查找。若能找到,則將method加入到cache中,以方便下次查找,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行。

若methodlist中未找到,則去superClass中查找。若能找到,則將method加入到cache中,以方便下次查找,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行。

注:對(duì)象如何找到對(duì)應(yīng)的方法去調(diào)用

1.方法保存到什么地方?

對(duì)象方法保存到類(lèi)中,類(lèi)方法保存到元類(lèi)(meta class),每一個(gè)類(lèi)都有方法列表methodList。

2明確去哪個(gè)類(lèi)中調(diào)用?

通過(guò)isa指針

1.根據(jù)對(duì)象的isa去對(duì)應(yīng)的類(lèi)查找方法,isa:判斷去哪個(gè)類(lèi)查找對(duì)應(yīng)的方法 指向方法調(diào)用的類(lèi)。

2.根據(jù)傳入的方法編號(hào)SEL,里面有個(gè)哈希列表,在列表中找到對(duì)應(yīng)方法Method(方法名)。

3.根據(jù)方法名(函數(shù)入口)找到函數(shù)實(shí)現(xiàn),函數(shù)實(shí)現(xiàn)在方法區(qū)。

10.使用RunTime交換方法

系統(tǒng)自帶的方法功能不夠,需要給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能時(shí),可以使用RunTime交換方法實(shí)現(xiàn)。這里要實(shí)現(xiàn)image添加圖片的時(shí)候,自動(dòng)判斷image是否為空,如果為空則提醒圖片不存在。

方法一:自定義UIImage類(lèi),缺點(diǎn):每次用要導(dǎo)入自己的類(lèi)。

方法二:使用分類(lèi),UIImage分類(lèi)擴(kuò)充一個(gè)這樣方法,缺點(diǎn):需要導(dǎo)入,無(wú)法寫(xiě)super和self,會(huì)干掉系統(tǒng)方法,解決:給系統(tǒng)方法加個(gè)前綴,與系統(tǒng)方法區(qū)分,如:xmg_imageNamed。

+ (nullable UIImage *)xx_ccimageNamed:(NSString *)name

{

? ? // 加載圖片? ? 如果圖片不存在則提醒或發(fā)出異常

? UIImage *image = [UIImage imageNamed:name];

? ? if (image == nil) {

? ? ? ? NSLog(@"圖片不存在");

? ? }

? ? return image;

}

缺點(diǎn):每次使用都需要導(dǎo)入頭文件,并且如果項(xiàng)目比較大,之前使用的方法全部需要更改。

方法三 :RunTime交換方法交換方法的本質(zhì)其實(shí)是交換兩個(gè)方法的實(shí)現(xiàn),即調(diào)換xx_ccimageNamed和imageName方法,達(dá)到調(diào)用xx_ccimageNamed其實(shí)就是調(diào)用imageNamed方法的目的;交互方法實(shí)現(xiàn),步驟: 1.提供分類(lèi) 2.寫(xiě)一個(gè)有這樣功能方法 3.用系統(tǒng)方法與這個(gè)功能方法交互實(shí)現(xiàn),在+load方法中實(shí)現(xiàn)。

那么首先需要明白方法在哪里交換,因?yàn)榻粨Q只需要進(jìn)行一次,所以在分類(lèi)的load方法中,當(dāng)加載分類(lèi)的時(shí)候交換方法即可。

+(void)load

{

? ? // 獲取要交換的兩個(gè)方法

? ? // 獲取類(lèi)方法? 用Method 接受一下

? ? // class :獲取哪個(gè)類(lèi)方法

? ? // SEL :獲取方法編號(hào),根據(jù)SEL就能去對(duì)應(yīng)的類(lèi)找方法。

? ? Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));

? ? // 獲取第二個(gè)類(lèi)方法

? ? Method xx_ccimageNameMrthod = class_getClassMethod([UIImage class], @selector(xx_ccimageNamed:));

? ? // 交換兩個(gè)方法的實(shí)現(xiàn) 方法一 ,方法二。

? ? method_exchangeImplementations(imageNameMethod, xx_ccimageNameMrthod);

? ? // IMP其實(shí)就是 implementation的縮寫(xiě):表示方法實(shí)現(xiàn)。

}

交換方法內(nèi)部實(shí)現(xiàn):

根據(jù)SEL方法編號(hào)在Method中找到方法,兩個(gè)方法都找到

交換方法的實(shí)現(xiàn),指針交叉指向。如圖所示:


注意:交換方法時(shí)候 xx_ccimageNamed方法中就不能再調(diào)用imageNamed方法了,因?yàn)檎{(diào)用imageNamed方法實(shí)質(zhì)上相當(dāng)于調(diào)用 xx_ccimageNamed方法,會(huì)循環(huán)引用造成死循環(huán)。

RunTime也提供了獲取對(duì)象方法和方法實(shí)現(xiàn)的方法。

// 獲取方法的實(shí)現(xiàn)

class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)

// 獲取對(duì)象方法

class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

此時(shí),當(dāng)調(diào)用imageNamed:方法的時(shí)候就會(huì)調(diào)用xx_ccimageNamed:方法,為image添加圖片,并判斷圖片是否存在,如果不存在則提醒圖片不存在。

注意:在分類(lèi)一定不要重寫(xiě)系統(tǒng)方法,就直接把系統(tǒng)方法干掉,如果真的想重寫(xiě),在系統(tǒng)方法前面加前綴,方法里面去調(diào)用系統(tǒng)方法。

思想:什么時(shí)候需要自定義,系統(tǒng)功能不完善,就自定義一個(gè)這樣類(lèi),去擴(kuò)展這個(gè)類(lèi)

11.Method Swizzling (黑魔法)

基于消息機(jī)制,才會(huì)有Method Swizzling,Method Swizzling是改變一個(gè)selector的實(shí)際實(shí)現(xiàn)的技術(shù)。通過(guò)這一技術(shù),我們可以在運(yùn)行時(shí)通過(guò)修改類(lèi)的分發(fā)表中selector對(duì)應(yīng)的函數(shù),來(lái)修改方法的實(shí)現(xiàn)。

https://nshipster.com/method-swizzling/

例如,我們想跟蹤在程序中每一個(gè) viewcontroller 展示給用戶(hù)的次數(shù):當(dāng)然,我們可以在每個(gè)view controller的viewDidAppear中添加跟蹤代碼;但是這太過(guò)麻煩,需要在每個(gè) viewcontroller 中寫(xiě)重復(fù)的代碼。創(chuàng)建一個(gè)子類(lèi)可能是一種實(shí)現(xiàn)方式,但需要同時(shí)創(chuàng)建UIViewController,?UITableViewController,?UINavigationController及其它UIKit中view controller的子類(lèi),這同樣會(huì)產(chǎn)生許多重復(fù)的代碼。

這種情況下,我們就可以使用Method Swizzling

#import@implementation UIViewController (Tracking)+ (void)load {

? ? ? ? static dispatch_once_t onceToken;

? ? dispatch_once(&onceToken, ^{

? ? ? ? Class class= [selfclass];? ? ? ?

? ? ? ? // When swizzling a class method, use the following:

? ? ? ? ? ? ? ? ? ? // Class class = object_getClass((id)self);? ? ? ? SEL originalSelector = @selector(viewWillAppear:);

? ? ? ? SEL swizzledSelector = @selector(xxx_viewWillAppear:);

? ? ? ? Method originalMethod = class_getInstanceMethod(class, originalSelector);

? ? ? ? Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

? ? ? ? BOOL didAddMethod =class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

? ? ? ? if (didAddMethod) {

? ? ? ? ? ? ? ? class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

? ? ? ? } else {

? ? ? ? ? ? method_exchangeImplementations(originalMethod, swizzledMethod);

? ? ? ? }

? ? });

}

#pragmamark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {

? ? ? ? [self xxx_viewWillAppear:animated];

? ? NSLog(@"viewWillAppear: %@", self);

}

@end

在這里,我們通過(guò) method swizzling 修改了 UIViewController 的 @selector(viewWillAppear:) 對(duì)應(yīng)的函數(shù)指針,使其實(shí)現(xiàn)指向了我們自定義的 xxx_viewWillAppear 的實(shí)現(xiàn)。這樣,當(dāng) UIViewController 及其子類(lèi)的對(duì)象調(diào)用 viewWillAppear 時(shí),都會(huì)打印一條日志信息。

上面的例子很好地展示了使用 method swizzling 來(lái)一個(gè)類(lèi)中注入一些我們新的操作。當(dāng)然,還有許多場(chǎng)景可以使用 method swizzling,在此不多舉例。在此我們說(shuō)說(shuō)使用 method swizzling 需要注意的一些問(wèn)題:

Swizzling應(yīng)該總是在+load中執(zhí)行

在Objective-C中,運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類(lèi)的兩個(gè)方法。+load會(huì)在類(lèi)初始加載時(shí)調(diào)用,+initialize會(huì)在第一次調(diào)用類(lèi)的類(lèi)方法或?qū)嵗椒ㄖ氨徽{(diào)用。這兩個(gè)方法是可選的,且只有在實(shí)現(xiàn)了它們時(shí)才會(huì)被調(diào)用。由于method swizzling會(huì)影響到類(lèi)的全局狀態(tài),因此要盡量避免在并發(fā)處理中出現(xiàn)競(jìng)爭(zhēng)的情況。+load能保證在類(lèi)的初始化過(guò)程中被加載,并保證這種改變應(yīng)用級(jí)別的行為的一致性。相比之下,+initialize在其執(zhí)行時(shí)不提供這種保證—事實(shí)上,如果在應(yīng)用中沒(méi)為給這個(gè)類(lèi)發(fā)送消息,則它可能永遠(yuǎn)不會(huì)被調(diào)用。

Swizzling應(yīng)該總是在dispatch_once中執(zhí)行

與上面相同,因?yàn)閟wizzling會(huì)改變?nèi)譅顟B(tài),所以我們需要在運(yùn)行時(shí)采取一些預(yù)防措施。原子性就是這樣一種措施,它確保代碼只被執(zhí)行一次,不管有多少個(gè)線(xiàn)程。GCD的dispatch_once可以確保這種行為,我們應(yīng)該將其作為method swizzling的最佳實(shí)踐。

注意事項(xiàng)

Swizzling通常被稱(chēng)作是一種黑魔法,容易產(chǎn)生不可預(yù)知的行為和無(wú)法預(yù)見(jiàn)的后果。雖然它不是最安全的,但如果遵從以下幾點(diǎn)預(yù)防措施的話(huà),還是比較安全的:

1、總是調(diào)用方法的原始實(shí)現(xiàn)(除非有更好的理由不這么做):API提供了一個(gè)輸入與輸出約定,但其內(nèi)部實(shí)現(xiàn)是一個(gè)黑盒。Swizzle一個(gè)方法而不調(diào)用原始實(shí)現(xiàn)可能會(huì)打破私有狀態(tài)底層操作,從而影響到程序的其它部分。

2、避免沖突:給自定義的分類(lèi)方法加前綴,從而使其與所依賴(lài)的代碼庫(kù)不會(huì)存在命名沖突。

3、明白是怎么回事:簡(jiǎn)單地拷貝粘貼swizzle代碼而不理解它是如何工作的,不僅危險(xiǎn),而且會(huì)浪費(fèi)學(xué)習(xí)Objective-C運(yùn)行時(shí)的機(jī)會(huì)。閱讀Objective-C Runtime Reference和查看<objc/runtime.h>頭文件以了解事件是如何發(fā)生的。

12.動(dòng)態(tài)添加方法

為什么動(dòng)態(tài)添加方法? OC都是懶加載,有些方法可能很久不會(huì)調(diào)用

應(yīng)用場(chǎng)景:電商,視頻,社交,收費(fèi)項(xiàng)目:會(huì)員機(jī)制中,只要會(huì)員才擁有這些功能

有沒(méi)有使用過(guò)performSelector,使用,什么時(shí)候使用?動(dòng)態(tài)添加方法的時(shí)候使用? 為什么動(dòng)態(tài)添加方法?

// 默認(rèn)OC方法都有兩個(gè)默認(rèn)存在的隱式參數(shù),self(哪個(gè)類(lèi)的方法),_cmd(方法編號(hào))

void run(idself, SEL _cmd,NSNumber*metre) {

NSLog(@"跑了%@",metre);

}


什么時(shí)候調(diào)用?只要調(diào)用沒(méi)有實(shí)現(xiàn)的方法 就會(huì)調(diào)用方法去解決,這里可以拿到那個(gè)未實(shí)現(xiàn)的方法名

// 作用:去解決沒(méi)有實(shí)現(xiàn)方法,動(dòng)態(tài)添加方法

+(BOOL)resolveInstanceMethod:(SEL)sel{

class:給誰(shuí)添加方法?

?SEL:添加哪個(gè)方法?

?IMP:方法實(shí)現(xiàn),函數(shù)入口,函數(shù)名,如:(IMP)run,方法名run強(qiáng)轉(zhuǎn)成IMP?

?type:方法類(lèi)型,通過(guò)查蘋(píng)果官方文檔,V:void,?

?class_addMethod(<#__unsafe_unretained Class cls#>,<#SEL name#>,<#IMP imp#>,<#const char *types#>)

// [NSStringFromSelector(sel) isEqualToString:@"eat"];

if(sel ==@selector(run:))?

{// 添加方法

class_addMethod(self, sel, (IMP)run,"v@:");

returnYES;?

?}

return[superresolveInstanceMethod:sel];

}


如果一個(gè)類(lèi)方法非常多,其中可能許多方法暫時(shí)用不到。而加載類(lèi)方法到內(nèi)存的時(shí)候需要給每個(gè)方法生成映射表,又比較耗費(fèi)資源。此時(shí)可以使用RunTime動(dòng)態(tài)添加方法

動(dòng)態(tài)給某個(gè)類(lèi)添加方法,相當(dāng)于懶加載機(jī)制,類(lèi)中許多方法暫時(shí)用不到,那么就先不加載,等用到的時(shí)候再去加載方法。

動(dòng)態(tài)添加方法的方法:首先我們先不實(shí)現(xiàn)對(duì)象方法,當(dāng)調(diào)用performSelector: 方法的時(shí)候,再去動(dòng)態(tài)加載方法。這里同上創(chuàng)建Person類(lèi),使用performSelector: 調(diào)用Person類(lèi)對(duì)象的eat方法。

Person *p = [[Person alloc]init];

// 當(dāng)調(diào)用 P中沒(méi)有實(shí)現(xiàn)的方法時(shí),動(dòng)態(tài)加載方法

[p performSelector:@selector(eat)];

此時(shí)編譯的時(shí)候是不會(huì)報(bào)錯(cuò)的,程序運(yùn)行時(shí)才會(huì)報(bào)錯(cuò),因?yàn)镻erson類(lèi)中并沒(méi)有實(shí)現(xiàn)eat方法,當(dāng)去類(lèi)中的Method List中發(fā)現(xiàn)找不到eat方法,會(huì)報(bào)錯(cuò)找不到eat方法。

而當(dāng)找不到對(duì)應(yīng)的方法時(shí)就會(huì)來(lái)到攔截調(diào)用,在找不到調(diào)用的方法程序崩潰之前調(diào)用的方法。當(dāng)調(diào)用了沒(méi)有實(shí)現(xiàn)的對(duì)象方法的時(shí),就會(huì)調(diào)用**+(BOOL)resolveInstanceMethod:(SEL)sel方法。當(dāng)調(diào)用了沒(méi)有實(shí)現(xiàn)的類(lèi)方法的時(shí)候,就會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel**方法。

首先我們來(lái)到API中看一下蘋(píng)果的說(shuō)明,搜索 Dynamic Method Resolution 來(lái)到動(dòng)態(tài)方法解析。


Dynamic Method Resolution的API中已經(jīng)講解的很清晰,我們可以實(shí)現(xiàn)方法resolveInstanceMethod:或者resolveClassMethod:方法,動(dòng)態(tài)的給實(shí)例方法或者類(lèi)方法添加方法和方法實(shí)現(xiàn)。

所以通過(guò)這兩個(gè)方法就可以知道哪些方法沒(méi)有實(shí)現(xiàn),從而動(dòng)態(tài)添加方法。參數(shù)sel即表示沒(méi)有實(shí)現(xiàn)的方法。

一個(gè)objective - C方法最終都是一個(gè)C函數(shù),默認(rèn)任何一個(gè)方法都有兩個(gè)參數(shù)。self : 方法調(diào)用者 _cmd : 調(diào)用方法編號(hào)。我們可以使用函數(shù)class_addMethod為類(lèi)添加一個(gè)方法以及實(shí)現(xiàn)。

這里仿照API給的例子,動(dòng)態(tài)的為P實(shí)例添加eat對(duì)象

+(BOOL)resolveInstanceMethod:(SEL)sel

{

? ? // 動(dòng)態(tài)添加eat方法

? ? // 首先判斷sel是不是eat方法 也可以轉(zhuǎn)化成字符串進(jìn)行比較。? ?

? ? if (sel == @selector(eat)) {

? ? /**

? ? 第一個(gè)參數(shù): cls:給哪個(gè)類(lèi)添加方法

? ? 第二個(gè)參數(shù): SEL name:添加方法的編號(hào)

? ? 第三個(gè)參數(shù): IMP imp: 方法的實(shí)現(xiàn),函數(shù)入口,函數(shù)名可與方法名不同(建議與方法名相同)

? ? 第四個(gè)參數(shù): types :方法類(lèi)型,需要用特定符號(hào),參考API

? ? */

? ? ? class_addMethod(self, sel, (IMP)eat , "v@:");

? ? ? ? // 處理完返回YES

? ? ? ? return YES;

? ? }

? ? return [super resolveInstanceMethod:sel];

}

重點(diǎn)來(lái)看一下class_addMethod方法

class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)

class_addMethod中的四個(gè)參數(shù)。第一,二個(gè)參數(shù)比較好理解,重點(diǎn)是第三,四個(gè)參數(shù)。

cls : 表示給哪個(gè)類(lèi)添加方法,這里要給Person類(lèi)添加方法,self即代表Person。

SEL name : 表示添加方法的編號(hào)。因?yàn)檫@里只有一個(gè)方法需要?jiǎng)討B(tài)添加,并且之前通過(guò)判斷確定sel就是eat方法,所以這里可以使用sel。

IMP imp : 表示方法的實(shí)現(xiàn),函數(shù)入口,函數(shù)名可與方法名不同(建議與方法名相同)需要自己來(lái)實(shí)現(xiàn)這個(gè)函數(shù)。每一個(gè)方法都默認(rèn)帶有兩個(gè)隱式參數(shù)self : 方法調(diào)用者 _cmd : 調(diào)用方法的標(biāo)號(hào),可以寫(xiě)也可以不寫(xiě)。


void eat(id self ,SEL _cmd)

{

? ? ? // 實(shí)現(xiàn)內(nèi)容

NSLog(@"%@的%@方法動(dòng)態(tài)實(shí)現(xiàn)了",self,NSStringFromSelector(_cmd));

}

4.types : 表示方法類(lèi)型,需要用特定符號(hào)。系統(tǒng)提供的例子中使用的是**"v@:",我們來(lái)到API中看看"v@:"**指定的方法是什么類(lèi)型的。


從圖中可以看出,

v?-> void 表示無(wú)返回值@?-> object 表示id參數(shù):?-> method selector 表示SEL。

至此已經(jīng)完成了P實(shí)例eat方法的動(dòng)態(tài)添加。當(dāng)P調(diào)用eat方法時(shí)輸出


動(dòng)態(tài)添加有參數(shù)的方法如果是有參數(shù)的方法,需要對(duì)方法的實(shí)現(xiàn)和class_addMethod方法內(nèi)方法類(lèi)型參數(shù)做一些修改。方法實(shí)現(xiàn):因?yàn)樵贑語(yǔ)言函數(shù)中,所以對(duì)象參數(shù)類(lèi)型只能用id代替。方法類(lèi)型參數(shù):因?yàn)樘砑恿艘粋€(gè)id參數(shù),所以方法類(lèi)型應(yīng)該為**"v@:@"**來(lái)看一下代碼

+(BOOL)resolveInstanceMethod:(SEL)sel

{

? ? if (sel == @selector(eat:)) {

? ? ? ? class_addMethod(self, sel, (IMP)aaaa , "v@:@");

? ? ? ? return YES;

? ? }

? ? return [super resolveInstanceMethod:sel];

}

void aaaa(id self ,SEL _cmd,id Num)

{

? ? // 實(shí)現(xiàn)內(nèi)容

? ? NSLog(@"%@的%@方法動(dòng)態(tài)實(shí)現(xiàn)了,參數(shù)為%@",self,NSStringFromSelector(_cmd),Num);

}

調(diào)用eat:函數(shù)

Person *p = [[Person alloc]init];

[p performSelector:@selector(eat:)withObject:@"xx_cc"];

輸出為


?13.RunTime動(dòng)態(tài)添加屬性

使用RunTime給系統(tǒng)的類(lèi)添加屬性,首先需要了解對(duì)象與屬性的關(guān)系。


對(duì)象一開(kāi)始初始化的時(shí)候其屬性name為nil,給屬性賦值其實(shí)就是讓name屬性指向一塊存儲(chǔ)字符串的內(nèi)存,使這個(gè)對(duì)象的屬性跟這塊內(nèi)存產(chǎn)生一種關(guān)聯(lián),個(gè)人理解對(duì)象的屬性就是一個(gè)指針,指向一塊內(nèi)存區(qū)域。

那么如果想動(dòng)態(tài)的添加屬性,其實(shí)就是動(dòng)態(tài)的產(chǎn)生某種關(guān)聯(lián)就好了。而想要給系統(tǒng)的類(lèi)添加屬性,只能通過(guò)分類(lèi)。

這里給NSObject添加name屬性,創(chuàng)建NSObject的分類(lèi)我們可以使用@property給分類(lèi)添加屬性

@property(nonatomic,strong)NSString *name;

雖然在分類(lèi)中可以寫(xiě)@property添加屬性,但是不會(huì)自動(dòng)生成私有屬性,也不會(huì)生成set,get方法的實(shí)現(xiàn),只會(huì)生成set,get的聲明,需要我們自己去實(shí)現(xiàn)。

方法一:我們可以通過(guò)使用靜態(tài)全局變量給分類(lèi)添加屬性

static NSString *_name;

-(void)setName:(NSString *)name

{

? ? _name = name;

}

-(NSString *)name

{

? ? return _name;

}

但是這樣_name靜態(tài)全局變量與類(lèi)并沒(méi)有關(guān)聯(lián),無(wú)論對(duì)象創(chuàng)建與銷(xiāo)毀,只要程序在運(yùn)行_name變量就存在,并不是真正意義上的屬性。

方法二:使用RunTime動(dòng)態(tài)添加屬性RunTime提供了動(dòng)態(tài)添加屬性和獲得屬性的方法。

-(void)setName:(NSString *)name

{

? ? objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

-(NSString *)name

{

? ? return objc_getAssociatedObject(self, @"name");? ?

}

動(dòng)態(tài)添加屬性

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

參數(shù)一:id object : 給哪個(gè)對(duì)象添加屬性,這里要給自己添加屬性,用self。參數(shù)二:void * == id key : 屬性名,根據(jù)key獲取關(guān)聯(lián)對(duì)象的屬性的值,在**objc_getAssociatedObject中通過(guò)次key獲得屬性的值并返回。參數(shù)三:id value** : 關(guān)聯(lián)的值,也就是set方法傳入的值給屬性去保存。參數(shù)四:objc_AssociationPolicy policy : 策略,屬性以什么形式保存。有以下幾種

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {

? ? OBJC_ASSOCIATION_ASSIGN = 0,? // 指定一個(gè)弱引用相關(guān)聯(lián)的對(duì)象

? ? OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對(duì)象的強(qiáng)引用,非原子性

? ? OBJC_ASSOCIATION_COPY_NONATOMIC = 3,? // 指定相關(guān)的對(duì)象被復(fù)制,非原子性

? ? OBJC_ASSOCIATION_RETAIN = 01401,? // 指定相關(guān)對(duì)象的強(qiáng)引用,原子性

? ? OBJC_ASSOCIATION_COPY = 01403? ? // 指定相關(guān)的對(duì)象被復(fù)制,原子性?

};

獲得屬性

objc_getAssociatedObject(id object, const void *key);

參數(shù)一:id object : 獲取哪個(gè)對(duì)象里面的關(guān)聯(lián)的屬性。參數(shù)二:void * == id key : 什么屬性,與**objc_setAssociatedObject**中的key相對(duì)應(yīng),即通過(guò)key值取出value。

此時(shí)已經(jīng)成功給NSObject添加name屬性,并且NSObject對(duì)象可以通過(guò)點(diǎn)語(yǔ)法為屬性賦值。

NSObject *objc = [[NSObject alloc]init];

objc.name = @"xx_cc";

NSLog(@"%@",objc.name);

14.?RunTime字典轉(zhuǎn)模型

為了方便以后重用,這里通過(guò)給NSObject添加分類(lèi),聲明并實(shí)現(xiàn)使用RunTime字典轉(zhuǎn)模型的類(lèi)方法。

+ (instancetype)modelWithDict:(NSDictionary *)dict

首先來(lái)看一下KVC字典轉(zhuǎn)模型和RunTime字典轉(zhuǎn)模型的區(qū)別

KVC:KVC字典轉(zhuǎn)模型實(shí)現(xiàn)原理是遍歷字典中所有Key,然后去模型中查找相對(duì)應(yīng)的屬性名,要求屬性名與Key必須一一對(duì)應(yīng),字典中所有key必須在模型中存在。RunTime:RunTime字典轉(zhuǎn)模型實(shí)現(xiàn)原理是遍歷模型中的所有屬性名,然后去字典查找相對(duì)應(yīng)的Key,也就是以模型為準(zhǔn),模型中有哪些屬性,就去字典中找那些屬性。

RunTime字典轉(zhuǎn)模型的優(yōu)點(diǎn):當(dāng)服務(wù)器返回的數(shù)據(jù)過(guò)多,而我們只使用其中很少一部分時(shí),沒(méi)有用的屬性就沒(méi)有必要定義成屬性浪費(fèi)不必要的資源。只保存最有用的屬性即可。

RunTime字典轉(zhuǎn)模型過(guò)程首先需要了解,屬性定義在類(lèi)里面,那么類(lèi)里面就有一個(gè)屬性列表,屬性列表以數(shù)組的形式存在,根據(jù)屬性列表就可以獲得類(lèi)里面的所有屬性名,所以遍歷屬性列表,也就可以遍歷模型中的所有屬性名。所以RunTime字典轉(zhuǎn)模型過(guò)程就很清晰了。

創(chuàng)建模型對(duì)象

id objc = [[self alloc] init];

使用**class_copyIvarList**方法拷貝成員屬性列表

unsigned int count = 0;

Ivar *ivarList = class_copyIvarList(self, &count);

參數(shù)一:__unsafe_unretained Class cls : 獲取哪個(gè)類(lèi)的成員屬性列表。這里是self,因?yàn)檎l(shuí)調(diào)用分類(lèi)中類(lèi)方法,誰(shuí)就是self。參數(shù)二:unsigned int *outCount : 無(wú)符號(hào)int型指針,這里創(chuàng)建unsigned int型count,&count就是他的地址,保證在方法中可以拿到count的地址為count賦值。傳出來(lái)的值為成員屬性總數(shù)。返回值:Ivar * : 返回的是一個(gè)Ivar類(lèi)型的指針 。指針默認(rèn)指向的是數(shù)組的第0個(gè)元素,指針+1會(huì)向高地址移動(dòng)一個(gè)Ivar單位的字節(jié),也就是指向第一個(gè)元素。Ivar表示成員屬性。3. 遍歷成員屬性列表,獲得屬性列表

for (int i = 0 ; i < count; i++) {

? ? ? ? // 獲取成員屬性

? ? ? ? Ivar ivar = ivarList[i];

}

使用**ivar_getName(ivar)**獲得成員屬性名,因?yàn)槌蓡T屬性名返回的是C語(yǔ)言字符串,將其轉(zhuǎn)化成OC字符串

NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];

通過(guò)**ivar_getTypeEncoding(ivar)**也可以獲得成員屬性類(lèi)型。5. 因?yàn)楂@得的是成員屬性名,是帶_的成員屬性,所以需要將下劃線(xiàn)去掉,獲得屬性名,也就是字典的key。

// 獲取key

NSString *key = [propertyName substringFromIndex:1];

獲取字典中key對(duì)應(yīng)的Value。

// 獲取字典的value

id value = dict[key];

給模型屬性賦值,并將模型返回

if (value) {

// KVC賦值:不能傳空

[objc setValue:value forKey:key];

}

return objc;

至此已成功將字典轉(zhuǎn)為模型。

15. RunTime字典轉(zhuǎn)模型的二級(jí)轉(zhuǎn)換

在開(kāi)發(fā)過(guò)程中經(jīng)常用到模型嵌套,也就是模型中還有一個(gè)模型,這里嘗試用RunTime進(jìn)行模型的二級(jí)轉(zhuǎn)換,實(shí)現(xiàn)思路其實(shí)比較簡(jiǎn)單清晰。

首先獲得一級(jí)模型中的成員屬性的類(lèi)型

// 成員屬性類(lèi)型

NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

判斷當(dāng)一級(jí)字典中的value是字典,并且一級(jí)模型中的成員屬性類(lèi)型不是NSDictionary的時(shí)候才需要進(jìn)行二級(jí)轉(zhuǎn)化。首先value是字典才進(jìn)行轉(zhuǎn)化是必須的,因?yàn)槲覀兺ǔ⒆值滢D(zhuǎn)化為模型,其次,成員屬性類(lèi)型不是系統(tǒng)類(lèi),說(shuō)明成員屬性是我們自定義的類(lèi),也就是要轉(zhuǎn)化的二級(jí)模型。而當(dāng)成員屬性類(lèi)型就是NSDictionary的話(huà)就表明,我們本就想讓成員屬性是一個(gè)字典,不需要進(jìn)行模型的轉(zhuǎn)換。

id value = dict[key];

if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"])

{

? ? ? // 進(jìn)行二級(jí)轉(zhuǎn)換。

}

獲取要轉(zhuǎn)換的模型類(lèi)型,這里需要對(duì)propertyType成員屬性類(lèi)型做一些處理,因?yàn)閜ropertyType返回給我們成員屬性類(lèi)型的是**@\"Mode\",我們需要對(duì)他進(jìn)行截取為Mode**。這里需要注意的是\只是轉(zhuǎn)義符,不占位。

// @\"Mode\"去掉前面的@\"

NSRange range = [propertyType rangeOfString:@"\""];

propertyType = [propertyType substringFromIndex:range.location + range.length];

// Mode\"去掉后面的\"

range = [propertyType rangeOfString:@"\""];

propertyType = [propertyType substringToIndex:range.location];

獲取需要轉(zhuǎn)換類(lèi)的類(lèi)對(duì)象,將字符串轉(zhuǎn)化為類(lèi)名。

Class modelClass =? NSClassFromString(propertyType);

判斷如果類(lèi)名不為空則調(diào)用分類(lèi)的modelWithDict方法,傳value字典,進(jìn)行二級(jí)模型轉(zhuǎn)換,返回二級(jí)模型在賦值給value。

if (modelClass) {

? ? ? value =? [modelClass modelWithDict:value];

}?

這里可能有些繞,重新理一下,我們通過(guò)判斷value是字典并且需要進(jìn)行二級(jí)轉(zhuǎn)換,然后將value字典轉(zhuǎn)化為模型返回,并重新賦值給value,最后給一級(jí)模型中相對(duì)應(yīng)的key賦值模型value即可完成二級(jí)字典對(duì)模型的轉(zhuǎn)換。

最后附上二級(jí)轉(zhuǎn)換的完整方法

+ (instancetype)modelWithDict:(NSDictionary *)dict{

? ? // 1.創(chuàng)建對(duì)應(yīng)類(lèi)的對(duì)象

? ? id objc = [[self alloc] init];

? ? // count:成員屬性總數(shù)

? ? unsigned int count = 0;

? // 獲得成員屬性列表和成員屬性數(shù)量

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

? ? ? ? // 獲取字典的value key:屬性名 value:字典的值

? ? ? ? id value = dict[key];

? ? ? ? // 獲取成員屬性類(lèi)型

? ? ? ? NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

? ? ? ? // 二級(jí)轉(zhuǎn)換

? ? ? ? // value值是字典并且成員屬性的類(lèi)型不是字典,才需要轉(zhuǎn)換成模型

? ? ? ? if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {

? ? ? ? ? ? // 進(jìn)行二級(jí)轉(zhuǎn)換

? ? ? ? ? ? // 獲取二級(jí)模型類(lèi)型進(jìn)行字符串截取,轉(zhuǎn)換為類(lèi)名

? ? ? ? ? ? NSRange range = [propertyType rangeOfString:@"\""];

? ? ? ? ? ? propertyType = [propertyType substringFromIndex:range.location + range.length];

? ? ? ? ? ? range = [propertyType rangeOfString:@"\""];

? ? ? ? ? ? propertyType = [propertyType substringToIndex:range.location];

? ? ? ? ? ? // 獲取需要轉(zhuǎn)換類(lèi)的類(lèi)對(duì)象

? ? ? ? ? Class modelClass =? NSClassFromString(propertyType);

? ? ? ? ? // 如果類(lèi)名不為空則進(jìn)行二級(jí)轉(zhuǎn)換

? ? ? ? ? ? if (modelClass) {

? ? ? ? ? ? ? ? // 返回二級(jí)模型賦值給value

? ? ? ? ? ? ? ? value =? [modelClass modelWithDict:value];

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if (value) {

? ? ? ? ? ? // KVC賦值:不能傳空

? ? ? ? ? ? [objc setValue:value forKey:key];

? ? ? ? }

? ? }

? ? // 返回模型

? ? return objc;

}

16.?MJExtention 的底層實(shí)現(xiàn)

#import"NSObject+Model.h"#import<objc/message.h>// class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 獲取屬性列表@implementationNSObject(Model)/**

字典轉(zhuǎn)模型

@param dict 傳入需要轉(zhuǎn)模型的字典

@return 賦值好的模型

*/+ (instancetype)modelWithDict:(NSDictionary*)dict {

idobjc = [[selfalloc] init];

//思路: runtime遍歷模型中屬性,去字典中取出對(duì)應(yīng)value,在給模型中屬性賦值

// 1.獲取模型中所有屬性 -> 保存到類(lèi)

// ivar:下劃線(xiàn)成員變量 和 Property:屬性

// 獲取成員變量列表

// class:獲取哪個(gè)類(lèi)成員變量列表

// count:成員變量總數(shù)

//這個(gè)方法得到一個(gè)裝有成員變量的數(shù)組

//class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)intcount =0;

// 成員變量數(shù)組 指向數(shù)組第0個(gè)元素

Ivar *ivarList = class_copyIvarList(self, &count);

// 遍歷所有成員變量

for(inti =0; i < count; i++) {

// 獲取成員變量?

userIvar ivar = ivarList[i];

// 獲取成員變量名稱(chēng),即將C語(yǔ)言的字符轉(zhuǎn)為OC字符串

NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];

// 獲取成員變量類(lèi)型,用于獲取二級(jí)字典的模型名字

NSString*type = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];

//? 將type這樣的字符串@"@\"User\"" 轉(zhuǎn)成 @"User"

type = [type stringByReplacingOccurrencesOfString:@"@\""withString:@""];? ? ? ?

?type = [type stringByReplacingOccurrencesOfString:@"\""withString:@""];

// 成員變量名稱(chēng)轉(zhuǎn)換key,即去掉成員變量前面的下劃線(xiàn)

NSString*key = [ivarName substringFromIndex:1];/

/ 從字典中取出對(duì)應(yīng)value dict[@"user"] -> 字典

idvalue = dict[key];

// 二級(jí)轉(zhuǎn)換

// 并且是自定義類(lèi)型,才需要轉(zhuǎn)換

if([value isKindOfClass:[NSDictionaryclass]] && ![type containsString:@"NS"]) {

// 只有是字典才需要轉(zhuǎn)換

Class className =NSClassFromString(type);

// 字典轉(zhuǎn)模型value = [className modelWithDict:value];? ? ? ?

?}

// 給模型中屬性賦值 key:user value:字典 -> 模型

if(value) {? ? ??

? ? ? [objc setValue:value forKey:key];??

}? ??

?}

returnobjc;

}

@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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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