開篇還是放上幾道面試題
Category能否添加成員變量?如果可以,如何給Category添加成員變量?
不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果
- 首先大家應(yīng)該都知道分類可以添加屬性,但是不可以添加成員變量,同時(shí)自動(dòng)聲明了set方法,get方法,此時(shí)獲取方法列表是不存在set,fet方法的,必須實(shí)現(xiàn)才會(huì)在方法列表中出現(xiàn),大家可能會(huì)想那我就手動(dòng)實(shí)現(xiàn)set方法,如下圖
分類添加了個(gè)屬性 @property (nonatomic,copy)NSString *categoryName1;

- 發(fā)現(xiàn)分類中根本沒有這個(gè)成員變量,其實(shí)如果看了分類的底層結(jié)構(gòu)category_t應(yīng)該就知道那里壓根就沒有存儲(chǔ)成員變量的地方,所以想通過正常的方式直接添加成員變量是不可以的,但是可以通過關(guān)聯(lián)對(duì)象,來間接添加成員變量
關(guān)聯(lián)對(duì)象提供了以下API
添加關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(id object, const void * key)
移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
關(guān)聯(lián)對(duì)象基本用法
1.以全局變量里面存放著自己的地址為key
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
2.以全局變量的地址為key
static char MyKey;
pobjc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
pobjc_getAssociatedObject(obj, &MyKey)
3.使用屬性名作為key
objc_setAssociatedObject(obj, @"name", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
pobjc_getAssociatedObject(obj, @"name");
4.使用get方法的@selecor作為key
objc_setAssociatedObject(obj, @selector(setName), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//get方法里面,下面這兩種寫法都可以
objc_getAssociatedObject(obj, @selector(name))
objc_getAssociatedObject(obj, _cmd)
objc_AssociationPolicy policy的用法

開始分析源碼 源碼地址
- 搜索objc_setAssociatedObject,找到如下源碼
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
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);
}
- 看到
AssociationsManager,AssociationsHashMap,ObjectAssociationMap,ObjcAssociation這四個(gè)類,接下來我們一一在仔細(xì)查看對(duì)應(yīng)的類結(jié)構(gòu) - AssociationsManager結(jié)構(gòu)如下
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
- 由上面代碼看到此類只有一個(gè)成員變量,也就是說這個(gè)manager管理著這個(gè)map,所有的信息都存在這個(gè)map里
static AssociationsHashMap *_map;
- 接下來查看這個(gè)
AssociationsHashMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
- 由上面代碼可以看出這個(gè)map的key和value類型結(jié)構(gòu)為:
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap
- 那么在接著看
ObjectAssociationMap
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
- 可以看到key value的結(jié)構(gòu)如下:
void * ObjcAssociation
void * ObjcAssociation
void * ObjcAssociation
- 接著再看
ObjcAssociation的結(jié)構(gòu)
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
- 從上面代碼可以看到ObjcAssociation類里有兩個(gè)成員變量,一個(gè)是_policy策略,一個(gè)是_value值
-
再具體點(diǎn)分析如下圖所示:
關(guān)聯(lián)對(duì)象分析圖.jpg -
最終得到結(jié)論:
屏幕快照 2018-11-21 下午6.56.32.png

好了看完了底層結(jié)構(gòu),在講講項(xiàng)目中怎么使用
- 大家可能注意到,policy沒有weak'類型,那得怎么實(shí)現(xiàn)weak關(guān)聯(lián)對(duì)象呢?
我們可以關(guān)聯(lián)個(gè)retain對(duì)象,讓這個(gè)對(duì)象持有他
恰好系統(tǒng)有提供這個(gè)類NSMapTable,這個(gè)map可以設(shè)置weak引用里面的值,和我們常用的字典類似
@interface NSObject()
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end
@implementation NSObject (Association)
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
[self.keyMapTable setObject:object forKey:key];
}
-(id)weakAssociatedObjectForKey:(NSString *)key
{
return [self.keyMapTable objectForKey:key];
}
#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 再來看一個(gè)問題,我們大家可能在添加retain關(guān)聯(lián)對(duì)象的時(shí)候,經(jīng)常用getter方法作為key,但是用起來每增加個(gè)成員變量都得寫一遍很麻煩,所以大家可能就會(huì)想到使用屬性名作為key,這樣寫一遍公共的方法就好了,但是我告訴你這樣是存在潛在問題的,那么就來看看下面代碼有啥問題:
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
objc_setAssociatedObject(self, &key, object, OBJC_ASSOCIATION_RETAIN);
}
- (id)associatedObjectForKey:(NSString *)key {
return objc_getAssociatedObject(self, &key);
}
從上面的代碼可以看出就是使用傳進(jìn)來的字符串為地址,每次傳進(jìn)來的字符串一樣,大家可能就以為這個(gè)地址就一樣了,其實(shí)不然,當(dāng)字符串相同地址不一樣時(shí),set完在取值就取不到了,值為nil

