一舞吭、RunTime概念
RunTime簡(jiǎn)稱運(yùn)行時(shí)泡垃,我們總是聽(tīng)說(shuō)OC是動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)機(jī)制析珊,也就是系統(tǒng)在運(yùn)行時(shí)候的一些機(jī)制,其中最重要的是消息機(jī)制蔑穴。C語(yǔ)言忠寻,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù),如果調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)存和,而OC語(yǔ)言屬于動(dòng)態(tài)調(diào)用過(guò)程奕剃,在編譯時(shí)并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正的運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)函數(shù)來(lái)調(diào)用捐腿,當(dāng)調(diào)用該對(duì)象上某個(gè)方法纵朋,而該對(duì)象上沒(méi)有實(shí)現(xiàn)這個(gè)方法的時(shí)候,可以通過(guò)“消息轉(zhuǎn)發(fā)”進(jìn)行解決叙量,也就是說(shuō)倡蝙,在編譯截?cái)啵琌C可以調(diào)用任何函數(shù)绞佩,即使是這個(gè)函數(shù)沒(méi)有實(shí)現(xiàn)寺鸥,只要聲明過(guò)就不會(huì)報(bào)錯(cuò)。
二品山、OC調(diào)用方法在RunTime中的具體實(shí)現(xiàn)
《一》RunTime消息機(jī)制
消息機(jī)制是運(yùn)行時(shí)里面最重要的機(jī)制胆建,OC是動(dòng)態(tài)語(yǔ)言,本質(zhì)都是發(fā)送消息肘交,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)化為消息發(fā)送笆载,即:objc_msgSend(receiver, selector)
比如:
- OC代碼實(shí)例方法調(diào)用底層的實(shí)現(xiàn):
BackView *backView = [[BackView alloc] init];
[backView changeBgColor];
//編譯時(shí)底層轉(zhuǎn)化
//objc對(duì)象的isa指針指向他的類對(duì)象,從而可以找到對(duì)象上的方法
//SEL:方法編號(hào),根據(jù)方法編號(hào)就可以找到對(duì)應(yīng)方法的實(shí)現(xiàn)涯呻。
[backView performSelector:@selector(changeBgColor)];
//performSelector本質(zhì)即為運(yùn)行時(shí)凉驻,發(fā)送消息,誰(shuí)做事情就調(diào)用誰(shuí)
objc_msgSend(backView, @selector(changeBgColor));
// 帶參數(shù)
objc_msgSend(backView, @selector(changeBgColor:),[UIColor RedColor]);
- OC代碼類方法調(diào)用底層的實(shí)現(xiàn)
//本質(zhì)是將類名轉(zhuǎn)化成類對(duì)象复罐,初始化方法其實(shí)是創(chuàng)建類對(duì)象涝登。
[BackView changeBgColor];
//BackView 只是表示一個(gè)類名,調(diào)用方法其實(shí)是用的類對(duì)象去調(diào)用的效诅。(類對(duì)象既然稱為對(duì)象胀滚,那它也是一個(gè)實(shí)例。類對(duì)象中也有一個(gè)isa指針指向它的元類(meta class)乱投,即類對(duì)象是元類的實(shí)例咽笼。元類內(nèi)部存放的是類方法列表,根元類的isa指針指向自己戚炫,superclass指針指向NSObject類剑刑。)
//編譯時(shí)底層轉(zhuǎn)化
//RunTime 調(diào)用類方法同樣,類方法也是類對(duì)象去調(diào)用双肤,所以需要獲取類對(duì)象施掏,然后使用類對(duì)象去調(diào)用方法
Class backViewClass = [BackView class];
[backViewClass performSelector:@selector(changeBgColor)];
//performSelector本質(zhì)即為運(yùn)行時(shí)层宫,發(fā)送消息,誰(shuí)做事情就調(diào)用誰(shuí)
//類對(duì)象發(fā)送消息
objc_msgSend(backViewClass, @selector(changeBgColor));
// 帶參數(shù)
objc_msgSend(backViewClass, @selector(changeBgColor:),[UIColor RedColor]);
selector(SEL):是一個(gè)SEL方法選擇器其监。
SEL其主要作用是快速的通過(guò)SEL其主要作用是快速的通過(guò)方法名字查找到對(duì)應(yīng)方法的函數(shù)指針,然后調(diào)用其函數(shù)限匣。SEL其本身是一個(gè)Int類型的地址抖苦,地址中存放著方法的名字。
對(duì)于一個(gè)類中米死。每一個(gè)方法對(duì)應(yīng)著一個(gè)SEL锌历。所以一個(gè)類中不能存在2個(gè)名稱相同的方法,即使參數(shù)類型不同峦筒,因?yàn)镾EL是根據(jù)方法名字生成的究西,相同的方法名稱只能對(duì)應(yīng)一個(gè)SEL。
- 消息傳遞的底層實(shí)現(xiàn)
這里我們要先說(shuō)一下物喷,一個(gè)Objc對(duì)象如何進(jìn)行內(nèi)存布局的卤材,我們先看一下objc_class源碼:
// runtime.h(類在runtime中的定義)
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class,因?yàn)镺bjc的類的本身也是一個(gè)Object峦失,為了處理這個(gè)關(guān)系扇丛,runtime就創(chuàng)造了Meta Class,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時(shí)尉辑,實(shí)際上是把這個(gè)消息發(fā)給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息帆精,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存隧魄,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象卓练,這時(shí)會(huì)在method Lists中遍歷,如果cache了购啄,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率襟企。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
objc對(duì)象內(nèi)存布局:
<1>所有父類的成員變量和自己的成員變量都會(huì)存放在該對(duì)象所對(duì)應(yīng)的存儲(chǔ)空間中。
<2>每個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針闸溃,指向它的類對(duì)象整吆,類對(duì)象中存放著本對(duì)象的:
1、對(duì)象方法列表(對(duì)象能夠接受的消息列表辉川,保存再它所對(duì)應(yīng)的類對(duì)象中)
2表蝙、成員變量的列表
3、屬性列表
每一個(gè)類都有一個(gè)方法列表Method List乓旗,保存著類里面所有的方法府蛇,根據(jù)SEL傳入的方法編號(hào)找到方法,然后找到方法的實(shí)現(xiàn)屿愚,然后在方法的實(shí)現(xiàn)里面實(shí)現(xiàn)汇跨。
消息發(fā)送動(dòng)態(tài)查找對(duì)應(yīng)的方法
<1>實(shí)例對(duì)象調(diào)用方法后务荆,底層調(diào)用[objc performSelector:@selector(SEL)];
方法,編譯器將代碼轉(zhuǎn)化為objc_msgSend(receiver, selector)
穷遂。
<2>在objc_msgSend
函數(shù)中函匕,首先通過(guò)objc
的isa
指針找到objc
對(duì)應(yīng)的class
,在class
中先去cache
中通過(guò)SEL
查找對(duì)應(yīng)函數(shù)的method
蚪黑,如果找到則通過(guò)method
中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行盅惜。
<3>如果在cacha
中未找到,再去methodList
中查找忌穿,如果能找到抒寂,則將method
加入到cache
中,以方便下次查找掠剑,并通過(guò)method
中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行屈芜。
<4>如果在methodlist
中未找到,則去superClass
中去查找朴译,如果能找到井佑,則將method
加入到cache
中,以方便下次查找动分,并通過(guò)method
中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行毅糟。-
消息傳遞的過(guò)程
<接上↑>objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類澜公,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行姆另,即:objc_msgSend(receiver, selector)
。如果坟乾,在最頂層的父類中依然找不到相應(yīng)的方法時(shí)迹辐,程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前甚侣,objc的運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì):
<1> Method resolution
objc運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:
或者+resolveClassMethod:
(實(shí)例方法和類方法)明吩,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)殷费,那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程印荔,否則 ,運(yùn)行時(shí)就會(huì)移到下一步详羡,消息轉(zhuǎn)發(fā)(Message Forwarding)仍律。
<2> Message Forwarding-
<1>Fast forwarding
如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:
,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法实柠,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)水泉。 只要這個(gè)方法返回的不是nil和self,整個(gè)消息發(fā)送的過(guò)程就會(huì)被重啟,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象草则。否則钢拧,就會(huì)繼續(xù)Normal Fowarding
。 這里叫Fast炕横,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制源内。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象份殿,所以相對(duì)更快點(diǎn)姿锭。 -
<2>Normal forwarding
這一步是Runtime最后一次給你挽救的機(jī)會(huì)。首先它會(huì)發(fā)送-methodSignatureForSelector:
消息獲得函數(shù)的參數(shù)和返回值類型伯铣。如果-methodSignatureForSelector:
返回nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:
消息轮纫,程序這時(shí)也就掛掉了腔寡。如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:
消息給目標(biāo)對(duì)象掌唾。
-
<1>Fast forwarding
《二》使用RunTime動(dòng)態(tài)的添加對(duì)象的成員變量和方法
-
動(dòng)態(tài)添加方法
動(dòng)態(tài)給某各類添加方法放前,相當(dāng)于懶加載機(jī)制。這里我們以實(shí)例方法為例糯彬,首先我們先不實(shí)現(xiàn)對(duì)象方法凭语,當(dāng)調(diào)用performSelector:
方法的時(shí)候,再去動(dòng)態(tài)加載方法調(diào)用撩扒。[bg performSelector:@selector(changeBgColor)];
當(dāng)編譯時(shí)是不會(huì)報(bào)錯(cuò)的似扔,運(yùn)行時(shí)才會(huì)報(bào)錯(cuò),因?yàn)檫@里我們BaseView類中并沒(méi)有實(shí)現(xiàn)changeBgColor
這個(gè)方法搓谆,當(dāng)去類的Method List
中發(fā)現(xiàn)找不到changeBgColor
方法炒辉,會(huì)報(bào)錯(cuò)找不到這個(gè)方法。這里我們就用到了上面提到的消息轉(zhuǎn)發(fā)機(jī)制泉手。當(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)的類方法的時(shí)候斩萌,就會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel方法缝裤。所以通過(guò)這兩個(gè)方法就可以動(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為類添加一個(gè)方法以及實(shí)現(xiàn)搀崭。
動(dòng)態(tài)添加方法:
+(BOOL)resolveInstanceMethod:(SEL)sel
{
// 動(dòng)態(tài)添加changeBgColor方法
// 首先判斷sel是不是changeBgColor方法 也可以轉(zhuǎn)化成字符串進(jìn)行比較。
if (sel == @selector(changeBgColor)) {
/**
第一個(gè)參數(shù): cls:給哪個(gè)類添加方法
第二個(gè)參數(shù): SEL name:添加方法的編號(hào)
第三個(gè)參數(shù): IMP imp: 方法的實(shí)現(xiàn),函數(shù)入口瘤睹,函數(shù)名可與方法名不同(建議與方法名相同)
第四個(gè)參數(shù): types :方法類型升敲,需要用特定符號(hào),參考API
*/
class_addMethod(self, sel, (IMP) newChangeBgColor , "v@:");
// 處理完返回YES
return YES;
}
return [super resolveInstanceMethod:sel];
}
void newChangeBgColor(id self ,SEL _cmd)
{
}
types:方法類型表:
-
動(dòng)態(tài)添加變量
1-> 動(dòng)態(tài)獲取類中的所有屬性(包括私有)
Ivar *ivar = class_copyIvarList([self.baseView class], &count);
2->遍歷屬性找到對(duì)應(yīng)屬性字段
const char *varName = ivar_getName(var);
3->修改對(duì)應(yīng)的字段
object_setIvar(self.baseView, var, @"newName");
具體:
-(void)addNewName{
unsigned int count = 0;
Ivar *ivar = class_copyIvarList([baseView class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivar[i];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithUTF8String:varName];
if ([name isEqualToString:@"oldName"]) {
object_setIvar(baseView, var, @"newName");
break;
}
}
self.nameLabel.text = baseView.oldName;
}
《三》動(dòng)態(tài)交換方法
當(dāng)遇到所使用的系統(tǒng)方法或者不可修改的靜態(tài)庫(kù)方法功能不夠時(shí)轰传,需要給此類方法擴(kuò)展一些功能驴党。比如我們有個(gè)BaseView類中有個(gè)changeBgColor的方法,此時(shí)我們想在這個(gè)方法里做些操作获茬,我們定義一個(gè)newChangeBgColor的方法港庄。因?yàn)榻粨Q只需進(jìn)行一次,所以我們?cè)贐aseView的Categary中的load方法中恕曲,當(dāng)加載分類的時(shí)候交換方法即可鹏氧。交換方法的本質(zhì)其實(shí)是交換兩個(gè)方法的實(shí)現(xiàn)
即:
1根據(jù)SEL方法編號(hào)在Method List中找到方法
2交換兩個(gè)IMP指針指向的方法實(shí)現(xiàn)
+(void)load
{
// 獲取要交換的兩個(gè)方法
// 獲取類方法 用Method 接受一下
// class :獲取哪個(gè)類方法
// SEL :獲取方法編號(hào),根據(jù)SEL就能去對(duì)應(yīng)的類找方法佩谣。
Method oldChangeColorMethod = class_getClassMethod([UIImage class], @selector(changeBgColor));
// 獲取第二個(gè)類方法
Method newChangeColorMethod = class_getClassMethod([UIImage class], @selector(newChangeBgColor));
// 交換兩個(gè)方法的實(shí)現(xiàn) 方法一 把还,方法二。
method_exchangeImplementations(oldChangeColorMethod, newChangeColorMethod);
// IMP其實(shí)就是 implementation的縮寫:表示方法實(shí)現(xiàn)茸俭。
}
注意:交換方法的時(shí)候newMethod里就不能再調(diào)用oldMethod方法了吊履,因?yàn)檎{(diào)用oldMethod方法實(shí)質(zhì)上相當(dāng)于調(diào)用newMethod方法,會(huì)循環(huán)引用造成死循環(huán)调鬓。
《四》RunTim動(dòng)態(tài)添加屬性
XCode運(yùn)行在Category的.h文件聲明@property編譯通過(guò)艇炎,但運(yùn)行時(shí)如果沒(méi)有runtime處理,進(jìn)行賦值取值腾窝,就會(huì)報(bào)錯(cuò)缀踪。
-
@property的本質(zhì)是什么
@property = ivar + getter + setter;
說(shuō)人話:
“屬性”(property)有兩大概念:ivar(實(shí)例變量)、存取方法(access method = getter + setter)虹脯。
“屬性”(property)作為OC的一項(xiàng)特性辜贵,主要的作用就在于封裝對(duì)象總的數(shù)據(jù)。OC 對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量归形,實(shí)例變量一般通過(guò)“存取方法”(access method)來(lái)訪問(wèn)托慨,其中,“獲取方法”(getter)用于讀取變量值暇榴,而“設(shè)置方法”(setter)用于寫入變量值厚棵。在正規(guī)的OC編碼風(fēng)格中,存取方法有著嚴(yán)格的命名規(guī)范蔼紧,正因?yàn)橛辛诉@種嚴(yán)格的命名規(guī)范婆硬,所以O(shè)C可以根據(jù)名稱自動(dòng)創(chuàng)建出存取方法,其實(shí)也可以把屬性當(dāng)做一種關(guān)鍵字奸例,可以表示:
編譯器會(huì)自動(dòng)寫出一套存取方法彬犯,用以訪問(wèn)給定類型中具有給定名稱的變量向楼,所以你也可以這么說(shuō):@property=getter+setter;
比如下面的這個(gè)類:
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代碼寫出來(lái)的類與下面這種寫法等效:
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
而objc_property是一個(gè)結(jié)構(gòu)體谐区,包括name和attributes湖蜕,定義如下:
struct property_t {
const char *name;
const char *attributes;
};
而attributes本質(zhì)是objc_property_attribute_t,定義了property的一些屬性宋列,定義如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
而attributes的具體內(nèi)容是什么呢昭抒?其實(shí),包括:類型炼杖,原子性灭返,內(nèi)存語(yǔ)義和對(duì)應(yīng)的實(shí)例變量。
例如:我們定義一個(gè)string的property@property (nonatomic, copy) NSString *string;
坤邪,通過(guò) property_getAttributes(property)
獲取到attributes并打印出來(lái)之后的結(jié)果為T@"NSString",C,N,V_string
其中T就代表類型熙含,可參閱Type Encodings,C就代表Copy艇纺,N代表nonatomic婆芦,V就代表對(duì)于的實(shí)例變量。
ivar喂饥、getter、setter 是如何生成并添加到這個(gè)類中的?
“自動(dòng)合成”( autosynthesis)
完成屬性定義后肠鲫,編譯器會(huì)自動(dòng)編寫訪問(wèn)這些屬性所需的方法员帮,此過(guò)程叫做“自動(dòng)合成”(autosynthesis)。需要強(qiáng)調(diào)的是导饲,這個(gè)過(guò)程由編譯 器在編譯期執(zhí)行讹语,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼鹅搪。除了生成方法代碼 getter、setter 之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量抡草,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字千扔。在前例中砸烦,會(huì)生成兩個(gè)實(shí)例變量,其名稱分別為 _firstName 與 _lastName听盖。也可以在類的實(shí)現(xiàn)代碼里通過(guò) @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
屬性是怎么實(shí)現(xiàn)的呢胀溺?
1、OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset)皆看,這個(gè)偏移量是“硬編碼” (hardcode)仓坞,表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。
2腰吟、setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)
3无埃、ivar_list :成員變量列表
4、method_list :方法列表
5、prop_list :屬性列表
也就是說(shuō)我們每次在增加一個(gè)屬性,系統(tǒng)都會(huì)在 ivar_list 中添加一個(gè)成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個(gè)屬性的描述,然后計(jì)算該屬性在對(duì)象中的偏移量,然后給出 setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn),在 setter 方法中從偏移量的位置開(kāi)始賦值,在 getter 方法中從偏移量開(kāi)始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對(duì)象偏移量的指針類型進(jìn)行了類型強(qiáng)轉(zhuǎn).
如何在@protocol和category中使用@property嫉称?
1侦镇、在 protocol 中使用 property 只會(huì)生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對(duì)象能實(shí)現(xiàn)該屬性
2、category 使用 @property 也是只會(huì)生成 setter 和 getter 方法的聲明,但是不會(huì)自動(dòng)生成私有屬性澎埠,如果我們真的需要給 category 增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時(shí)的兩個(gè)函數(shù):
1虽缕、動(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ǔ)法為屬性賦值席怪。
下面這個(gè)也是一樣的:
-(void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
《RunTime字典轉(zhuǎn)模型》
通過(guò)給NSObject添加分類应闯,聲明并實(shí)現(xiàn)使用Runtime字典轉(zhuǎn)模型的類方法:
+ (instancetype)modelWithDict:(NSDictionary *)dict
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)不必要的資源抵卫。只保存最有用的屬性即可狮荔。
字典轉(zhuǎn)模型簡(jiǎn)要過(guò)程:
1、創(chuàng)建模型對(duì)象
2介粘、使用class_copyIvarList方法copy成員屬性列表
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
參數(shù)一:__unsafe_unretained Class cls : 獲取哪個(gè)類的成員屬性列表殖氏。這里是self,因?yàn)檎l(shuí)調(diào)用分類中類方法姻采,誰(shuí)就是self雅采。
參數(shù)二:unsigned int *outCount : 無(wú)符號(hào)int型指針,這里創(chuàng)建unsigned int型count慨亲,&count就是他的地址婚瓜,保證在方法中可以拿到count的地址為count賦值。傳出來(lái)的值為成員屬性總數(shù)刑棵。
返回值:Ivar * : 返回的是一個(gè)Ivar類型的指針 巴刻。指針默認(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];
}
4、使用ivar_getName(ivar)獲得成員屬性名片橡,因?yàn)槌蓡T屬性名返回的是C語(yǔ)言字符串妈经,將其轉(zhuǎn)化成OC 字符串
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
或者ivar_getTypeEncoding(ivar)方法
5、獲得的成員屬性名是帶的成員屬性锻全,去掉,獲得屬性名录煤,也就是字典的key鳄厌。
// 獲取key
NSString *key = [propertyName substringFromIndex:1];
6、獲取字典中key對(duì)應(yīng)的Value妈踊。
id value = dict[key];
7了嚎、給模型屬性賦值,并將模型返回
if (value) {
// KVC賦值:不能傳空
[objc setValue:value forKey:key];
}
return objc;
二級(jí)模型轉(zhuǎn)化方法:
+ (instancetype)modelWithDict:(NSDictionary *)dict{
// 1.創(chuàng)建對(duì)應(yīng)類的對(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];
// 獲取成員屬性類型
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 二級(jí)轉(zhuǎn)換
// value值是字典并且成員屬性的類型不是字典,才需要轉(zhuǎn)換成模型
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
// 進(jìn)行二級(jí)轉(zhuǎn)換
// 獲取二級(jí)模型類型進(jìn)行字符串截取廊营,轉(zhuǎn)換為類名
NSRange range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location + range.length];
range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringToIndex:range.location];
// 獲取需要轉(zhuǎn)換類的類對(duì)象
Class modelClass = NSClassFromString(propertyType);
// 如果類名不為空則進(jìn)行二級(jí)轉(zhuǎn)換
if (modelClass) {
// 返回二級(jí)模型賦值給value
value = [modelClass modelWithDict:value];
}
}
if (value) {
// KVC賦值:不能傳空
[objc setValue:value forKey:key];
}
}
// 返回模型
return objc;
}
總結(jié)
上述對(duì)RunTime的總結(jié)只是一些自己平時(shí)的積累歪泳,借鑒了一些好的博文資料加上自己的一些理解,還有很多東西沒(méi)有理解到位露筒,還請(qǐng)多多指教呐伞。