本文是Objective-C系列的第12篇,主要講述了關聯對象的底層結構和使用。c++
咱們在Objective-C(十)Category中講過,Category能添加協議、方法、屬性等,參考下面的結構體。git
//from objc-runtime-new.h
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
....
};
複製代碼
既然能夠添加屬性,那是否是能夠添加成員變量呢?github
答案是:不能夠。app
以下面的實例,調用後,Crash異步
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BFPerson setAge:]: unrecognized selector sent to instance 0x60000202c4b0'ide
//分類聲明
@interface BFPerson (BFBoy)
@property (nonatomic, assign) NSInteger age;
@end
//調用
BFPerson *person = [[BFPerson alloc] init];
person.age = 28;
複製代碼
因此,咱們得知:函數
Category能夠添加屬性,但不會添加對應的成員變量,也不會實現對應的setter、getter方法。工具
實例源碼參考01-Category成員變量。post
既然沒法直接給分類中添加,咱們能夠經過全局變量的方式來保存成員變量的值,且實現對應的setter、getter來模擬爲Category完整添加屬性(成員變量)的效果。ui
@interface BFPerson (BFBoy)
@property (nonatomic, assign) NSInteger age;
@end
@implementation BFPerson (BFBoy)
NSMutableDictionary *ages_;
+ (void)load
{
ages_ = [[NSMutableDictionary alloc] init];
}
- (void)setAge:(NSInteger)age
{
NSString *key = [NSString stringWithFormat:@"%p", self];
ages_[key] = @(age);
}
- (NSInteger)age
{
NSString *key = [NSString stringWithFormat:@"%p", self];
return [ages_[key] integerValue];
}
@end
複製代碼
上述方案有一些缺點:
關聯是指把兩個對象相互關聯起來,使得其中的一個對象做爲另一個對象的一部分。 關聯特性只有在Mac OS X V10.六、iOSV3.1以及之後的版本上纔是可用的。
使用關聯,咱們能夠不用修改類的定義而爲其對象增長存儲空間。這在咱們沒法訪問到類的源碼的時候或者是考慮到二進制兼容性的時候是很是有用。
關聯是基於關鍵字的,所以,咱們能夠爲任何對象增長任意多的關聯,每一個都使用不一樣的關鍵字便可。關聯是能夠保證被關聯的對象在關聯對象的整個生命週期都是可用的(在垃圾自動回收環境下也不會致使資源不可回收)。
示例代碼02-關聯對象
關鍵字是一個void類型的指針,必須惟一
經常使用靜態變量來做爲key。一般推薦key使用static char類型——使用指針或許更好,並只在getter和setter方法內部使用。
更簡單的方案是:直接使用選擇器(selector),由於SEL生成的時候就是一個惟一的常量。
policy
代表了value
內存語義,是經過賦值,保留引用仍是複製的方式進行關聯的;
policy
還代表原子的仍是非原子的。
這裏的關聯策略和聲明屬性時的很相似。這種關聯策略是經過使用預先定義好的常量來表示的。
在本文開始就提出,關聯對象做爲給Category添加成員變量是一種高效可行的方案
可是,咱們仍然須要提醒你:
category
自己就不該該是解決問題優先選擇的工具說到這裏,咱們在開發中經常看到一種,或者經歷過一種現象——某個開發者,在博客或其餘方式中得知某一個技術點,這個技術點具有一些特定——是巧妙的伎倆、hack手段或者是變通的解決方案,開發者這時候躍躍欲試,爲了用而用,在不徹底吃透該技術點或原理時,就貿然使用。
我給出的建議就是,**吃透原理,分析場景,**這些小伎倆再用到平時開發中。
下面給出了一些其餘開源庫中用到的場景:
添加私有變量來幫助實現細節 。當拓展一個內置類時,可能有必要跟蹤一些額外的狀態,這是關聯對象最廣泛的應用場景。
例如:AFNetworking中在UIImageView的分類中使用關聯對象來存儲一個請求操做對象(operation object),用於異步的從遠程獲取圖片。
使用關聯對象來代替X,其中X表明下面的一些項:
對應的API爲:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)
複製代碼
全局關聯對象管理類,其擁有一個map字典用於存放全部被關聯對象object的關聯對象。
class AssociationsManager {
static AssociationsHashMap *_map;
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
複製代碼
AssociationsHashMap
就是存放一個被關聯對象object的全部管理對象的字典,根據下面的類定義:
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
};
複製代碼
unordered_map
,後面三個參數,分別是hash
、equal
及allocator
函數的實現。其中該字典對應的:
disguised_ptr_t
類型ObjectAssociationMap
類型ObjectAssociationMap
是真正存儲object中關聯對象的載體。
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
};
複製代碼
其中該字典:
void *
指針類型,即傳進來的key。ObjcAssociation
類型,該類型存儲了傳進來的value
和policy
。ObjcAssociation
用於存放關聯對象的value
及policy
。
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
複製代碼
object
不能爲nilvalue
可爲nil,nil表示清除該關聯對象void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
if (new_value) {
// break any existing association.
// 1.value有值
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 1.1.1 找到該object對應的ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 1.1.2 在ObjectAssociationMap尋找key對應的ObjcAssociation
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 1.1.2.1 找到ObjcAssociation,將舊值保存在old_association
// 1.1.2.2 從新設新值
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 1.1.2.3 未找到對應ObjcAssociation,直接設新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// 1.2.1 未找到ObjectAssociationMap,即以前object未設置過關聯對象
// ObjectAssociationMap新增,ObjcAssociation新增
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// 2. value爲空
// 2.1 尋找object對應的ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 2.1.1 尋找key對應的ObjcAssociation
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 2.2.1.1 找到ObjcAssociation,將舊值保存在old_association
// 2.2.1.2 從新設新值
old_association = j->second;
refs->erase(j);
}
}
}
}
複製代碼
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
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()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
複製代碼
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());
}
複製代碼