在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)

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.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容