首发于个人我的博客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
更多资料,欢迎关注我的公众号,不定时分享各类技术文章。