概述
關(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ì)象。