一、前言
Associated Objects(關(guān)聯(lián)對象)是什么坐求?什么時候用蚕泽?為什么要用?怎么用桥嗤?
最開始用到關(guān)聯(lián)對象是源于一個需求(廢話须妻,肯定是源于需求)。
大家都知道泛领,Button的點擊事件荒吏,一定是將本身傳入?yún)?shù):
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSLog(@"Btn did click ...");
}
如果想要傳入一個特定的參數(shù)呢?
- 當(dāng)時我想傳的參數(shù)是整型渊鞋,于是我想到了tag(那時的我還不知道關(guān)聯(lián)對象)
- tag其實是用來標(biāo)記不同的Button對象绰更,但此時瞧挤,我也很無奈。儡湾。特恬。就先借用一下吧,哈哈
也就是這樣??????
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
btn.tag = 110;
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSLog(@"Btn did click ...tag = %zd", sender.tag);
}
后來又有需求徐钠,要傳的參數(shù)是字符串癌刽,甚至是對象。尝丐。显拜。
就在此時我注意到了關(guān)聯(lián)對象(Associated Objects)
二、關(guān)聯(lián)對象的介紹
1.關(guān)聯(lián)對象解決的問題
我們知道爹袁,在 Objective-C 中可以通過 Category (類別远荠、分類,反正你們懂得)給一個現(xiàn)有的類添加屬性失息,但是卻不能添加實例變量譬淳,這似乎成為了 Objective-C 的一個明顯短板,關(guān)聯(lián)對象就可以解決這個問題根时。
2.如何用關(guān)聯(lián)對象
- 首先要引入 runtime
#import <objc/runtime.h>
- API主要就是(來自系統(tǒng)文件runtime.h的介紹)??????
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
官方的解釋已經(jīng)很清晰了瘦赫,就不過多解讀了(綁定、獲取蛤迎、移除),值得注意的一點是:objc_removeAssociatedObjects
是移除一個對象的所有關(guān)聯(lián)對象含友,將該對象恢復(fù)成“原始”狀態(tài)替裆,這樣的操作風(fēng)險太大,所以一般的做法是通過給 objc_setAssociatedObject
函數(shù)傳入 nil 來移除某個已有的關(guān)聯(lián)對象窘问。如下這樣??????
objc_setAssociatedObject(self, &key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- 關(guān)于key的問題
- 聲明 static char kAssociatedObjectKey; 使用 &kAssociatedObjectKey 作為 key 值;
- 聲明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; 使用 kAssociatedObjectKey 作為 key 值辆童;
- 用 selector ,使用 getter 方法的名稱作為 key 值惠赫。
- 關(guān)于policy(關(guān)聯(lián)策略)的問題
OBJC_ASSOCIATION_ASSIGN
等價屬性@property (assign) or @property (unsafe_unretained)
弱引用關(guān)聯(lián)對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC
等價屬性@property (strong, nonatomic)
強引用關(guān)聯(lián)對象把鉴,且為非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC
等價屬性@property (copy, nonatomic)
復(fù)制關(guān)聯(lián)對象,且為非原子操作
OBJC_ASSOCIATION_RETAIN
等價屬性@property (strong, atomic)
強引用關(guān)聯(lián)對象儿咱,且為原子操作
OBJC_ASSOCIATION_COPY
等價屬性@property (copy, atomic)
復(fù)制關(guān)聯(lián)對象庭砍,且為原子操作
具體內(nèi)容可以參考官方文檔,這里就不copy了
三混埠、用關(guān)聯(lián)對象解決上述問題
- 傳整型數(shù)據(jù)
NSString *const kButtonKey = @"kButtonKey";
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
objc_setAssociatedObject(btn, &kButtonKey, @110, OBJC_ASSOCIATION_ASSIGN);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSInteger value = [objc_getAssociatedObject(sender, &kButtonKey) integerValue];
NSLog(@"btn did click ...value = %zd", value);
}
- 傳對象數(shù)據(jù)
NSString *const kButtonKey = @"kButtonKey";
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
Person *person = [[Person alloc] init];
person.name = @"LiMing";
objc_setAssociatedObject(btn, &kButtonKey, person, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
Person *person = objc_getAssociatedObject(sender, &kButtonKey);
NSLog(@"person`s name = %@", person.name);
}
四怠缸、關(guān)聯(lián)對象用于Category
以實現(xiàn)UIBarButtonItem的擴展為例子,為其增加紅點的功能钳宪,其中大量的使用了關(guān)聯(lián)對象
需求:
1.顯示小紅點
2.顯示數(shù)字紅點
3.即有小紅點又有數(shù)字紅點時揭北,優(yōu)先顯示數(shù)字紅點
4.可自定義紅點顏色(默認(rèn)是紅色[UIColor redColor])
5.數(shù)字紅點數(shù)目大于99時扳炬,顯示99+
具體代碼如下:??????
#import <UIKit/UIKit.h>
@interface UIBarButtonItem (Badge)
@property (assign, nonatomic) UIColor *badgeColor;
- (void)configBadgeWithBigNum:(NSInteger)bigNum small:(BOOL)isOn;
@end
#import "UIBarButtonItem+Badge.h"
#import <objc/runtime.h>
NSString *const ZYBarButtonItem_hasBadgeKey = @"ZYBarButtonItem_hasBadgeKey";
NSString *const ZYBarButtonItem_badgeKey = @"ZYBarButtonItem_badgeKey";
NSString *const ZYBarButtonItem_badgeSizeKey = @"ZYBarButtonItem_badgeSizeKey";
NSString *const ZYBarButtonItem_badgeOriginXKey = @"ZYBarButtonItem_badgeOriginXKey";
NSString *const ZYBarButtonItem_badgeOriginYKey = @"ZYBarButtonItem_badgeOriginYKey";
NSString *const ZYBarButtonItem_badgeColorKey = @"ZYBarButtonItem_badgeColorKey";
NSString *const ZYBarButtonItem_badgeSizeWKey = @"ZYBarButtonItem_badgeSizeWKey";
@interface UIBarButtonItem ()
@property (nonatomic, assign) CGFloat badgeSizeW;
@property (strong, nonatomic) UILabel *badge;
@property (assign, nonatomic) CGFloat badgeOriginX;
@property (assign, nonatomic) CGFloat badgeOriginY;
@property (assign, nonatomic) CGFloat badgeSize;
@property BOOL hasBadge;
@end
@implementation UIBarButtonItem (Badge)
- (void)initBadge {
UIView *superview = nil;
if (self.customView) {
superview = self.customView;
superview.clipsToBounds = NO;
} else if ([self respondsToSelector:@selector(view)] && [(id)self view]) {
superview = [(id)self view];
}
[superview addSubview:self.badge];
// 默認(rèn)設(shè)置 default configure
self.badgeColor = [UIColor redColor];
self.badgeSize = 10;
self.badgeSizeW = 10;
self.badgeOriginX = 28;
self.badgeOriginY = 8;
self.badge.hidden = YES;
self.badge.layer.masksToBounds = YES;
self.badge.font = [UIFont boldSystemFontOfSize:12];
self.badge.textAlignment = NSTextAlignmentCenter;
self.badge.textColor = [UIColor whiteColor];
}
- (void)showBadge {
self.badge.hidden = NO;
}
- (void)hideBadge {
self.badge.hidden = YES;
}
- (void)refreshBadge {
self.badge.frame = (CGRect){self.badgeOriginX,self.badgeOriginY,self.badgeSizeW,self.badgeSize};
self.badge.backgroundColor = self.badgeColor;
self.badge.layer.cornerRadius = self.badgeSize/2;
}
#pragma mark ---------- badge getter & setter function -----------
- (UILabel *)badge {
UILabel *badge = (UILabel *)objc_getAssociatedObject(self, &ZYBarButtonItem_badgeKey);
if (!badge) {
badge = [[UILabel alloc] init];
[self setBadge:badge];
[self initBadge];
}
return badge;
}
- (void)setBadge:(UILabel *)badge {
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeKey, badge, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIColor *)badgeColor {
return objc_getAssociatedObject(self, &ZYBarButtonItem_badgeColorKey);
}
- (void)setBadgeColor:(UIColor *)badgeColor {
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeColorKey, badgeColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
-(CGFloat)badgeSize {
NSNumber *number = objc_getAssociatedObject(self, &ZYBarButtonItem_badgeSizeKey);
return number.floatValue;
}
-(void)setBadgeSize:(CGFloat)badgeSize {
NSNumber *number = [NSNumber numberWithDouble:badgeSize];
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeSizeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
- (CGFloat)badgeSizeW {
NSNumber *number = objc_getAssociatedObject(self, &ZYBarButtonItem_badgeSizeWKey);
return number.floatValue;
}
- (void)setBadgeSizeW:(CGFloat)badgeSizeW {
NSNumber *number = [NSNumber numberWithDouble:badgeSizeW];
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeSizeWKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
-(CGFloat)badgeOriginX {
NSNumber *number = objc_getAssociatedObject(self, &ZYBarButtonItem_badgeOriginXKey);
return number.floatValue;
}
-(void)setBadgeOriginX:(CGFloat)badgeOriginX {
NSNumber *number = [NSNumber numberWithDouble:badgeOriginX];
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
-(CGFloat)badgeOriginY {
NSNumber *number = objc_getAssociatedObject(self, &ZYBarButtonItem_badgeOriginYKey);
return number.floatValue;
}
-(void)setBadgeOriginY:(CGFloat)badgeOriginY {
NSNumber *number = [NSNumber numberWithDouble:badgeOriginY];
objc_setAssociatedObject(self, &ZYBarButtonItem_badgeOriginYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
- (void)setHasBadge:(BOOL)hasBadge {
if (hasBadge) {
[self showBadge];
}else{
[self hideBadge];
}
NSNumber *number = [NSNumber numberWithBool:hasBadge];
objc_setAssociatedObject(self, &ZYBarButtonItem_hasBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)hasBadge {
NSNumber *number = objc_getAssociatedObject(self, &ZYBarButtonItem_hasBadgeKey);
return number.boolValue;
}
#pragma mark - Public
- (void)configBadgeWithBigNum:(NSInteger)bigNum small:(BOOL)isOn {
if (bigNum > 0) {
self.hasBadge = YES;
self.badgeSize = 18;
self.badgeOriginY = 6;
NSString *numStr = [NSString stringWithFormat:@"%zd", bigNum];
if (bigNum < 10) {
self.badgeSizeW = 18;
} else if (bigNum < 100) {
self.badgeSizeW = 25;
} else {
self.badgeSizeW = 30;
numStr = @"99+";
}
self.badge.text = numStr;
} else if (isOn) {
self.hasBadge = YES;
self.badgeSizeW = 10;
self.badgeSize = 10;
self.badgeOriginY = 8;
self.badge.text = nil;
} else {
self.hasBadge = NO;
}
}
@end
五、寫在最后
- 關(guān)聯(lián)對象與被關(guān)聯(lián)對象本身的存儲并沒有直接的關(guān)系搔体,它是存儲在單獨的哈希表中的恨樟;
- 關(guān)聯(lián)對象的五種關(guān)聯(lián)策略與屬性的限定符非常類似,在絕大多數(shù)情況下疚俱,我們都會使用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
的關(guān)聯(lián)策略厌杜,這可以保證我們持有關(guān)聯(lián)對象; - 關(guān)聯(lián)對象的釋放時機與移除時機并不總是一致计螺,比如用關(guān)聯(lián)策略
OBJC_ASSOCIATION_ASSIGN
進(jìn)行關(guān)聯(lián)的對象夯尽,很早就已經(jīng)被釋放了,但是并沒有被移除登馒,而再使用這個關(guān)聯(lián)對象時就會造成 Crash 匙握。
Associated.jpg