iOS 關(guān)聯(lián)對(duì)象

概述

關(guān)聯(lián)對(duì)象顧名思義,就是給對(duì)象關(guān)聯(lián)對(duì)象的意思塞栅,一個(gè)對(duì)象可以關(guān)聯(lián)多個(gè)其他對(duì)象者铜,這些對(duì)象通過key來(lái)區(qū)分,存儲(chǔ)對(duì)象值時(shí)放椰,可以指明“存儲(chǔ)策略”作烟,用以維護(hù)“內(nèi)存管理語(yǔ)義”,存儲(chǔ)策略由名為objc_AssociationPolicy的枚舉所定義庄敛,枚舉類型如下:

    OBJC_ASSOCIATION_ASSIGN = 0,           /**等效 @property 屬性 assign */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**等效 @property 屬性 nonatomic, retain*/
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**等效 @property 屬性 nonatomic, copy */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**等效 @property 屬性 retain*/
    OBJC_ASSOCIATION_COPY = 01403          /**等效 @property 屬性 copy */

可以看出俗壹,這些枚舉類型涵蓋了我們用@property指定存儲(chǔ)策略時(shí)各種類型(在ARC中strong與retain對(duì)應(yīng))。
關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的過程需要用到三個(gè)方法藻烤,即runtime庫(kù)中的三個(gè)API绷雏,如下:

