0%

iOS底层探索-类的扩展和关联对象

前面几篇我们探索了的加载过程,本篇我们研究类相关的两个点:类的扩展关联对象

类扩展

clang编译

我们首先在main.m文件中新建一个类JSAnimal,并给类定义扩展,注意扩展要在声明之后实现之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@interface JSAnimal : NSObject

@property (nonatomic,copy)NSString *name;

- (void)sayWow;

@end

@interface JSAnimal ()

@property (nonatomic,copy)NSString *type;

- (void)ex_sayWow;

@end

@implementation JSAnimal

+ (void)classMethod{
NSLog(@"%s",__func__);
}

- (void)sayWow{
NSLog(@"%s",__func__);
}

- (void)ex_sayWow{
NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
JSAnimal *animal = [[JSAnimal alloc] init];
[animal ex_sayWow];
NSLog(@"done");
}
return 0;
}

我们使用clang命令,将main.m转成main.cpp文件,看一下分类的实现

1
clang -rewrite-objc main.m -o main.cpp

main.cpp文件搜索JSAnimal

1626779765218

1626779854673

发现扩展里声明的属性方法编译后和中的在一起,作为的一部分,也就是说扩展中的属性和方法编译期就添加到本类中了。

通过源码探索运行时

定义一个JSPerson类和扩展,本类中实现扩展中声明的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

@interface JSPerson : NSObject

@property (nonatomic, copy) NSString *name;

- (void)saySomething;

@end

#import "JSPerson.h"

@implementation JSPerson

+ (void)load{

}

- (void)saySomething{
NSLog(@"%s",__func__);
}

- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
1
2
3
4
5
6
7
@interface JSPerson ()

- (void)ext_instanceMethod;

+ (void)ext_classMethod;

@end

注意JSPerson类实现了load方法,目的是让其非懒加载。根据我们的经验,我们在realizeClassWithoutSwift添加断点调试

1626781568401

通过lldb打印ro中的方法列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
(lldb) p ro
(const class_ro_t *) $0 = 0x0000000100004790
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 16
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003b58 "JSPerson"
}
}
baseMethodList = 0x00000001000047d8
baseProtocols = nil
ivars = 0x0000000100004840
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100004868
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethods()
(method_list_t *) $2 = 0x00000001000047d8
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 4)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
name = "saySomething"
types = 0x0000000100003d84 "v16@0:8"
imp = 0x0000000100003600 (KCObjcBuild`-[JSPerson saySomething])
}
(lldb) p $3.get(1).big()
(method_t::big) $5 = {
name = "ext_instanceMethod"
types = 0x0000000100003d84 "v16@0:8"
imp = 0x0000000100003630 (KCObjcBuild`-[JSPerson ext_instanceMethod])
}
(lldb) p $3.get(2).big()
(method_t::big) $6 = {
name = "name"
types = 0x0000000100003d98 "@16@0:8"
imp = 0x0000000100003660 (KCObjcBuild`-[JSPerson name])
}
(lldb) p $3.get(3).big()
(method_t::big) $7 = {
name = "setName:"
types = 0x0000000100003da0 "v24@0:8@16"
imp = 0x0000000100003690 (KCObjcBuild`-[JSPerson setName:])
}

可以看到,扩展中的方向现在已经加载了已知ro中的方法是编译期就确定的,所以也验证了扩展的方法是在编译期添加到本类的。

小结

  • 类的扩展 在编译期 会作为类的一部分,和类一起编译进来
  • 类的扩展只是声明,依赖于本类的实现。

分类的关联对象

我们知道分类正常是不能添加属性的,但是通过关联对象可以,其实现通过两个方法

  • 通过objc_setAssociatedObject方法设置值。
  • 通过objc_getAssociatedObject方法取值。

下面我们分别探索。

objc_setAssociatedObject流程

objc_setAssociatedObject有四个参数:

  • 参数1:要关联的对象
  • 参数2:表示符,方便查找识别
  • 参数3:value值
  • 参数4:属性的策略,我们定义属性经常用到的如nonatomicstrongweak

