runtime中一個(gè)重要功能就是關(guān)聯(lián)對(duì)象。作用就是運(yùn)行時(shí)動(dòng)態(tài)的給對(duì)象添加變量扔茅。
本文分三個(gè)部分介紹關(guān)聯(lián)變量:
1. 關(guān)聯(lián)變量是如何實(shí)現(xiàn)。
???那么runtime是如何實(shí)現(xiàn)動(dòng)態(tài)添加的呢秸苗?直接上結(jié)論
關(guān)聯(lián)對(duì)象并沒(méi)有存放在對(duì)象的實(shí)體中召娜,而是runtime維護(hù)了一個(gè)全局二維map來(lái)管理所有關(guān)聯(lián)對(duì)象。
如果你對(duì)iOS實(shí)現(xiàn)過(guò)程不敢興趣惊楼,那么你可以關(guān)閉這一篇文章了玖瘸。但是如果你想了解可以繼續(xù)看下去
來(lái)看看runtime源碼,objc_setAssociatedObject的具體實(shí)現(xiàn)
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
//關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
//移除關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
通過(guò)這兩個(gè)方法就可以輕易的找到關(guān)聯(lián)對(duì)象的實(shí)際調(diào)用方法是_object_get_associative_reference和_object_set_associative_reference
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
函數(shù)中涉及幾個(gè)4個(gè)重要的數(shù)據(jù)結(jié)構(gòu):
??1.AssociationsManager //管理全局AssociationsHashMap
??2.AssociationsHashMap //存放對(duì)象的關(guān)聯(lián)對(duì)象map的map(key為傳入的object檀咙,value為map雅倒,也就是ObjectAssociationMap)
??3.ObjectAssociationMap //存放關(guān)聯(lián)對(duì)象的map(key為傳入的key,value為關(guān)聯(lián)對(duì)象)
??4.ObjcAssociation //關(guān)聯(lián)對(duì)象實(shí)體包含了value和policy兩個(gè)重要信息(policy決定了value的內(nèi)存管理方式)
文字介紹可能比較繞弧可,引用一下另一位間書(shū)大神的文章的圖
http://www.reibang.com/p/4b463169a84a
四個(gè)結(jié)構(gòu)圖關(guān)系一目了然蔑匣。所以:
runtime中維護(hù)了一個(gè)全局二維map來(lái)管理關(guān)聯(lián)對(duì)象。
從源碼可以發(fā)現(xiàn)以下幾點(diǎn)信息:
1.從源碼中可以看到如果傳入value是nil,會(huì)把對(duì)應(yīng)的關(guān)聯(lián)變量為key的相關(guān)數(shù)據(jù)也就是ObjcAssociation從這個(gè)變量的字典中移除
2.一個(gè)對(duì)象可以關(guān)聯(lián)多個(gè)對(duì)象裁良。
3.所有對(duì)象的關(guān)聯(lián)變量信息都在要一個(gè)AssociationsHashMap 里凿将。
來(lái)看看_object_remove_assocations的源碼:
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
從源碼可以看出也就是移除了對(duì)象的ObjectAssociationMap這個(gè)二級(jí)map。
查看這個(gè)方法的調(diào)用价脾,可以看到在NSObject的dealloc的時(shí)候會(huì)自動(dòng)調(diào)用這個(gè)方法牧抵,移除該變量所有關(guān)聯(lián)信息。
2. 關(guān)聯(lián)變量的內(nèi)存管理方式彼棍。
???iOS關(guān)于關(guān)聯(lián)變量的內(nèi)存管理有以下幾種方式:
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. */
};
這里需要謹(jǐn)慎使用OBJC_ASSOCIATION_ASSIGN灭忠,因?yàn)樵陉P(guān)聯(lián)變量被釋放之后,再次取這個(gè)變量會(huì)引起crash座硕。OBJC_ASSOCIATION_ASSIGN并不會(huì)持有變量類似于_unsafe_unreatin的內(nèi)存管理方式弛作,所以這個(gè)變量釋放之后對(duì)應(yīng)的關(guān)聯(lián)對(duì)象體并沒(méi)有沒(méi)移除,而變量指針會(huì)成為野指針华匾。
- (void)associatiedTest
{
{
NSObject* associatedObject = NSObject.new;
objc_setAssociatedObject(self, @selector(associatiedTest), associatedObject, OBJC_ASSOCIATION_ASSIGN);
id obj = objc_getAssociatedObject(self, @selector(associatiedTest));
}
id obj = objc_getAssociatedObject(self, @selector(associatiedTest));
}
這里最后一行取關(guān)聯(lián)變量的時(shí)候就會(huì)崩潰映琳,因?yàn)閍ssociatedObject已經(jīng)超出作用區(qū)被釋放掉了。
關(guān)于關(guān)聯(lián)變量的內(nèi)存管理在代碼中只能看到如下代碼:
//對(duì)關(guān)聯(lián)的變量做內(nèi)存管理
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
也就是對(duì)變量發(fā)送SEL_retain或者SEL_copy消息蜘拉。但是具體怎么實(shí)現(xiàn)的原子操作我并沒(méi)有找到萨西,如果有知道這里具體怎么實(shí)現(xiàn)的小伙伴麻煩留言告訴我。
3. 如何實(shí)現(xiàn)weak特性的關(guān)聯(lián)變量
1.最佳方案:利用block捕獲特性和__weak變量特性
#import "NSObject+Weak.h"
#import <objc/runtime.h>
typedef id(^WeakWrapperBlock)(void);
@implementation NSObject (Weak)
- (id)weakAssObject {
WeakWrapperBlock wrapper = objc_getAssociatedObject(self, @selector(weakAssObject));
return wrapper ? wrapper() : nil;
}
- (void)setWeakAssObject:(id)obj {
id __weak weakObject = obj;
WeakWrapperBlock wrap = ^{
return weakObject;
};
objc_setAssociatedObject(self, @selector(weakAssObject), wrap, OBJC_ASSOCIATION_COPY);
}
@end
利用__weak變量會(huì)在對(duì)象dealloc銷(xiāo)毀之后置空的特性旭旭。用Block去捕獲這個(gè)__weak變量谎脯,然后把block設(shè)置成關(guān)聯(lián)變量,注意這里要用Copy持寄,因?yàn)閏opy會(huì)把block拷貝到堆上源梭。
這樣實(shí)現(xiàn)只是模擬了weak特性,在對(duì)象釋放后通過(guò)getter方法獲取到的值是空稍味。但是废麻,實(shí)際上這個(gè)關(guān)聯(lián)變量并沒(méi)有被置為空。
類似于用block的方式模庐,還可以用于一個(gè)中間容器實(shí)現(xiàn)烛愧,實(shí)現(xiàn)原理類似:
2.用中間容器實(shí)現(xiàn)
///WeakAssociatedObjectWrapper.h
@interface WeakAssociatedObjectWrapper : NSObject
@property (nonatomic, weak) id weakObject;
@end
@implementation WeakAssociatedObjectWrapper
@end
#import "WeakAssociatedObjectWrapper.h"
#import <objc/runtime.h>
@implementation NSObject (WeakAssociatedObject)
- (NSObject *)assObject {
id object = objc_getAssociatedObject(self, @selector(assObject));
if ([object isKindOfClass:WeakAssociatedObjectWrapper.class]) {
if ([(WeakAssociatedObjectWrapper*)object weakObject] == NULL) {
objc_setAssociatedObject(self, @selector(assObject), NULL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return [(WeakAssociatedObjectWrapper*)object weakObject];
}
return object;
}
- (void)setAssObject:(NSObject *)assObject {
if (assObject) {
WeakAssociatedObjectWrapper* wrapper = objc_getAssociatedObject(self, @selector(assObject));
if (!wrapper) {
WeakAssociatedObjectWrapper* wrapper = WeakAssociatedObjectWrapper.new;
objc_setAssociatedObject(self, @selector(assObject), wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
wrapper.assObject = assObject;
}
}
@end
這種方式其實(shí)和block原理類似,只是實(shí)現(xiàn)了一個(gè)wrapper替代block掂碱,wrapper內(nèi)有一個(gè)weak屬性怜姿。用把關(guān)聯(lián)變量賦值給這個(gè)weak屬性。
這么實(shí)現(xiàn)的問(wèn)題疼燥,同樣是在關(guān)聯(lián)變量釋放時(shí)社牲,wrapper并沒(méi)有為空,只有在通過(guò)getter取值的時(shí)候悴了,會(huì)返回空搏恤,同時(shí)發(fā)現(xiàn)變量已經(jīng)為空了违寿,再去通過(guò)objc_setAssociatedObject把wrapper置為空。
4. 利用關(guān)聯(lián)變量監(jiān)控一個(gè)對(duì)象的釋放
實(shí)現(xiàn)一個(gè)方法熟空,對(duì)傳入的對(duì)象進(jìn)行監(jiān)控藤巢,在對(duì)象釋放時(shí),調(diào)用一個(gè)block息罗。
@interface ObjectDeallocMonitor : NSObject
- (instancetype)initWithBlock:(dispatch_block_t)block;
@end
@interface ObjectDeallocMonitor()
@property (nonatomic, copy) dispatch_block_t block;
@end
@implementation ObjectDeallocMonitor
- (instancetype)initWithBlock:(dispatch_block_t)block {
self = [super init];
if (self) {
self.block = block;
}
return self;
}
- (void)dealloc {
if (self.block) {
self.block();
}
}
@end
把ObjectDeallocMonitor的實(shí)例作為一個(gè)關(guān)聯(lián)對(duì)象設(shè)置給對(duì)應(yīng)的對(duì)象掂咒,在對(duì)象被釋放后,會(huì)同時(shí)釋放對(duì)應(yīng)的關(guān)聯(lián)變量迈喉,調(diào)用ObjectDeallocMonitor的dealloc方法绍刮。
@interface NSObject (DeallocMonitor)
- (void)addDeallocMonitorFor:(id)obj withBlock:(dispatch_block_t)block;
@end
#import "ObjectDeallocMonitor.h"
#import <objc/runtime.h>
@implementation NSObject (DeallocMonitor)
- (void)addDeallocMonitorFor:(id)obj withBlock:(dispatch_block_t)block {
if (!obj || !block) return;
ObjectDeallocMonitor* monitor = [[ObjectDeallocMonitor alloc] initWithBlock:block];
objc_setAssociatedObject(self, @"DeallocMonitor", monitor, OBJC_ASSOCIATION_RETAIN);
}
相應(yīng)第三方庫(kù):http://tutuge.me/2017/03/11/TTGDeallocTaskHelper/
5. 關(guān)聯(lián)變量的應(yīng)用場(chǎng)景和坑
??關(guān)于關(guān)聯(lián)變量的應(yīng)用場(chǎng)景,Mattt大神給出過(guò)建議:
引用:
http://www.reibang.com/p/4b463169a84a
https://nshipster.com/associated-objects/