Associated Objects 關(guān)聯(lián)對象

Associated Objects 介紹

Associated Objects

<objc/runtime.h>中的函數(shù)號稱是iOS中最后一把神兵利刃,具有其他方式做不到的,能為應(yīng)用和框架提供強大功能的能力。但使用不當(dāng)也可能廢掉代碼的,一切代碼和邏輯都可能被異常糟糕的副作用影響。

就像和魔鬼做交易一樣常常讓人懷著巨大的恐懼。

歷史

對象關(guān)聯(lián)(或稱為關(guān)聯(lián)引用)本來是Objective-C 2.0運行時的一個特性,起始于OS X Snow LeopardiOS4。

核心函數(shù)3個 -- 允許將任何鍵值在允許時關(guān)聯(lián)到對象上

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedObjects

這有什么用呢?這允許開發(fā)者對已經(jīng)存在的類在擴展中添加自定義的屬性

實現(xiàn)的3種方式

  • static char
  • selector
  • _cmd_
1.使用 static char 實現(xiàn)

NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)

static char kAssociatedObjectKey;

- (id)associatedObject
{
    return objc_getAssociatedObject(self,&kAssociatedObjectKey);
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, &kAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
2.使用 selector 實現(xiàn)

NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
2.使用 _cmd_ 實現(xiàn)

NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
    return objc_getAssociatedObject(self, _cmd_);
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

關(guān)聯(lián)對象的行為

屬性根據(jù)枚舉類型objc_AssociationPolicy來決定被關(guān)聯(lián)在對象上的行為:

Behavior 與之等效的@property
OBJC_ASSOCIATION_ASSIGN @property (assign) 或@property (unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN @property (atomic, strong)
OBJC_ASSOCIATION_COPY @property (atomic, copy)

OBJC_ASSOCIATION_ASSIGN類型關(guān)聯(lián)對象的弱引用不代表weak的弱引用,行為上更像unsafe_unretained屬性。

對象銷毀時間

被關(guān)聯(lián)的對象在聲明周期內(nèi)比對象本身釋放要晚很多,在對象調(diào)用-dealloc中的object_dispose()中釋放。

刪除屬性

你或許想要使用objc_removeAssociatedObjects()來進行刪除操作,但官方文檔不建議手動調(diào)用這個函數(shù)。

這個函數(shù)可能會把其他用戶對其添加的屬性也移除了,正確的方式是調(diào)用objc_setAssociatedObject方法,傳入nil值來清楚一個關(guān)聯(lián)。

優(yōu)秀樣例

  • 添加私有屬性用于更好地去實現(xiàn)細節(jié)。
  • 添加public屬性來增強category的功能。
  • 創(chuàng)建一個用于KVO的關(guān)聯(lián)觀察者。

應(yīng)用舉例

1.UIImagePickerController 圖片選擇回調(diào)

關(guān)聯(lián)一個block實現(xiàn)完成選擇圖片后的回調(diào)。

UIImagePickerController+RFBlocks.h
@interface UIImagePickerController (RFBlocks)<UIImagePickerControllerDelegate, UINavigationControllerDelegate>

typedef void(^RFImagePickerFinishBlock)(NSDictionary *info);

@property (nonatomic, copy) RFImagePickerFinishBlock rf_finishBlock;

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock;

@end
UIImagePickerController+RFBlocks.m
static const char kFinishBlockKey;

@implementation UIImagePickerController (RFBlocks)

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock
{
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.rf_finishBlock = finishBlock;
    
    return picker;
}

- (RFImagePickerFinishBlock)rf_finishBlock {
    return objc_getAssociatedObject(self, &kFinishBlockKey);
}

- (void)setRf_finishBlock:(RFImagePickerFinishBlock)rf_finishBlock {
    self.delegate = self;
    
    objc_setAssociatedObject(self, &kFinishBlockKey, rf_finishBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

#pragma mark - UIImagePickerControllerDelegate

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    RFImagePickerFinishBlock block = self.rf_finishBlock;
    
    if (block)  block(info);
    
    [self dismissViewControllerAnimated:YES completion:nil];
    
    self.rf_finishBlock = nil;
}

@end

使用:

__weak typeof(self)weakSelf = self;

UIImagePickerController *picker =  [UIImagePickerController rf_imagePickerWithFinishBlock:^(NSDictionary *info) {
        NSLog(@"finish picke image\n info:%@",info);
        
        UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
        weakSelf.view.layer.contents = (id)img.CGImage;
    }];
[self presentViewController:picker animated:YES completion:nil];

或者

UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    
__weak typeof(self)weakSelf = self;

picker.rf_finishBlock = ^(NSDictionary *info) {
    NSLog(@"finish picke image\n info:%@",info);
    
    UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
    weakSelf.view.layer.contentsGravity = kCAGravityResizeAspect;
    weakSelf.view.layer.contents = (id)img.CGImage;
};

[self presentViewController:picker animated:YES completion:nil];

1.UIButton 按鈕事件回調(diào)

關(guān)聯(lián)一個按鈕事件block,當(dāng)觸發(fā)按鈕UIControlEventTouchUpInside事件時回調(diào)。

UIButton+RFBlcoks.h
@interface UIButton (RFBlcoks)

typedef void(^RFButtonClickBlock)(UIButton *button);

@property (nonatomic, copy) RFButtonClickBlock rf_buttonClickBlock;

@end
UIButton+RFBlcoks.m
static char kButttonClickBlockKey;

@implementation UIButton (RFBlcoks)

- (RFButtonClickBlock)rf_buttonClickBlock
{
    return objc_getAssociatedObject(self, &kButttonClickBlockKey);
}

- (void)setRf_buttonClickBlock:(RFButtonClickBlock)rf_buttonClickBlock
{
    objc_setAssociatedObject(self, &kButttonClickBlockKey, rf_buttonClickBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    [self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)buttonClicked
{
    if (self.rf_buttonClickBlock) {
        self.rf_buttonClickBlock(self);
    }
}

@end

使用:

self.button.rf_buttonClickBlock = ^(UIButton *button){
        NSLog(@"%@ clicked",button);
};

Demo地址:

原文發(fā)表于王若風(fēng)的技術(shù)博客

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,041評論 0 9
  • 在之前文章說過。category 可以添加方法,可以添加協(xié)議實現(xiàn),可以添加屬性,但是卻不能添加實例變量。那么如果在...
    MaZengyi閱讀 478評論 0 1
  • 我們知道,在 Objective-C 中可以通過 Category 給一個現(xiàn)有的類添加屬性,但是卻不能添加實例變量...
    iloveyou6415閱讀 854評論 0 5
  • 唉,最近工作壓力好大,早餐就吃兩個包子還在公司樓下買的…… 額,話說這兩者有什么關(guān)系嗎? 有啊,每天都加班到很晚都...
    老夫子的天地閱讀 403評論 0 1
  • 一輪圓月, 帶著游離的想念, 劃過那人的心底, 流失的溫度, 壓彎了月牙。
    夏天的小蘑菇閱讀 498評論 0 6

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