- 上圖這種情況,雖然字符串的值相同,但是地址值不同,所以導(dǎo)致取出的值為nil,常見場(chǎng)景就是set的時(shí)候用的是常量字符串,取值的時(shí)候用的是后臺(tái)返回的數(shù)據(jù)里的值導(dǎo)致不一樣,如果你取值時(shí)還是用常量字符串那么就沒問題,因?yàn)槌A孔址刂芬粯?/li>
- 解決方式如下
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@end
@implementation NSObject (Association)
-(NSMutableDictionary *)strongKeyBuffer{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.strongKeyBuffer=[NSMutableDictionary dictionary];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setStrongKeyBuffer:(NSMutableDictionary *)strongKeyBuffer{
objc_setAssociatedObject(self, @selector(strongKeyBuffer), strongKeyBuffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
/**
* set 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 set 方法調(diào)用
* @param object 要關(guān)聯(lián)的對(duì)象,也就是要設(shè)置的新的屬性值
* @param key 屬性名稱,傳入新增屬性的名稱
解決常亮字符串地址不一致問題 導(dǎo)致取值為nil
**/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先獲取key
if (cKey == NULL) { // 字典中不存在就創(chuàng)建
cKey = key.UTF8String;
self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
}
objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark - get 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 get 方法調(diào)用
- (id)associatedObjectForKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue];
if (cKey == NULL) {
return nil;
} else {
return objc_getAssociatedObject(self, cKey);
}
}
用一個(gè)可變字典存放添加過哪些key,和真正set時(shí)用的地址是啥,這里這么用
[NSValue valueWithPointer:key.UTF8String];
當(dāng)然你也可以不這么用,主要思想是用個(gè)字典存儲(chǔ)添加過的key,然后取值的時(shí)候通過傳進(jìn)來的key,找到set時(shí)用的地址值,然后再用此地址值取值,所以就不會(huì)為nil了
下面附上我封裝的類.h .m文件
NSObject+Association.h
//
// NSObject+Association.h
// NSObject+Association
//
// Created by liusong on 2018/4/3.
// Copyright ? 2018年 liusong. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSObject (Association)
/** 所有要增加的屬性的 set 方法都可以調(diào)用這個(gè)方法來實(shí)現(xiàn) */
- (void)setAssociatedObject:(id)object forKey:(NSString *)key;
/** 所有要增加的屬性的 get 方法都可以調(diào)用這個(gè)方法來實(shí)現(xiàn) */
- (id)associatedObjectForKey:(NSString *)key ;
//以下為增加獲取 weak 屬性
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key;
- (id)weakAssociatedObjectForKey:(NSString *)key ;
@end
NSObject+Association.m
//
// NSObject+Association.m
// NSObject+Association
//
// Created by liusong on 2018/4/3.
// Copyright ? 2018年 liusong. All rights reserved.
//
#import "NSObject+Association.h"
#import <objc/runtime.h>
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end
@implementation NSObject (Association)
/**
* set 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 set 方法調(diào)用
* @param object 要關(guān)聯(lián)的對(duì)象,也就是要設(shè)置的新的屬性值
* @param key 屬性名稱,傳入新增屬性的名稱
解決同一字符串地址不一致問題 導(dǎo)致取值為nil
**/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先獲取key
if (cKey == NULL) { // 字典中不存在就創(chuàng)建
cKey = key.UTF8String;
self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
}
objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark - get 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 get 方法調(diào)用
- (id)associatedObjectForKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue];
if (cKey == NULL) {
return nil;
} else {
return objc_getAssociatedObject(self, cKey);
}
}
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
[self.keyMapTable setObject:object forKey:key];
}
-(id)weakAssociatedObjectForKey:(NSString *)key
{
return [self.keyMapTable objectForKey:key];
}
#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

