有次在大牛羣看到一個問題:“如何給一個字典添加一個屬性(不能繼承)”,立馬蒙逼了,不能用繼承,難道用分類?但是分類貌似只能添加方法不能添加屬性啊,百思不得其解,直到後來接觸到了runtime才恍然大悟。
什麼是關聯對象
關聯對象是指某個OC對象通過一個唯一的key連接到一個類的實例上。
舉個例子:Duan是Person類的一個實例,他的狗dog(一個OC對象)通過一根繩子(key)被他牽着散步,這可以說Duan和nimo是關聯起來的,當然Duan可以牽着多個dog。
怎樣關聯對象
runtime提供給我們的方法:
//關聯對像
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關聯對像
id objc_getAssociatedObject(id object, const void *key)
//移除關聯的對像
void objc_removeAssociatedObjects(id object)
變量說明:
id object:被關聯的對像(eg:Duan)
const void *key:關聯的key,要求唯一
id value:關聯的對像(eg:dog)
objc_AssociationPolicy policy:內存管理的策略
objc_AssociationPolicy policy的enum值有:
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
當對象被釋放時,會根據這個策略來決定是否釋放關聯的對象,當策略是retain/copy時,會釋放(release)關聯的對象,當是assign,將不會釋放。
值得注意的是,我們不需要主動調用removeAssociated來解除關聯的對象,如果需要解除指定的對象,可以使用setAssociatedObject置nil來實現。
關聯對象的應用
1.添加公用屬性
這是最常用的一個模式,通常我們會在類的聲明裏面添加屬性,但是出於某些需求,我們需要在分類裏添加一個貨多個屬性的話,編譯器就會報錯,這個問題的解決方案就是實用runtime的關聯對象。
eg:我們需要自定義一個tabBar,並暴露公共的屬性和方法。
@interface UITabBarController (custom)
@property (nonatomic,strong) UIView *customTabBar;
@end
#import "UITabBarController+custom.h"
#import <objc/runtime.h>
@implementation UITabBarController (custom)
- (void)setCustomTabBar:(UITabBar *)customTabBar
{
//這裏使用方法的指針地址作爲唯一的key
objc_setAssociatedObject(self, @selector(customTabBar), customTabBar, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)customTabBar
{
return objc_getAssociatedObject(self, @selector(customTabBar));
}
// 其他方法。。。
@end
這樣我們就可以跟原生的tabbar一樣使用自定義的tabbar:
[self.tabBarController.customTabBar doSomeThing];
2.添加私有成員變量
有時候,需要在分類中添加不想暴露在公共聲明的成員變量。
eg:給按鈕添加點擊事件的回調
@interface UIButton (CallBack)
- (instancetype)initWithFrame:(CGRect)frame callBack:(void (^)(UIButton *))callBlock;
@end
@interface UIButton()
@property (nonatomic,copy) void (^callbackBlock)(UIButton * button);
@end
@implementation UIButton (CallBack)
- (void)setCallbackBlock:(void (^)(UIButton *))callbackBlock {
objc_setAssociatedObject(self, @selector(callbackBlock), callbackBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^)(UIButton *))callbackBlock {
return objc_getAssociatedObject(self, @selector(callbackBlock));
}
- (instancetype)initWithFrame:(CGRect)frame callBack:(void (^)(UIButton *))callBlock {
if (self == [super initWithFrame:frame]) {
self.callbackBlock = callBlock;
[self addTarget:self action:@selector(didClickAction:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)didClickAction:(UIButton *)button
{
self.callbackBlock(button);
}
3.關聯KVO觀察者
有時候我們在分類中使用KVO,推薦使用關聯的對象作爲觀察者,儘量避免對像觀察自身。
此應用模式就不再舉例了。