首先定义JSPerson分类,定义一个属性cateegory_name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface JSPerson (JSCategory)

@property (nonatomic, copy) NSString *cateegory_name;

@end
@implementation JSPerson (JSCategory)

- (void)setCateegory_name:(NSString *)category_name{
objc_setAssociatedObject(self, "category_name", category_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)category_name{
return objc_getAssociatedObject(self, "category_name");
}

@end

main函数赋值属性的地方添加断点,根据调用情况

1626783154111

定位到objc_setAssociatedObject方法

1626783226697

调用的是_object_set_associative_reference方法,我们继续跟进查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
DisguisedPtr<objc_object> disguised{(objc_object *)object};
///包装policy value
ObjcAssociation association{policy, value};

// retain the new value (if any) outside the lock.
//根据策略类型(strong、weak等)进行处理
association.acquireValue();

bool isFirstAssociation = false;
{
//初始化manager变量,相当于自动调用AssociationsManager的构造函数进行初始化
AssociationsManager manager;
///一个HashMap
AssociationsHashMap &associations(manager.get());

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;///得到一个空的桶,找到引用对象类型,即第一个元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有association关联对象
if (!result.second) {///如果结果不存在
association.swap(result.first->second);
}
} else {//如果传的是空值,则移除关联,相当于移除
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();
}

通过源码我们看到大体的流程为:

  • 创建一个AssociationsManager管理类
  • 获取静态哈希表:associations
  • 判断关联值value是否为空
    • 如果为空就走:插入空值流程。
    • 如果不为空继续下一步
  • 通过try_emplace方法,创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 如果发现没有 key插入一个 空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象
  • 用当前 policy 和 value 组成了一个 ObjcAssociation 替换原来 BucketT中的值
  • 标记一下 ObjectAssociationMap第一次false

源码调试

对流程有了大概的认识,我们开始断点调试

if (value) 之前变量的值

1626785173962

通过lldb我们打印出了disguisedassociationmanagerassociationsvalue的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069393517536)
(lldb) p association
(objc::ObjcAssociation) $1 = {
_policy = 3
_value = 0x0000000100004080 "哈哈哈"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
Buckets = nil
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
(lldb) p value
(__NSCFConstantString *) $4 = 0x0000000100004080 "哈哈哈"
value不为空流程

上面我们看到value值不为空,我们进入if语句继续调试。

  • p refs_result
1
2
3
4
5
6
7
8
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $5 = {
first = {
Ptr = 0x00000001012102a0
End = 0x0000000101210300
}
second = true
}

看到refs_result的数据结构看起来比较复杂,但是值比较简单,有两个属性firstsecond。其中first的值为:

1
2
3
4
(lldb) p $5.first.Ptr
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $6 = 0x00000001012102a0
(lldb) p $5.first.End
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $7 = 0x0000000101210300

second值为true,所以会执行isFirstAssociation = true

  • try_emplace方法,associations调用了try_emplace方法,我们看一下他的源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template <typename... Ts>
    std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    ///根据key找桶
    if (LookupBucketFor(Key, TheBucket))
    ///如果桶存在 则返回
    return std::make_pair(
    makeIterator(TheBucket, getBucketsEnd(), true),
    false); // Already in map.

    // Otherwise, insert the new element.
    ///如果不存在则插入桶 并返回
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
    makeIterator(TheBucket, getBucketsEnd(), true),
    true);
    }

    有两个返回,都是通过std::make_pair生成相应的键值对。

    1. 通过LookupBucketFor方法查找桶,如果map中已经存在,则直接返回,其中make_pair的第二个参数bool值为false
    2. 如果没有找到,则通过InsertIntoBucket插入map,其中make_pair的第二个参数bool值为true

    我们断点进来使用lldb调试

    1626787367239

    p TheBucket

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (lldb) p TheBucket
    (objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $1 = 0x0000000101c04200
    (lldb) p *$1
    (objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $2 = {
    std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
    first = (value = 18446744069384153152)
    second = {
    Buckets = nil
    NumEntries = 0
    NumTombstones = 0
    NumBuckets = 0
    }
    }
    }

    看到TheBucket的类型与 refs_result中属性的类型是一致的。

  • LookupBucketFor方法

    我们进入LookupBucketFor源码发现有两个实现,它们的区别是FoundBucket的参数类型第一个实现多const修饰。

    1626786329854

    我们通过断点调试,发现调用的是第2个实现,第2个方法内部调用了第1个实现。我们先看第1个实现源码,注释中有流程说明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    template<typename LookupKeyT>
    bool LookupBucketFor(const LookupKeyT &Val,
    const BucketT *&FoundBucket) const {
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
    FoundBucket = nullptr;
    return false;
    }
    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    const KeyT EmptyKey = getEmptyKey();
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
    !KeyInfoT::isEqual(Val, TombstoneKey) &&
    "Empty/Tombstone value shouldn't be inserted into map!");
    ///通过哈希函数得到BucketNo
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    unsigned ProbeAmt = 1;
    while (true) {//与catche_t查找imp类似,通过哈希查找
    const BucketT *ThisBucket = BucketsPtr + BucketNo;
    // Found Val's bucket? If so, return it. 如果找到直接返回 true
    if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
    FoundBucket = ThisBucket;
    return true;
    }

    // If we found an empty bucket, the key doesn't exist in the set.
    // Insert it and return the default value.
    //如果是一个空桶 说明key不在集合中,将key插入 返回false
    if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
    // If we've already seen a tombstone while probing, fill it in instead
    // of the empty bucket we eventually probed to.
    FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
    return false;
    }

    // If this is a tombstone, remember it. If Val ends up not in the map, we
    // prefer to return it than something that would require more probing.
    // Ditto for zero values.
    // 以上条件都不满足 BucketNo调整进行平移、再哈希继续查找
    if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
    !FoundTombstone)
    FoundTombstone = ThisBucket; // Remember the first tombstone found.
    if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
    FoundTombstone = ThisBucket;

    // Otherwise, it's a hash collision or a tombstone, continue quadratic
    // probing.
    if (ProbeAmt > NumBuckets) {
    FatalCorruptHashTables(BucketsPtr, NumBuckets);
    }
    BucketNo += ProbeAmt++;
    BucketNo &= (NumBuckets-1);
    }
    }

    第2个LookupBucketFor的实现

    1
    2
    3
    4
    5
    6
    7
    bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;//空的桶
    bool Result = const_cast<const DenseMapBase *>(this)
    ->LookupBucketFor(Val, ConstFoundBucket);//调用第一个LookupBucketFor方法查找
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);//如果找到复制给第二个参数,因为第二个参数是引用类型会直接让调用的地方获取到值。也就是try_emplace方法的TheBucket
    return Result;
    }
  • 继续走valuetrue的流程

    后面还会执行try_emplace方法,我们在执行之前查看一下refs的值

    1
    2
    3
    4
    5
    6
    7
    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $3 = {
    Buckets = nil
    NumEntries = 0
    NumTombstones = 0
    NumBuckets = 0
    }

    try_emplace方法之后refs的值

    1
    2
    3
    4
    5
    6
    7
    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $4 = {
    Buckets = 0x0000000100711390
    NumEntries = 1
    NumTombstones = 0
    NumBuckets = 4
    }

    第一次执行try_emplace插入的是一个空桶,还没有值,第二次执行第一次执行try_emplace才插入值,即往空桶中插入ObjectAssociationMap(value,policy),返回true。

    此时result.secondtrue,此时属性的value就关联上了。

