Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼 在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)
有時候類的實(shí)例可能是某種機(jī)制所創(chuàng)建,而開發(fā)者無法令這種機(jī)制創(chuàng)建出自己所寫的子類實(shí)例。
可以給某對象關(guān)聯(lián)許多其他對象。這些對象會通過“鍵”來區(qū)分。
存儲對象值的時候,可以指明“存儲策略”,用以維護(hù)相應(yīng)的“內(nèi)存管理語義”。
存儲策略由名為objc_AssociationPolicy 的枚舉定義。
如果關(guān)聯(lián)對象成了屬性,那么它就會具備對應(yīng)的語義。
關(guān)聯(lián)類型 | 等效的@property屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
以下方法用來管理關(guān)聯(lián)對象:
① void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy);
此方法以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象值。
② void objc_getAssociatedObject(id object, void *key);
此方法根據(jù)給定的鍵從某對象中獲取相應(yīng)的關(guān)聯(lián)對象值。
③ void objc_removeAssociatedObjects(id object);
此方法移除指定對象的全部關(guān)聯(lián)對象。
設(shè)置關(guān)聯(lián)對象用的鍵是個“不透明的指針”,即所指向的數(shù)據(jù)結(jié)構(gòu)不局限于某種特定類型的指針。再設(shè)置關(guān)聯(lián)對象值時,若想令兩個鍵匹配到同一個值,則二者必須時完全相同的指針才行。跟NSDictionary不一樣。NSDictionary認(rèn)為“isEqual:”返回YES,則二者相同。故設(shè)置關(guān)聯(lián)對象值時,通常使用靜態(tài)全局變量做鍵。
關(guān)聯(lián)對象用法
開發(fā)iOS時經(jīng)常用到UIAlertView類,該類提供了一種標(biāo)準(zhǔn)視圖,可向用戶展示警告信息.當(dāng)用戶按下按鈕關(guān)閉該視圖時,需要委托協(xié)議(delegate protocol)來處理此動作,但是先要設(shè)置好這個委托機(jī)制,就得把創(chuàng)建警告視圖和處理按鈕動作的代碼分開.由于代碼分作兩塊,所以取締來有點(diǎn)亂.比方說,我們在使用UIAlertView時,一般都會這么寫:
- (IBAction)buttonClick {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"測試" message:@"提示信息" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil];
[alert show];
}
#pragma mark - UIAlertViewDelegate
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
// 取消操作
NSLog(@"取消操作");
} else if(buttonIndex == 1){
// 確定操作
NSLog(@"定操作");
}
}
如果想在同一個類里處理多個警告信息視圖,那么代碼就會變得更為復(fù)雜,我們必須在delegate方法中檢查傳入的alertView參數(shù),并據(jù)此選用相應(yīng)的邏輯.要是能在創(chuàng)建警告視圖的時候直接把處理每個按鈕的邏輯都寫好,那就簡單多了.這可以通過關(guān)聯(lián)對象來做.創(chuàng)建完警告視圖之后,設(shè)定一個與之關(guān)聯(lián)的"塊"(block),等到執(zhí)行delegate方法時再將其讀取出來.此方案的實(shí)現(xiàn)代碼如下:
#import <objc/runtime.h>
static void *MKAlertViewKey = @"MKAlertViewKey";
- (IBAction)buttonClick {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"測試" message:@"提示信息" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
// 取消操作
NSLog(@"取消操作");
} else if(buttonIndex == 1){
// 確定操作
NSLog(@"確定操作");
}
};
objc_setAssociatedObject(alert, MKAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
#pragma mark - UIAlertViewDelegate
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, MKAlertViewKey);
block(buttonIndex);
}
以這種方式改寫之后,創(chuàng)建警告視圖與處理操作結(jié)果的代碼都放在一起了,這樣比原來更易讀懂,因?yàn)槲覀儫o需在兩部分代碼之間來回游走,即可明白警告視圖的用處.但是,采用該方案時需要注意block可能捕獲(capture)某些變量,這也許會造成循環(huán)引用(retain cycle).
正如大家所見,這種做法很有用,但是只應(yīng)該在其他辦法行不通時采取考慮用它.若是濫用,則很快就會令代碼失控,使其難于調(diào)試.循環(huán)引用產(chǎn)生的原因很難查明,因?yàn)殛P(guān)聯(lián)的時候才定義的,而不是在接口中預(yù)先定好的.使用這種寫法時要小心,不能僅僅因?yàn)槟程幙梢杂迷搶懛ň鸵欢ㄒ盟?創(chuàng)建這種UIAlertView還有個辦法,那就是從中繼承子類,把block保存為子類中的屬性.如果需要多次用到alert視圖,那么這種做法比使用關(guān)聯(lián)對象要好.
要點(diǎn)
- 可以通過"關(guān)聯(lián)對象"機(jī)制把兩個對象連起來.
- 定義關(guān)聯(lián)對象時可指定內(nèi)存管理語義,用以模仿定義屬性時所采用的"擁有關(guān)系"與"非擁有關(guān)系".
- 只有在其他做法不可行時才應(yīng)選關(guān)聯(lián)對象,因?yàn)檫@種做法通常會引入難于查找的bug.