前言
從今天開(kāi)始,我們將要開(kāi)始逐步接近OC的動(dòng)態(tài)特性,慢慢揭開(kāi)OC底層runtime系統(tǒng)的神秘面紗,超強(qiáng)的動(dòng)態(tài)特性,是OC和一般面向?qū)ο蟮恼Z(yǔ)言最明顯的區(qū)別,也是這門(mén)古老的語(yǔ)言最具魅力的地方,如果可以正確的利用動(dòng)態(tài)特性,那么可以幫助我們方便的解決很多棘手的問(wèn)題.
我們今天來(lái)看的呢,是runtime庫(kù)里非常有用的一組函數(shù),Associated Object動(dòng)態(tài)關(guān)聯(lián)對(duì)象.這個(gè)功能,能夠讓我們動(dòng)態(tài)的對(duì)一個(gè)對(duì)象綁定相關(guān)聯(lián)的數(shù)據(jù).
有了這個(gè)功能,我們不僅能夠?yàn)樽约壕帉?xiě)的類(lèi)添加屬性,還能夠?yàn)橄到y(tǒng)框架里的類(lèi)動(dòng)態(tài)添加我們需要的屬性,甚至能夠在分類(lèi)里動(dòng)態(tài)添加屬性.我們這就來(lái)看看這個(gè)方便又強(qiáng)大的功能吧.
Associated Object使用場(chǎng)景
我們都有這樣的經(jīng)驗(yàn),我們希望在既有的類(lèi)上添加新的屬性,通常情況下:
如果這個(gè)類(lèi)是我們自己編寫(xiě)的,那么我們就可以方便的在文件中添加對(duì)應(yīng)的屬性就可以
如果這個(gè)類(lèi)是系統(tǒng)框架的,而并非我們自己編寫(xiě)的,那么,我們通常會(huì)采取集成該系統(tǒng)類(lèi),產(chǎn)生新的類(lèi),添加我們需要的方法和屬性
但是,很多時(shí)候,某個(gè)對(duì)象并不是我們產(chǎn)生,而是通過(guò)其它機(jī)制產(chǎn)生的,比如,系統(tǒng)為我們返回了一個(gè)系統(tǒng)類(lèi)的實(shí)例,但是,我們卻希望在這個(gè)實(shí)例加上這個(gè)類(lèi)原本并沒(méi)有的屬性,來(lái)記錄或存儲(chǔ)一些數(shù)據(jù),在這個(gè)時(shí)候我們按照常規(guī)的方法就不好解決了.
這時(shí),我們的Associated Object就要發(fā)揮它的作用了.
相關(guān)函數(shù)
Associated Object的相關(guān)函數(shù)在runtime.h 文件中,使用前,我們需要先引入該庫(kù).而該庫(kù)中和Associated Object相關(guān)的函數(shù)有三個(gè):
- objc_setAssociatedObject:用來(lái)給對(duì)象動(dòng)態(tài)綁定關(guān)聯(lián)對(duì)象(也就是添加相應(yīng)屬性)
- objc_getAssociatedObject:用來(lái)讀取對(duì)某對(duì)象動(dòng)態(tài)綁定的關(guān)聯(lián)對(duì)象(讀取相應(yīng)屬性)
- objc_removeAssociatedObjects:這個(gè)函數(shù)是用來(lái)解除某對(duì)象的所有關(guān)聯(lián)對(duì)象這項(xiàng)操作會(huì)將該對(duì)象所有的關(guān)聯(lián)對(duì)象都全部刪除.使用時(shí),我們要格外注意.
Associated Object存儲(chǔ)策略:
在調(diào)用objc_setAssociatedObject:函數(shù)時(shí),我們需要設(shè)置關(guān)聯(lián)對(duì)象的存儲(chǔ)策略,這類(lèi)似于property屬性的存儲(chǔ)策略關(guān)鍵字,這些存儲(chǔ)策略名稱(chēng)從字面上就可以輕易的與property屬性的存儲(chǔ)策略關(guān)鍵字輕松匹配,相信大家一定可以選對(duì)的.我就不再一一舉例了.
Associated Object存儲(chǔ)策略 | property屬性存儲(chǔ)策略關(guān)鍵字 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, strong |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong |
OBJC_ASSOCIATION_COPY | copy |
示例
說(shuō)了這么多,我們通過(guò)一個(gè)小小的例子,來(lái)感受下Associated Object的妙用吧
我們都有過(guò)這樣的經(jīng)歷,我們需要在用戶(hù)進(jìn)行過(guò)某些操作的時(shí)候觸發(fā)一個(gè)提醒框,即AlertView.
通常代碼會(huì)像這個(gè)樣子:
-(void)click
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"alert" message:@"msg" delegate:self cancelButtonTitle:@"cancle" otherButtonTitles:@"confirm", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}
但是經(jīng)常我們會(huì)在一個(gè)控制器中有多個(gè)潛在將會(huì)觸發(fā)的提醒框,如果是這樣,那么我們通常會(huì)為alertView指定tag,在代理方法里區(qū)分不同的提醒框,做不同處理,那么代碼就會(huì)變成這個(gè)樣子.
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (alertView.tag==0) {
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==1)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==2)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==3)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}
}
這樣的代碼,不僅繁瑣冗長(zhǎng),并不利于我們的代碼可讀性和高內(nèi)聚低耦合的設(shè)計(jì)原則
我們將要通過(guò)Associated Object方式,對(duì)該情況進(jìn)行處理
大概思路為,在編寫(xiě)alertView時(shí)為每一個(gè)alertView關(guān)聯(lián)一個(gè)處理Block,在代理方法中我們只要調(diào)用alertView關(guān)聯(lián)的處理block就可以了,這樣的代碼將會(huì)變得異常清晰,而且具有極高的可讀性我們一起來(lái)看一下吧:
我們需要先導(dǎo)入需要的文件,并聲明一個(gè)用來(lái)充當(dāng)關(guān)聯(lián)Key的字符常量:
#import <objc/runtime.h>
static char const alertDealBlockKey;
然后在編寫(xiě)提示框的時(shí)候,我們需要編寫(xiě)一個(gè)處理alertView的block,并和alertView進(jìn)行關(guān)聯(lián):
-(void)click
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"alert" message:@"msg" delegate:self cancelButtonTitle:@"cancle" otherButtonTitles:@"confirm", nil];
void (^alertDealBlock)(NSInteger btnIndex)=^(NSInteger btnIndex){
if (btnIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
};
objc_setAssociatedObject(alert, &alertDealBlockKey, alertDealBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
[alert show];
}
這樣處理過(guò)得alertView中就會(huì)綁定一個(gè)處理的block了,接下來(lái)我們?cè)赼lertview的代理方法中,僅僅需要將這個(gè)block取出并調(diào)用,就可以了:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^alertDealBlock)(NSInteger btnIndex)=objc_getAssociatedObject(alertView, &alertDealBlockKey);
alertDealBlock(buttonIndex);
}
這樣無(wú)論該文件中將有多少個(gè)alertView,我們的代理方法中也只需要這樣短短兩行的代碼就可以處理,是不是很簡(jiǎn)單呢,而且,在編寫(xiě)alertView的時(shí)候我們就可以順便把處理方式編寫(xiě)好,可讀性也大大提高,是不是還是很優(yōu)雅的呢?
總結(jié)
上面的這個(gè)例子只是一個(gè)Associated Object一個(gè)簡(jiǎn)單使用,還有很多妙用等待大家發(fā)現(xiàn),Associated Object之所以能夠動(dòng)態(tài)的為實(shí)例添加關(guān)聯(lián)對(duì)象,這要依附于我們強(qiáng)大的運(yùn)行時(shí)系統(tǒng),這點(diǎn)大家要好好理解,最后雖然Associated Object非常好用,但是也不建議大家濫用,只有在別的方式都不可行的情況下才建議大家使用關(guān)聯(lián)對(duì)象處理,因?yàn)槿绻愕木幋a有誤,產(chǎn)生問(wèn)題將非常難于查找,因?yàn)榫幾g器并不能夠檢測(cè)出運(yùn)行時(shí)才會(huì)關(guān)聯(lián)的對(duì)象的相關(guān)問(wèn)題,對(duì)于關(guān)聯(lián)對(duì)象,編譯器是有心無(wú)力的,保證它的正確性只能我們?nèi)藶槿z測(cè)驗(yàn)證