关联对象结构

关联对象的设置图如下:

关联对象

属性设计的哈希表结构如下:

关联对象哈希表

map中有很多的关联对象map,类型是ObjectAssociationMap,其中key为DisguisedPtr<objc_object>,例如JSPerson会对应一个ObjectAssociationMapJSTeacher也会对应一个ObjectAssociationMap

ObjectAssociationMap哈希表中有很多key-value键值对,其中key的类型为const void *,其实这个key从底层这个方法_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)的参数就可以看出,key是我们关联属性时设置的字符串value的类型为ObjcAssociation

value为空流程

这个过程其实就是else流程,也就是我们对value设置为nil的流程,主要就是移除关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else {
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);

}
}
}
}
  • 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 清理迭代器
  • 实际上如果插入空置 相当于清除

objc_getAssociatedObject流程

main方法中添加一个取值的语句
1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
JSPerson *person = [[JSPerson alloc] init];
person.category_name = @"哈哈哈";
NSString *name = person.category_name;
NSLog(@"done");
}
return 0;
}
objc_getAssociatedObject源码实现
1
2
3
4
5
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}

调用_object_get_associative_reference函数。

_object_get_associative_reference源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};

{
///创建AssociationsManager管理类
AssociationsManager manager;
///获取静态哈希表
AssociationsHashMap &associations(manager.get());
/////找到迭代器,即获取buckets
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {//如果这个迭代查询器不是最后一个 继续获取
//找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
ObjectAssociationMap &refs = i->second;
//根据key查找ObjectAssociationMap,即获取bucket
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
//获取ObjcAssociation
association = j->second;
association.retainReturnedValue();
}
}
}
///返回值
return association.autoreleaseReturnedValue();
}