/** 
 * 使用給定的鍵和關(guān)聯(lián)策略头滔,給指定的對(duì)象設(shè)置關(guān)聯(lián)值
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 */
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
/** 
 * 使用給定的對(duì)象和關(guān)聯(lián)鍵,返回關(guān)聯(lián)值
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @return The value associated with the key \e key for \e object.
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/** 
 * 移除給定對(duì)象的所有關(guān)聯(lián)對(duì)象
 * 
 * @param object An object that maintains associated objects.
 * @note 這個(gè)方法是用于清理對(duì)象的所有關(guān)聯(lián)對(duì)象的涎显,相當(dāng)于使一個(gè)對(duì)象回到“原始狀態(tài)”坤检,
 * 不能用此方法去刪除關(guān)聯(lián),刪除關(guān)聯(lián)應(yīng)該使用objc_setAccociatedObject()方法,給此方法的value參數(shù)傳nil就可以刪除對(duì)應(yīng)關(guān)聯(lián)對(duì)象期吓。
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)

使用場(chǎng)景

通常我們想給一個(gè)現(xiàn)有的類添加屬性時(shí)早歇,一般是創(chuàng)建一個(gè)繼承自現(xiàn)有類的子類,然后再給子類添加屬性讨勤,這在開發(fā)過程中非常常見箭跳,比喻自定義一個(gè)tableViewCell的類,它往往就是繼承自UITableViewCell類潭千。但是如果給UITableViewCell的category添加屬性就行不通了谱姓,另外,給一個(gè)創(chuàng)建好的UITableViewCell對(duì)象添加一個(gè)屬性刨晴,也是行不通的屉来,這個(gè)時(shí)候關(guān)聯(lián)對(duì)象就可以派上用場(chǎng)了。
下面來(lái)看看實(shí)例狈癞,這樣會(huì)更好理解些茄靠。

實(shí)例

實(shí)例分兩個(gè)部分,先創(chuàng)建好一個(gè)工程蝶桶,做好準(zhǔn)備慨绳。

給分類添加屬性

給ViewController創(chuàng)建一個(gè)名為info的分類,并給這個(gè)分類添加一個(gè)名為name的NSString類型的屬性莫瞬,如下:

#import "ViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface ViewController (Info)

@property (nonatomic, copy)NSString *name;

@end

NS_ASSUME_NONNULL_END

編譯一下儡蔓,會(huì)發(fā)現(xiàn)報(bào)如下的警告:


我們知道在分類中是不能添加實(shí)例變量,但是可以添加屬性疼邀,不過添加的屬性是不會(huì)自動(dòng)生成setter和getter方法的喂江。

為什么不能添加實(shí)例變量呢?
類的內(nèi)存布局在編譯時(shí)期就已經(jīng)確定了,category是運(yùn)行時(shí)才加載的,此時(shí)早已經(jīng)確定了內(nèi)存布局所以無(wú)法添加實(shí)例變量旁振,如果添加實(shí)例變量就會(huì)破壞category的內(nèi)部布局获询。

通常setter和getter方法中是要用到屬性對(duì)應(yīng)實(shí)例變量的,既然分類不能添加實(shí)例變量拐袜,那又要怎樣實(shí)現(xiàn)setter和getter方法呢吉嚣?使用關(guān)聯(lián)對(duì)象方法!
直接上代碼

#import "ViewController+Info.h"
#import <objc/runtime.h>

@implementation ViewController (Info)

-(NSString *)name{
    return objc_getAssociatedObject(self, _cmd);
}

-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @selector(name), name,  OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

解釋下上面的代碼

  • 方法objc_getAssociatedObject()中的第二個(gè)參數(shù)為什么傳“_cmd”?

_cmd是什么蹬铺?
_cmd是指當(dāng)前方法的一個(gè)SEL類型的指針尝哆,SEL可理解成方法選擇器 - @selector(),就是取類成員方法的編號(hào),他的行為基本可以等同C語(yǔ)言的中函數(shù)指針(函數(shù)地址),只不過C語(yǔ)言中,可以把函數(shù)名直接賦給一個(gè)函數(shù)指針甜攀,而Object-C的類不能直接應(yīng)用函數(shù)指針秋泄,這樣只能做一個(gè)@selector語(yǔ)法來(lái)取,他的結(jié)果是SEL類型琐馆,用assgin修飾。

該方法第二個(gè)參數(shù)的類型為const void * 表示是一個(gè)指針恒序,該指針可以指向任意類型的值瘦麸,但它指向的值必須是常量。剛好_cmd就是一個(gè)指針歧胁,指向的是當(dāng)前方法的編號(hào)滋饲,是個(gè)常量,所以傳_cmd沒毛病喊巍。傳_cmd的一個(gè)好處就是即保證唯一性又不需要聲明參數(shù)屠缭。

  • 方法objc_setAssociatedObject()中的第四個(gè)參數(shù)為什么要傳OBJC_ASSOCIATION_COPY_NONATOMIC?

因?yàn)樵谔砑觧ame屬性的時(shí)候使用的是(nonatomic, copy)修飾的崭参,所以對(duì)應(yīng)的存儲(chǔ)策略就應(yīng)該是OBJC_ASSOCIATION_COPY_NONATOMIC勿她。

下面使用一下在分類中創(chuàng)建的屬性name

self.name = @"我是大廈";
NSLog(@"%@",self.name);

編譯、運(yùn)行阵翎,結(jié)果如下:

2019-02-15 17:21:39.565185+0800 Classes[23957:5481755] 我是大廈

這就是在分類中通過關(guān)聯(lián)對(duì)象的方式添加屬性的例子。

給對(duì)象添加屬性

如果沒有分類之剧,沒有繼承父類的子類郭卫,直接給某個(gè)系統(tǒng)的類的一個(gè)對(duì)象添加屬性,該怎么辦呢背稼?先看下面代碼

    UISwitch *swit = [[UISwitch alloc] initWithFrame:CGRectMake(10, 100, 200, 50)];
    [self.view addSubview:swit];
    [swit addTarget:self action:@selector(switAction:) forControlEvents:(UIControlEventValueChanged)];

如果要想給swit對(duì)象添加屬性贰军,可以通過關(guān)聯(lián)對(duì)象來(lái)實(shí)現(xiàn),在這里給swit對(duì)象關(guān)聯(lián)一個(gè)block對(duì)象和NSString 類型的對(duì)象蟹肘,代碼如下:

#import "ViewController.h"
#import "ViewController+Info.h"
#import <objc/runtime.h>

// 聲明關(guān)聯(lián)鍵
const void *associatedBlockKey = @"dd";
const void *associatedTypeKey = @"ss";

@interface ViewController ()

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    
    UISwitch *swit = [[UISwitch alloc] initWithFrame:CGRectMake(10, 100, 200, 50)];
    [self.view addSubview:swit];
    [swit addTarget:self action:@selector(switAction:) forControlEvents:(UIControlEventValueChanged)];
    
    // 創(chuàng)建要關(guān)聯(lián)的對(duì)象值
    void (^block)(BOOL isOn) = ^(BOOL isOn){
        NSLog(@"%d",isOn);
    };
    NSString *type = @"love";
    
    // 設(shè)置關(guān)聯(lián)
    objc_setAssociatedObject(swit, associatedBlockKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(swit, associatedTypeKey, type, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

然后使用關(guān)聯(lián)對(duì)象词疼,在事件方法中使用:

-(void)switAction:(UISwitch*)sender{
    void (^block)(BOOL isOn) = objc_getAssociatedObject(sender, associatedBlockKey);
    block(sender.isOn);
    NSString *tempType = objc_getAssociatedObject(sender, associatedTypeKey);
    NSLog(@"%@",tempType);
}

編譯運(yùn)行,觸發(fā)開關(guān)的一開一關(guān)操作帘腹,打印結(jié)果如下:

2019-02-15 17:35:58.277116+0800 Classes[24171:5507080] 1
2019-02-15 17:35:58.277282+0800 Classes[24171:5507080] love
2019-02-15 17:35:59.280377+0800 Classes[24171:5507080] 0
2019-02-15 17:35:59.280512+0800 Classes[24171:5507080] love

可以看出贰盗,swit對(duì)象關(guān)聯(lián)對(duì)象后,每次操作開關(guān)阳欲,都可以獲取到關(guān)聯(lián)對(duì)象的值舵盈。到這一步就完成了對(duì)象關(guān)聯(lián)對(duì)象的操作。

有點(diǎn)需要注意球化,既然是關(guān)聯(lián)對(duì)象秽晚,就只能關(guān)聯(lián)對(duì)象,所以如果關(guān)聯(lián)的不是對(duì)象筒愚,比喻關(guān)聯(lián)NSInteger類型的值赴蝇,就不可以,會(huì)報(bào)錯(cuò)巢掺。

總結(jié)

關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)用到了三個(gè)方法句伶,分別是:

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedBbjects

關(guān)聯(lián)對(duì)象一般來(lái)說(shuō)用在兩種場(chǎng)景劲蜻,即:

  • 給分類添加屬性
  • 給對(duì)象添加關(guān)聯(lián)對(duì)象

雖然關(guān)聯(lián)對(duì)象有一定的實(shí)用性,但是需要注意熄阻,在實(shí)際場(chǎng)景中不常用到關(guān)聯(lián)對(duì)象斋竞,因?yàn)樗幸欢ǖ谋锥耍绻麨E用秃殉,代碼容易失控坝初,而且代碼可讀性要相對(duì)差些,最主要是出了bug 也不好找钾军,為什么這么說(shuō)呢鳄袍?仔細(xì)想一下,關(guān)聯(lián)的對(duì)象吏恭,在運(yùn)行時(shí)才會(huì)被關(guān)聯(lián)拗小,只有被關(guān)聯(lián)后,才確定其內(nèi)存管理語(yǔ)義樱哼,這樣的話哀九,在關(guān)聯(lián)對(duì)象大量使用時(shí)出現(xiàn)bug后,排查問題的難度大大增加搅幅。所以如果有別的辦法可以用阅束,就不要選擇使用關(guān)聯(lián)對(duì)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茄唐,一起剝皮案震驚了整個(gè)濱河市凌简,隨后出現(xiàn)的幾起案子朱庆,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诲祸,死亡現(xiàn)場(chǎng)離奇詭異待侵,居然都是意外死亡陌选,警方通過查閱死者的電腦和手機(jī)替蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纳令,“玉大人挽荠,你說(shuō)我怎么就攤上這事∑郊ǎ” “怎么了圈匆?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)捏雌。 經(jīng)常有香客問我跃赚,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任纬傲,我火速辦了婚禮满败,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叹括。我一直安慰自己算墨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布汁雷。 她就那樣靜靜地躺著净嘀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侠讯。 梳的紋絲不亂的頭發(fā)上挖藏,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音厢漩,去河邊找鬼膜眠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溜嗜,可吹牛的內(nèi)容都是我干的宵膨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼炸宵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柄驻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起焙压,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抑钟,沒想到半個(gè)月后涯曲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡在塔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年幻件,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛔溃。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绰沥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贺待,到底是詐尸還是另有隱情徽曲,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布麸塞,位于F島的核電站秃臣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奥此,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一弧哎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稚虎,春花似錦撤嫩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蜕径,卻和暖如春两踏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兜喻。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工梦染, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朴皆。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓帕识,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親遂铡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肮疗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354