和谐学习!不急不躁!!我是你们的老朋友小青龙~
承接文章iOS底层分析之类的加载(下)做一些补充。
抛出探究的问题
前面文章对分类数据的加载已经做了分析,但是还有一些收尾工作尚未说明。譬如分类里添加了属性,但是具体的实现并没有提供,以至于报了警告:
以至于,当我直接对分类里的属性赋值的时候,提示我没有实现setName_category
:
既然如此,那就按照要求,我们自己实现一下呗:
如上图所示,按照我们平时的开发习惯,用objc_setAssociatedObject
和objc_getAssociatedObject
去实现set和get方法,就可以正常赋值、访问属性。
我们知道category的数据是运行时,动态加载到类列表的,set和get方法系统不会帮我们生成,需要手动去添加。
但是objc_setAssociatedObject
和objc_getAssociatedObject
,是如何做到数据绑定的呢?这就是我们要探究的点。
探究 - objc_setAssociatedObject
由于当前工程就是objc源码,所以直接command+单机一步步访问,最终定位到:
objc_setAssociatedObject
-> _object_set_associative_reference
,
// object 实例对象,即属性要跟谁关联
// key 属性名
// value 属性值
// policy 关联策略
/// 探究点:属性值如何绑定到类的实例对象
/// 将value通过key绑定到object上,绑定策略是policy
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
...
/// 1、伪装object的指针地址,统一数据结构,即把(objc_object *)object包装成DisguisedPtr<objc_object>这样的数据结构
DisguisedPtr<objc_object> disguised{(objc_object *)object};
/// 2、存储policy和value到association
ObjcAssociation association{policy, value};
...
bool isFirstAssociation = false;
{
/// 管理类
AssociationsManager manager;
/// 3、找到AssociationsHashMap哈希表
AssociationsHashMap &associations(manager.get());
if (value) {
/// 4、第一次调用try_emplace,就创建空的BucketT
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;
/// 5、再调用一次try_emplace,插入association到BucketT
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
/// 进入这里,说明参数value为空,那就进行erase(擦除,相当于情况这条记录)
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);
}
}
}
}
}
if (isFirstAssociation)
object->setHasAssociatedObjects();
// 释放旧值(锁外部)。
association.releaseHeldValue();
}
第一次调用try_emplace
我们发现,名叫LookupBucketFor的函数有两个,分别是:
- 第二个参数
带cost
/**
LookupBucketFor-查找Val的适当存储桶,并以
铸造桶。如果bucket包含键和值,则返回
true,否则它返回一个带有空标记或墓碑的bucket,并且
返回false。
*/
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
printf("LookupBucketFor-------const-----const \n");
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
...
}
- 第二个参数
不带cost
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
printf("LookupBucketFor-------const-----non \n");
const BucketT *ConstFoundBucket;
// 这里会调用LookupBucketFor,第二个参数带const
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
从try_emplace函数的代码来看,是调用第2个LookupBucketFor。
我们来运行一下工程,看下控制台打印的顺序:
打印结构,跟我们的分析结果一致。
InsertIntoBucket做了什么
template <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
TheBucket->getFirst() = std::forward<KeyArg>(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
我们打打印一下,最终的TheBucket是什么样的:
我们发现InsertIntoBucket函数,会把Key
和Values
插入到TheBucket
里。
Key和Values分别对应着:
- Key ---
try_emplace函数
的第一个参数
- Values ---
try_emplace函数
的第二个参数
这里我们可以看到,第一次调用,Values是空的。
继续下一步,结束了第一次try_emplace函数
调用,打印一下refs_result:
根据前面对InsertIntoBucket
函数的分析,second存放的应该是values,但是从打印结果来看,values还是空的,说明并没有插入进去。
第二次调用try_emplace
接下来第二次进入try_emplace,我们发现第二个参数带了association
(association是对policy和value的封装)。
我们直接定位InsertIntoBucket函数:
如图所示,此刻的Values才算是真正的有值。 下一步出来,打印result结果:
AssociationsHashMap为什么是单例?
定位代码:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
...
AssociationsHashMap &associations(manager.get());
...
}
command+单机进入get:
static修饰,于AssociationsManager来说,_mapStorage是唯一的,所以说AssociationsHashMap是单例
。
探究 - objc_getAssociatedObject
objc_getAssociatedObject -》_object_get_associative_reference:
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;// 管理类
AssociationsHashMap &associations(manager.get());/// 得到所有HashMap
/// 根据object找到对应HashMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);//根据key查找,得到TheBucket(j)
if (j != refs.end()) {
/// 拿到对应的association(association是对_policy和_value的封装)
association = j->second;
/// 取出Value
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
下断点,打印数据:
断点继续往下走:
至此,完成了objc_setAssociatedObject和objc_getAssociatedObject的探究。
总结
objc_setAssociatedObject流程
:
-
将object封装成「DisguisedPtr」结构
-
将policy和value封装成「ObjcAssociation」结构
-
找到AssociationsHashMap哈希表
-
调用try_emplace
-
如果是第一次调用try_emplace,就创建空的BucketT
-
然后再调用一次try_emplace,插入association到BucketT
objc_getAssociatedObject流程
:
-
得到管理类
-
得到所有的HashMap
-
根据object找到对应HashMap
-
根据key查找,得到TheBucket
-
从TheBucket拿出association
-
从association拿出value
补充 - association关联是什么时候释放的呢?
我们知道association是通过object
去找到对应的HashMap表
,然后再对这张表进行操作,那么association的释放
是否也跟object
的生命周期
有关呢?
快速定位,全局搜索“dealloc {
”:
// deallocating我们知道,传进来是true
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
// 下面代码跟objc_getAssociatedObject函数源码有点类似,就不做详细解释,
// 大致流程是:找到关联对象,然后执行erase擦除动作。
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {
// 因为参数deallocating为true,所以不会进入这里
...
}
if (!didReInsert)
/// 擦除
associations.erase(i);
}
}
...
}
这里放一张关系图
代码
链接: pan.baidu.com/s/16oTTFgdB…
密码: 5g27
--来自百度网盘超级会员V2的分享
更新
更新时间:2021.08.23
更新内容:association关联是什么时候释放的呢?
(详情请看文章)