看源码分析主要分为以下几步

  • 创建一个 AssociationsManager 管理类
  • 获取静态哈希表:AssociationsHashMap
  • 通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 如果这个迭代查询器不是最后一个 继续获取 : ObjectAssociationMap (policy和value)
  • 通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回 value
查找方法find
1
2
3
4
5
6
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}

根据关联对象迭代查找AssociationsHashMap,也就是buckets

通过源码看取值流程

我们直接断点到_object_get_associative_reference函数

1626789232319

执行p ip i->second:

1
2
3
4
5
6
7
8
9
10
11
12
(lldb) p i
(objc::DenseMapBase<objc::DenseMap<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >, DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >::iterator) $0 = {
Ptr = 0x0000000100631d60
End = 0x0000000100631d80
}
(lldb) p i->second
(objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $1 = {
Buckets = 0x0000000100631d80
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}

再次执行find方法,在调用find方法之前,我们先打印j,此时valuenil

1
2
3
4
5
6
7
8
9
10
11
(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $2 = {
Ptr = 0x00007ffeefbff400
End = 0x00000001002e70db
}
(lldb) p j->second
(objc::ObjcAssociation) $3 = {
_policy = 4294980472
_value = nil
}
(lldb)

执行find方法之后再次打印,发现value已经有值,也就是取到了关联对象。

1
2
3
4
5
6
7
8
9
10
(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $4 = {
Ptr = 0x0000000100631d80
End = 0x0000000100631de0
}
(lldb) p j->second
(objc::ObjcAssociation) $5 = {
_policy = 3
_value = 0x0000000100004080 "哈哈哈"
}

总结

本篇主要探索了扩展关联对象,其中类的扩展编译期 会作为类的一部分,和类一起编译进来。

关联对象设置流程为:

  • 创建一个AssociationsManager管理类
  • 获取静态哈希表:associations
  • 判断关联值value是否为空
    • 如果为空就走:插入空值流程。
    • 如果不为空继续下一步
  • 通过try_emplace方法,创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 如果发现没有 key插入一个 空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象
  • 用当前 policy 和 value 组成了一个 ObjcAssociation 替换原来 BucketT中的值
  • 标记一下 ObjectAssociationMap第一次false

关联对象取值的流程为:

  • 创建一个 AssociationsManager 管理类
  • 获取静态哈希表:AssociationsHashMap
  • 通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 如果这个迭代查询器不是最后一个 继续获取 : ObjectAssociationMap (policy和value)
  • 通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回 value