前言
我们都很清楚本身分类使用@Property添加属性只会有setter和getter方法的声明没有对应的实现,也不会有对应的带下划线的成员变量存在,目前我们添加属性都是通过关联对象的方式即运用了运行时相关的API来实现的,下面我们来分析下它的实现原理。
关联对象添加
我们常写的分类添加关联对象的代码如下:
- (void)setCate_name:(NSString *)cate_name{
/**
1: 对象
2: 标识符
3: value
4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
我们进入objc_setAssociatedObject()方法内部,看下它的实现:
进入_object_set_associative_reference方法:
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
//对object指针进行数据封装以方便作为统一的格式处理
DisguisedPtr<objc_object> disguised{(objc_object *)object};
//对存储策略和value进行相关赋值存储在ObjcAssociation对象中
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
//构造函数 AssociationsManager() { AssociationsManagerLock.lock(); }
//析构函数 ~AssociationsManager() { AssociationsManagerLock.unlock();}
//下面这行代码调用AssociationsManager构造函数
AssociationsManager manager;
//这里AssociationsHashMap是个全局静态变量,相当于单例,获取全局唯一的哈希表
AssociationsHashMap &associations(manager.get());
//value存在
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
//若value不存在,移除关联对象
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
简单的地方我们直接就上面代码里注释说明,这里有涉及哈希表(AssociationsHashMap)存储查询的地方associations-(AssociationsHashMap).try_emplace(disguised, ObjectAssociationMap{}),我们看下:
这里我们首先看到BucketT *TheBucket创建了一个空的桶子,接着调用LookupBucketFor(Key, TheBucket)判断根据Key查询对应的BucketT是否存在,若存在,调用make_pair返回当前BucketT并返回false,表示未添加新的BucketT,这里的Key是外面传入的object指针包装的DisguisedPtr<objc_object> disguised,Value是外部传入的ObjectAssociationMap,我们看下LookupBucketFor方法:
这里LookupBucketFor调用了另一个方法LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket):
这里其实就是哈希表的一个查询过程,查询返回结果后,就接着根据返回的结果来继续执行后续代码,我们第一次进来调用LookupBucketFor的时候,bucket肯定是空的,所以接着执行InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...),把当前创建的空桶子存入AssociationsHashMap中,此时注意BucketT是空的,我们调试下:
我们看下InsertIntoBucket实现:
我们看到这里一个关键函数InsertIntoBucketImpl(Key, Key, TheBucket):
这里插入完成后,我们在InsertIntoBucket返回的TheBucket就看到它就赋值完成:
此时我们第一次调用associations.try_emplace调用结束,我们看refs_result.second为true,此时isFirstAssociation也为true,此时我们的policy和value还没有存进去,他们目前存在assocition对象中,我们看下refs这里,此时都还是空:
下一步refs.try_emplace(key,std::move(association))存入association,这里刚好也解释了第一次associations-(AssociationsHashMap).try_emplace(disguised, ObjectAssociationMap{})中bucket最终插入后并不包含相关的Key和Value,因为此时association并没有传入,我们看refs.try_emplace这里,此时传入的Key就是我们外部传入的Key(这里是const void *类型值为cate_name),而Key存bucket的first,value存second:
我们看refs.try_emplace调用结束后,发现bucket中Key和Value都已存入:
到这里,我们基本已经清楚了它的存储结构,大致是这样的,我们看张图:
关联对象设值流程
- 创建一个 AssociationsManager 管理类
- 获取唯一的全局静态哈希Map
- 判断是否插入的关联值是否存在:
3.1 存在走第4步
3.2 不存在:关联对象插入空流程 - 创建一个空的 ObjectAssociationMap 去取查询的键值对
- 如果发现没有这个key就插入一个空的BucketT进去,然后返回
- 标记对象存在关联对象
- 用当前修饰策略(policy)和值(value)组成了一个ObjcAssociation替换原来BucketT中的空
- 标记一下 ObjectAssociationMap的第一次为false
关联对象取值分析
我们上面分析的是objc_setAssociatedObject,接下来看下objc_getAssociatedObject(self, "cate_name"),我们进入底层源码看下它的具体实现:
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
//创建一个 AssociationsManager管理类
AssociationsManager manager;
//获取唯一的全局静态AssociationsHashMap
AssociationsHashMap &associations(manager.get());
//根据 DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
//如果这个迭代查询器不是最后一个获取 :ObjectAssociationMap (这里有策略和value)
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
//找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
//返回value
return association.autoreleaseReturnedValue();
}
- 创建一个 AssociationsManager 管理类
- 获取唯一的全局静态哈希表AssociationsHashMap
- 根据DisguisedPtr找到AssociationsHashMap中的 iterator迭代查询器
- 如果这个迭代查询器不是最后一个 获取:ObjectAssociationMap(这里有policy和value)
- 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
- 返回value
关联对象移除
关联对象的移除在dealloc方法中,我们看下它调用流程:dealloc——>_objc_rootDealloc——>rootDealloc:
我们接着看object_dispose()——>objc_destructInstance:
我们进入_object_remove_assocations,看下它的实现:
相关面试题分析
[self class]和[super class]的区别
[self class]本质是objc_msgSend,消息的接收者是self,方法编号:class,[super class]本质是objc_msgSendSuper,消息的接收者也是self,方法编号:class;我们看个例子:
我们运行之后这里输出得到的都是LGTeacher,那么为什么呢?首先LGTeacher中是没有class这个方法的,该方法存在于NSObject中,我们可以进入class方法看下:
我们之前就已经知道方法调用本质是消息的发送,底层调用的objc_msgSend(),这里的class方法是有两个默认参数的id self和 sel _cmd,只是被系统隐藏起来了,我们要清楚这里的self,就是传进来的实例对象,而object_getClass()内存实现是这样的:
obj——>getIsa()获取的就是当前类,因为我们之前已经讲过实例对象的isa指向当前类,所以获取的LGTeacher;[super class]这里的super只是个关键字,跟前面self不同,self是形参名,我们汇编调试:
我们可以看到[super class]底层调用的objc_msgSendSuper2,我们源码中看下它:
这里我看注释就可以知道super使用的当前类,而不是它的父类,我们看下这里的objc_super的数据结构:
从上图中可以看出,这里recevier接收的是当前类的实例,最后一句注释我们可以知道[super class]其实只是保证了当前父类被作为第一个查询的类,所以到这里我们基本也就清楚了[super class]为什么跟[self class]输出同一个类,因为他们最终接收到的都是当前类的实例,通过实例对象找到当前类。这里objc_msgSendSuper2本质也是从objc_msgSuperSend()跳转过来的,我们在汇编代码可以看到:
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//这里做了objc_msgSendSuper2的跳转
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper