10. 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
“關聯(lián)對象”(Associated Object) 是指動態(tài)創(chuàng)建一個指針從一個對象指向另外一個對象,并且遵循相應的“內(nèi)存管理語義”,相當于動態(tài)添加一個屬性。
關聯(lián)的類型
存儲對象值的時候,可以指明“存儲策略”(storage policy),用以維護相應的“內(nèi)存管理語義”。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /** assign < Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**nonatomic, retain< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**nonatomic, copy< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**retain< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**copy< Specifies that the associated object is copied.
* The association is made atomically. */
};

對象關聯(lián)類型
關聯(lián)的相關api
設置關聯(lián)對象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
/*
此方法以給定的鍵和策略為某對象設置關聯(lián)對象值(將值value與對象object關聯(lián)起來)
參數(shù)key:const void * 類型,將來可以通過key取出這個存儲的值
參數(shù)policy:存儲策略(assign、copy、retain)
*/
獲取關聯(lián)對象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/*此方法根據(jù)給定的鍵從某對象中獲取相應的關聯(lián)對象值 */
移除關聯(lián)對象
void objc_removeAssociatedObjects(id _Nonnull object)
/*此方法移除指定對象的全部關聯(lián)對象 */
以靜態(tài)全局變量作為key
設置關聯(lián)對象的key和NSDictionary中的key不一樣。其在術(shù)語上屬于“不透明的指針”。
NSDictionary中,兩個key如果isEqual方法返回YES,則認為兩個key相同。
而這里要完全相同。鑒于此,在設置關聯(lián)對象值時,通常使用靜態(tài)全局變量做鍵。
關聯(lián)對象用法舉例
1. 給分類添加屬性
分類的作用:在不改變原來類內(nèi)容的基礎上,可以為類增加一些方法。使用注意:
分類只能增加方法,不能增加成員變量(使用objc/runtime中的objc_setAssociatedObject(關聯(lián))可以給分類添加屬性)。
分類方法實現(xiàn)中可以訪問原來類中聲明的成員變量。
分類可以重新實現(xiàn)原來類中的方法,但是會覆蓋掉原來的方法,會導致原來的方法沒法再使用。
方法調(diào)用的優(yōu)先級:分類(最后參與編譯的分類優(yōu)先) --> 原來類 --> 父類。
@interface NSObject (EOC_CX)
/**
* 為每一個對象添加一個name屬性
*/
@property (nonatomic,copy) NSString *name;
/**
* 為每個對象添加一個View屬性
*/
@property (nonatomic,strong) UIView *booksView;
/**
* 為每個對象添加一個是否被選中屬性
*/
@property(nonatomic, assign) BOOL isSelected;
@end
- 第一種寫法
#import "NSObject + EOC_CX .h"
#import <objc/runtime.h>
// 使用對象關聯(lián)需引入#import <objc/runtime.h>頭文件
@implementation NSObject (EOC_CX)
// 用一個字節(jié)來存儲key值,設置為靜態(tài)私有變量,避免外界修改
static void *nameKey;
- (void)setName:(NSString *)name
{
// 將某個值與某個對象關聯(lián)起來,將某個值存儲到某個對象中
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &nameKey);
}
static void *booksViewKey;
- (void)setBooksView:(UIView *) booksView
{
objc_setAssociatedObject(self, &booksViewKey, booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, &booksViewKey);
}
//將bool類型轉(zhuǎn)變成NSNumber類型來進行添加屬性 這樣儲存策略為OBJC_ASSOCIATION_COPY_NONATOMIC
static void *isSelectedKey;
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, &isSelectedKey, @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [((NSNumber *) objc_getAssociatedObject(self, &isSelectedKey)) boolValue];
}
@end
- 第二種寫法(該用法取自于: forkingdog / UITableView-FDTemplateLayoutCell 中的用法)
#import "NSObject + EOC_CX .h"
#import <objc/runtime.h>
// 使用對象關聯(lián)需引入#import <objc/runtime.h>頭文件
@implementation NSObject (EOC_CX)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &nameKey 或者 @selector(name).
}
- (void)setBooksView:(UIView *)booksView
{
objc_setAssociatedObject(self, @selector(booksView), booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &booksKey 或者 @selector(booksView).
}
//將bool類型轉(zhuǎn)變成NSNumber類型來進行添加屬性 這樣儲存策略為OBJC_ASSOCIATION_COPY_NONATOMIC
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, @selector(isSelected), @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [objc_getAssociatedObject(self, _cmd) boolValue];
//_cmd 代替了 &isSelectedKey 或者 @selector(isSelected).
}
@end
2. 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
- (void)viewDidLoad {
[super viewDidLoad];
[self showAlertView];
}
- (void)showAlertView
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"UIAlertView" message:@"what do you do" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"sure", nil];
// 將邏輯定義到代碼塊里面
void(^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
NSLog(@"%ld",buttonIndex);
} else {
NSLog(@"%ld",buttonIndex);
}
};
// 使用對象關聯(lián)需引入#import <objc/runtime.h>頭文件
// 對象關聯(lián) block 用OBJC_ASSOCIATION_COPY_NONATOMIC
objc_setAssociatedObject(alert, @selector(alertView:clickedButtonAtIndex:), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void(^block)(NSInteger) = objc_getAssociatedObject(alertView, _cmd);
block(buttonIndex);
}
3. 傳遞數(shù)據(jù)
- (void)viewDidLoad {
[super viewDidLoad];
// static const char associatedButtonkey
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:@"點我" forState:UIControlStateNormal];
[self.view addSubview:btn];
[btn setFrame:CGRectMake(50, 50, 50, 50)];
btn.backgroundColor = [UIColor redColor];
[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)click:(UIButton *)sender
{
NSString *message = @"你是誰";
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"我要傳值·" delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil];
alert.delegate = self;
[alert show];
// 使用對象關聯(lián)需引入#import <objc/runtime.h>頭文件
//把alert和message字符串關聯(lián)起來,作為alertview的一部分,關鍵詞就是msgstr,之后可以使用objc_getAssociatedObject從alertview中獲取到所關聯(lián)的對象,便可以訪問message或者btn了
//即實現(xiàn)了關聯(lián)傳值
objc_setAssociatedObject(alert, @"msgstr", message,OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(alert, @"btn property",sender,OBJC_ASSOCIATION_ASSIGN);
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//通過 objc_getAssociatedObject獲取關聯(lián)對象
NSString *messageString =objc_getAssociatedObject(alertView, @"msgstr");
UIButton *sender = objc_getAssociatedObject(alertView, @"btn property");
NSLog(@"%ld",buttonIndex);
NSLog(@"%@",messageString);
NSLog(@"%@",[[sender titleLabel] text]);
//使用函數(shù)objc_removeAssociatedObjects可以斷開所有關聯(lián)。通常情況下不建議使用這個函數(shù),因為他會斷開所有關聯(lián)。只有在需要把對象恢復到“原始狀態(tài)”的時候才會使用這個函數(shù)。
}
要點
可以通過“關聯(lián)對象”機制來把兩個對象連起來。
定義關聯(lián)對象時可指定內(nèi)存管理語義,用以模仿定義屬性時采用的擁有關系與非擁有關系。
只有在其他做法不可行時才應選用關聯(lián)對象,因為這種做法通常會引入難于查找的bug。