52個有效方法(10) - 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)

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
#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ù)。
}
要點
  1. 可以通過“關聯(lián)對象”機制來把兩個對象連起來。

  2. 定義關聯(lián)對象時可指定內(nèi)存管理語義,用以模仿定義屬性時采用的擁有關系與非擁有關系。

  3. 只有在其他做法不可行時才應選用關聯(liá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)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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