首發於個人我的博客html
在詳解iOS中分類Cateogry 一文中,咱們提出一個問題,c++
那這裏就詳細說明git
首先咱們要回憶一下,添加屬性,實際上作了三件事github
eg: 定義一個 YZPerson
類,並定義age
屬性數組
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
@property (assign, nonatomic) int age;
@end
複製代碼
就至關於幹了三件事安全
_age
set
方法和get
方法的聲明set
方法和get
方法的實現 以下#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
{
int _age;
}
- (void)setAge:(int)age;
- (int)age;
@end
#import "YZPerson.h"
@implementation YZPerson
- (void)setAge:(int)age{
_age = age;
}
- (int)age{
return _age;
}
@end
複製代碼
_age
set
方法和get
方法的聲明set
方法和get
方法的實現set
方法和get
方法的實現定義一個分類 YZPerson+Ext.h
,而後添加屬性weight
bash
#import "YZPerson.h"
@interface YZPerson (Ext)
@property (nonatomic ,assign) int weight;
@end
複製代碼
使用app
YZPerson *person = [[YZPerson alloc] init];
person.weight = 10;
複製代碼
會直接報錯,ide
iOS-關聯對象[1009:10944] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[YZPerson setWeight:]: unrecognized selector sent to instance 0x10182bd10'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3550d063 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff6ac8e06b objc_exception_throw + 48
2 CoreFoundation 0x00007fff355961bd -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff354b34b4 ___forwarding___ + 1427
4 CoreFoundation 0x00007fff354b2e98 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff6c0183f9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Program ended with exit code: 9
複製代碼
從 reason: '-[YZPerson setWeight:]: unrecognized selector sent to instance 0x10182bd10'
可知,分類中添加屬性,沒有生成set
方法和get
方法的實現函數
set
方法和get
方法的聲明#import "YZPerson+Ext.h"
@implementation YZPerson (Ext)
- (void)setWeight:(int)weight{
}
- (int)weight{
return 100;
}
@end
複製代碼
而後再調用
YZPerson *person = [[YZPerson alloc] init];
person.age = 25;
person.weight = 10;
NSLog(@"person.age = %d",person.age);
NSLog(@"person.weight = %d",person.weight);
複製代碼
輸出
2019-07-10 08:28:04.406972+0800 iOS-關聯對象[1620:18520] person.age = 25
2019-07-10 08:28:04.407291+0800 iOS-關聯對象[1620:18520] person.weight = 100
複製代碼
進一步證實了,不會生成set
方法和get
方法的實現,可是會生成set
方法和get
方法的聲明,由於若是沒有生成set
方法和get
方法的聲明,這個方法就不能調用。
咱們還能夠這樣:在YZPerson+Ext.h
文件中聲明瞭weight
,而後再YZPerson+Ext.m
中寫實現的時候,會有提示的
更加說明了是有聲明的。
#import "YZPerson.h"
@interface YZPerson (Ext)
{
int _weight; // 報錯 Instance variables may not be placed in categories
}
@property (nonatomic ,assign) int weight;
@end
複製代碼
會直接報錯Instance variables may not be placed in categories
,成員變量不能定義在分類中
前面的文章詳解iOS中分類Cateogry 中分析過源碼,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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
複製代碼
可知,這個結構體中,沒有數組存放成員變量,只有屬性,協議等。
有什麼辦法能夠實如今分類中添加屬性和在類中添加屬性同樣的效果麼?答案是有的
分類YZPerson+Ext.m
中定義全局變量 _weight
#import "YZPerson+Ext.h"
@implementation YZPerson (Ext)
int _weight;
- (void)setWeight:(int)weight{
_weight = weight;
}
- (int)weight{
return _weight;
}
@end
複製代碼
使用時候
YZPerson *person = [[YZPerson alloc] init];
person.weight = 103;
NSLog(@"person.weight = %d",person.weight);
複製代碼
輸出爲
iOS-關聯對象[1983:23793] person.weight = 103
複製代碼
看起來確實能夠,而後實際上咱們不能這麼用,由於,全局變量是共享的,假設有兩個 Person
,第二個Person
修改了weight屬性,而後打印第一個Person.weight
YZPerson *person = [[YZPerson alloc] init];
person.weight = 103;
NSLog(@"person.weight = %d",person.weight);
YZPerson *person2 = [[YZPerson alloc] init];
person2.weight = 10;
NSLog(@"person.weight = %d",person.weight);
複製代碼
輸出爲
iOS-關聯對象[1983:23793] person.weight = 103
iOS-關聯對象[1983:23793] person.weight = 10
複製代碼
可知,修改了Person2.weight
會改變Person.weight
的值,由於是全局變量的緣故。因此這種方法不行
既然前面方案不能用的緣由是全局變量,共享一份,那咱們是否是隻要保證,一對一的關係,是否是就能夠了呢?
定義 字典weights_
以對象的地址值做爲key
來,weight
的值做爲value
來存儲和使用
#import "YZPerson+Ext.h"
@implementation YZPerson (Ext)
NSMutableDictionary *weights_;
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 寫在這裏,保證s只初始化一次
weights_ = [NSMutableDictionary dictionary];
});
}
- (void)setWeight:(int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];//self 地址值做爲key
weights_[key] = @(weight);//字典中的value不能直接放int,須要包裝成對象
}
- (int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];
return [weights_[key] intValue];
}
@end
複製代碼
這樣的話,使用起來,就不會由於不一樣對象而干擾了 結果以下
下面先簡單說明關聯對象的使用
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
複製代碼
id object
: 給哪一個對象添加屬性,這裏要給本身添加屬性,用self
。void * == id key
: key
值,根據key獲取關聯對象的屬性的值,在objc_getAssociatedObject
中經過次key
得到屬性的值並返回。id value
: 關聯的值,也就是set
方法傳入的值給屬性去保存。objc_AssociationPolicy policy
: 策略,屬性以什麼形式保存。typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關聯的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關對象的強引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關的對象被複制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關對象的強引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關的對象被複制,原子性
};
複製代碼
整理成表格以下
objc_AssociationPolicy | 對應的修飾符 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
eg: 咱們在代碼中使用了 OBJC_ASSOCIATION_RETAIN_NONATOMIC
就至關於使用了 nonatomic
和 strong
修飾符。
注意點 上面列表中,沒有對應weak
修飾的策略, 緣由是 object
通過DISGUISE
函數被轉化爲了disguised_ptr_t
類型的disguised_object
。
disguised_ptr_t disguised_object = DISGUISE(object);
複製代碼
而weak
修飾的屬性,當沒有擁有對象以後就會被銷燬,而且指針置爲nil
,那麼在對象銷燬以後,雖然在map
中仍然存在值object
對應的AssociationsHashMap
,可是由於object
地址已經被置爲nil
,會形成壞地址訪問而沒法根據object
對象的地址轉化爲disguised_object
了,這段話能夠再看徹底文以後,再回來體會下。
objc_getAssociatedObject(id object, const void *key);
複製代碼
id object
: 獲取哪一個對象裏面的關聯的屬性。void * == id key
: 什麼屬性,與objc_setAssociatedObject
中的key
相對應,即經過key
值取出value
。- (void)removeAssociatedObjects
{
// 移除關聯對象
objc_removeAssociatedObjects(self);
}
複製代碼
#import "YZPerson.h"
@interface YZPerson (Ext)
@property (nonatomic,strong) NSString *name;
@end
#import "YZPerson+Ext.h"
#import <objc/runtime.h>
@implementation YZPerson (Ext)
const void *YZNameKey = &YZNameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, YZNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, YZNameKey);
}
- (void)dealloc
{
objc_removeAssociatedObjects(self);
}
@end
複製代碼
使用的時候,正常使用,就能夠了
YZPerson *person = [[YZPerson alloc] init];
person.name = @"jack";
YZPerson *person2 = [[YZPerson alloc] init];
person2.name = @"rose";
NSLog(@"person.name = %@",person.name);
NSLog(@"person2.name = %@",person2.name);
複製代碼
輸出
iOS-關聯對象[4266:52285] person.name = jack
iOS-關聯對象[4266:52285] person2.name = rose
複製代碼
使用起來就是這麼簡單
實現關聯對象技術的核心對象有
關聯對象的源碼在 Runtime源碼中
objc_setAssociatedObject
查看objc-runtime.mm
類,首先找到objc_setAssociatedObject
函數,看一下其實現
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
複製代碼
_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);
}
複製代碼
如圖所示
_object_set_associative_reference
函數內部咱們能夠找到咱們上面說過的實現關聯對象技術的四個核心對象。接下來咱們來一個一個看其內部實現原理探尋他們之間的關係。
AssociationsManager
查看 AssociationsManager
咱們知道AssociationsManager
內部有static AssociationsHashMap *_map;
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;
}
};
複製代碼
AssociationsHashMap
接下來看 AssociationsHashMap
上圖中 AssociationsHashMap
的源碼咱們發現AssociationsHashMap
繼承自unordered_map
首先來看一下unordered_map
內的源碼
從unordered_map
源碼中咱們能夠看出 參數 _Key
和_Tp
對應着map
中的Key
和Value
,那麼對照上面AssociationsHashMap
的源碼,能夠發現_Key
中傳入的是unordered_map<disguised_ptr_t
,_Tp
中傳入的值則爲ObjectAssociationMap *
。
而後 咱們查看ObjectAssociationMap
的源碼,上圖中ObjectAssociationMap
已經標記出,咱們能夠知道ObjectAssociationMap
中一樣以key
、Value
的方式存儲着ObjcAssociation
。
ObjcAssociation
接着咱們來到ObjcAssociation
中,能夠看到
class ObjcAssociation {
uintptr_t _policy; // 策略
id _value; // 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
存儲着_policy
和_value
,而這兩個值咱們能夠發現正是咱們調用objc_setAssociatedObject
函數傳入的值,換句話說咱們在調用objc_setAssociatedObject
函數中傳入value
和policy
這兩個值最終是存儲在ObjcAssociation
中的。
如今咱們已經對四個核心對象AssociationsManager
、 AssociationsHashMap
、 ObjectAssociationMap
、ObjcAssociation
之間的關係有了初步的瞭解,那麼接下繼續仔細閱讀源碼,看一下objc_setAssociatedObject
函數中傳入的四個參數分別放在哪一個對象中充當什麼做用
_object_set_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);
// 根據value的值經過acquireValue函數獲取獲得new_value
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
// 獲取 manager 內的 AssociationsHashMap 也就是 associations
AssociationsHashMap &associations(manager.associations());
// object 通過 DISGUISE 函數被轉化爲了disguised_ptr_t類型的disguised_object
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;
// policy和new_value 做爲鍵值對存入了ObjcAssociation
j->second = ObjcAssociation(policy, new_value);
} else {
// policy和new_value 做爲鍵值對存入了ObjcAssociation
(*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 {
// 來到這裏說明,value爲空
// 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);
}
複製代碼
**acquireValue
**內部實現 經過對策略的判斷返回不一樣的值
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
複製代碼
value
通過acquireValue
函數處理返回了new_value
。acquireValue
函數內部實際上是經過對策略的判斷返回不一樣的值typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
複製代碼
AssociationsManager manager
,獲得manager
內部的AssociationsHashMap
即associations
。 以後咱們看到了咱們傳入的第一個參數object
通過DISGUISE
函數被轉化爲了disguised_ptr_t
類型的disguised_object
。typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
複製代碼
new_value
的value
,和policy
一塊兒被存入了ObjcAssociation
中。 而ObjcAssociation
對應咱們傳入的key
被存入了ObjectAssociationMap
中。 disguised_object
和ObjectAssociationMap
則以key-value
的形式對應存儲在associations
中也就是AssociationsHashMap
中。若是傳入的value爲空,那麼就刪除這個關聯對象
// 來到這裏說明,value爲空
// 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);
}
}
複製代碼
本文參考資料:
用表格總結來展現這幾個核心類的關係以下
AssociationsManager
中ObjectAssociationMap
,ObjectAssociationMap
中存儲着多個此實例對象的關聯對象的key
以及ObjcAssociation
,ObjcAssociation
中存儲着關聯對象的value
和policy
策略objc_getAssociatedObject
內部調用的是_object_get_associative_reference
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
複製代碼
_object_get_associative_reference
函數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);
// 查找 disguised_object
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
//查看key 和value
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// 存在key 和value 就取出對應的值
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// 不存在key value 就把這個關聯對象擦除
objc_autorelease(value);
}
return value;
}
複製代碼
關鍵代碼已經在上文中給了註釋
objc_removeAssociatedObjects
函數objc_removeAssociatedObjects
函數用來刪除全部關聯對象,內部調用了_object_remove_assocations
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
複製代碼
_object_remove_assocations
再來看看_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()) { // 遍歷AssociationsHashMap 取出值
// 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());
}
複製代碼
代碼中能夠看出,接受一個object
對象,而後遍歷刪除該對象全部的關聯對象
用表格總結來展現這幾個核心類的關係以下
AssociationsManager
中ObjectAssociationMap
,ObjectAssociationMap
中存儲着多個此實例對象的關聯對象的key
以及ObjcAssociation
,ObjcAssociation
中存儲着關聯對象的value
和policy
策略object
對象,而後遍歷刪除該對象全部的關聯對象_object_set_associative_reference
的是時候,若是傳入的value
爲空就刪除這個關聯對象本文參考資料:
本文相關代碼github地址 github
更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。