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 Leopard和iOS4。
核心函數(shù)3個 -- 允許將任何鍵值在允許時關(guān)聯(lián)到對象上
- objc_setAssociatedObject
- objc_getAssociatedObject
- objc_removeAssociatedObjects
這有什么用呢?這允許開發(fā)者對已經(jīng)存在的類在擴展中添加自定義的屬性
實現(xiàn)的3種方式
static charselector_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ù)博客
參考資料:
- Associated Objects -- NSHipster
- Objective-C Associated Objects -- King's Cocoa
- Objective-C Associated Objects 的實現(xiàn)原理-- 雷純峰的技術(shù)博客
- Associated Objects by Example -- Sebastian Rehnby