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

Effective Object-C 第10條:在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)

  • Effective Object-C 2.0 編寫高質(zhì)量iOS與OSX的52個有效方法-學(xué)習(xí)筆記

有時需要在對象中存放相關(guān)信息。這時我們通常會從對象所屬的類中繼承一個子類,然后改用這個子類對象。然而并非所有情況下都能這么做,有時候類的實(shí)例可能是由某種機(jī)制所創(chuàng)建的,而開發(fā)者無法令這種機(jī)制創(chuàng)建出自己縮寫的子類實(shí)例。OC中有一項(xiàng)強(qiáng)大的特性可以解決此問題,這就是關(guān)聯(lián)對象。

可以給某對象關(guān)聯(lián)許多其他對象,這些對象通過來區(qū)分。存儲對象值的時候,可以指明存儲策略,用以維護(hù)響應(yīng)的內(nèi)存管理語義。存儲策略由名為objc_AssociationPolicy的枚舉所定義,同時還列出了與之等效的@property屬性:假如關(guān)聯(lián)對象成為了屬性,那么它就會具備對應(yīng)的語義。

關(guān)聯(lián)類型 等效的@property語義
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC retain, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy

下列方法可以管理關(guān)聯(lián)對象:

//以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象值
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)

//根據(jù)給定的鍵從某對象中國區(qū)響應(yīng)的關(guān)聯(lián)對象值
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

//移除指定對象的全部關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id _Nonnull object)

我們可以把某對象想象成NSDictionary,把關(guān)聯(lián)到該對象的值理解為字典中的條目,于是,存取關(guān)聯(lián)對象的值就相當(dāng)于在NSDictionary對象上調(diào)用[dictionary setValue:value forKey:key][object objectForKey:key]方法。然而兩者之間有個重要的差別:設(shè)置關(guān)聯(lián)對象時用的是個不透明的指針。如果在兩個鍵上調(diào)用isEqual:方法的返回值是YES,那么NSDictionary就認(rèn)為兩者相等;然而在設(shè)置關(guān)聯(lián)對象值時,若想另兩個鍵匹配到同一個值,則二者必須是完全相同的指針才行。鑒于此,在設(shè)置關(guān)聯(lián)對象值時,通常使用靜態(tài)全局變量做鍵。

關(guān)聯(lián)對象用法舉例

拿UIAlertView類舉例,當(dāng)用戶按下按鈕關(guān)閉該視圖時,需要用委托協(xié)議來處理此動作,但是,要想設(shè)置好這個委托機(jī)制,就得把創(chuàng)建警告視圖和處理按鈕動作代碼分開。由于代碼分作兩塊,所以讀起來有點(diǎn)亂。

- (void)askUserQuestion {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"
                                                    message:@"what do you want to do?"
                                                   delegate:self
                                          cancelButtonTitle:@"cancel"
                                          otherButtonTitles:@"continue", nil];
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 0) {
        [self doCancel];
    } else {
        [self doContinue];
    }
}

如果想在同一個類里處理多個警告信息視圖,那么代碼就會變得更為復(fù)雜,我們必須在delegate方法中檢查傳入的alertView參數(shù),并據(jù)此選用相應(yīng)的邏輯。要是能在創(chuàng)建警告視圖的時候直接把處理每個按鈕的邏輯都寫好,那就簡單多了。這可以通過關(guān)聯(lián)對象來做。創(chuàng)建完警告視圖之后,設(shè)定一個與之關(guān)聯(lián)的塊(block),等到執(zhí)行delegate方法時再將其讀出來。代碼如下:

#import <objc/runtime.h>

static void *MyAlertViewKey = "MyAlertViewKey";

- (void)askUserQuestion {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"
                                                    message:@"what do you want to do?"
                                                   delegate:self
                                          cancelButtonTitle:@"cancel"
                                          otherButtonTitles:@"continue", nil];
    __weak typeof(self) weakSelf = self;
    void (^block)(NSInteger) = ^(NSInteger buttonIndex){
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (buttonIndex == 0) {
            [strongSelf doCancel];
        } else {
            [strongSelf doContinue];
        }
    };
    objc_setAssociatedObject(alert,
                             MyAlertViewKey,
                             block,
                             OBJC_ASSOCIATION_COPY);
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    void (^block)(NSInteger) = objc_getAssociatedObject(alertView, MyAlertViewKey);
    block(buttonIndex);
}

以這種方式改寫之后,創(chuàng)建警告視圖與處理操作結(jié)果的代碼都放在一起了,這樣比原來更易讀懂,因?yàn)槲覀儫o須在兩部分代碼之間來回游走,即可明白警告視圖的用處。block也容易造成“循環(huán)引用”。

這種做法很有用,但是只應(yīng)該在其他辦法行不通時才去考慮用它。若是濫用,則很快就會令代碼失控,使其難于調(diào)試?!把h(huán)引用”產(chǎn)生的原因很難查明,因?yàn)殛P(guān)聯(lián)對象之間的關(guān)系并沒有正式定義,其內(nèi)存管理語義是在關(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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