类的加载 - 类的扩展 & 分类关联对象

一、类扩展

1、类扩展与分类

category

  • 准用来给类添加新方法;
  • 不能添加成员变量,即使添加了也无法取到;
  • 属性可添加,但只会生成 setter/getter 的声明而没有相应的实现 –> 可通过 runtime 进行关联实现。

extension

  • 可看做匿名分类;
  • 可以给类添加成员属性,但是私有的;
  • 可以给类添加方法,也是私有的。

类扩展

类的扩展必须在实现前,如下图 error 信息:

类扩展的本质是什么呢?  

2、类扩展 - Extension 的本质

1、cpp 文件的编译查看

在 main.m 文件中添加如下简单代码,

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

编译 main.m 文件成 .cpp:

编译后文件代码:

setter/getter 的实现:

由上可验证:通过类扩展添加的属性变量,在编译时生成了 成员变量 ivar 和 setter(objc_setProperty()) / getter 方法的声明与实现。

方法列表 method_list 中,类扩展添加的方法和原本可重点二分法一样,编译时已经编译到 method_list 中了。

我们使用之前类加载分析时的 objc 源码工程 进行验证,运行:

方法们见下图:

这里也可验证。更多操作:1、将添加的分类删除,重新运行,结果相同;2、将 MyPerson 类的 load 方法实现注释掉,依然相同:

总结: 类的扩展在编译时就会被加载到类中了,直接成为为类的一部分和类共存了 (自然类扩展添加的东西不存在脏内存问题,rwe 永远 NULL)。

扩展和分类不同,分类更主要用于动态的添加。 

二、关联对象 

一直分类添加的属性是没有 setter/getter 方法的实现的,我们可通过 runtime 的关联方法进行动态添加。

对 Myperson 的 cateMore_name 进行赋值,运行工程,断点在 setCateMore_name 处,进入源码:

get() 点进去没有找到实现相关代码,进入 SetAssocHook: 发现是 _base_objc_setAssociatedObject 方法的调用,继续点进去,到了 _object_set_associative_reference。

1、源码分析

1)_object_set_associative_reference() 源码 01:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
1 void
2 _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
3 {
4 // This code used to work when nil was passed for object and key. Some code
5 // probably relies on that to not crash. Check and handle it explicitly.
6 // rdar://problem/44094390
7 if (!object && !value) return;
9 if (object->getIsa()->forbidsAssociatedObjects())
10 _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
11 // DisguisedPtr 相当于 包装了一下原来的对象
12 DisguisedPtr<objc_object> disguised{(objc_object *)object};
13 // 包装一下 policy 和 value
14 ObjcAssociation association{policy, value};// 构造函数
16 // retain the new value (if any) outside the lock.
17 association.acquireValue();/**
18 inline void acquireValue() { // 根据关联方法设置的类型 policy 处理 retain 和 copy,其他场景不处理
19 if (_value) {
20 switch (_policy & 0xFF) {
21 case OBJC_ASSOCIATION_SETTER_RETAIN:// retain
22 _value = objc_retain(_value);
23 break;
24 case OBJC_ASSOCIATION_SETTER_COPY:// copy
25 _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
26 break;
27 }
28 }
29 }
30 */
32 {
33 AssociationsManager manager;/**
34 // class AssociationsManager manages a lock / hash table singleton pair.
35 // Allocating an instance acquires the lock
37 class AssociationsManager {
38 using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
39 static Storage _mapStorage; // 静态变量
41 public:
42 AssociationsManager() { AssociationsManagerLock.lock(); } // 初始化, 加 lock 锁避免了多线程对其重复操作,但并非表明它不可多次操作
43 ~AssociationsManager() { AssociationsManagerLock.unlock(); } // 析构
45 AssociationsHashMap &get() {
46 return _mapStorage.get();
47 }
49 static void init() {
50 _mapStorage.init();
51 }
52 };
53 */
55 // AssociationsHashMap 关联对象的表 全部的关联对象都在此表中 此表示唯一的
56 // _mapStorage.get() --> static Storage _mapStorage;//静态变量 全场唯一
57 AssociationsHashMap &associations(manager.get());
59 if (value) {
60 auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
61 /** refs_result:
62 $0 = {
63 first = {
64 Ptr = 0x00000001012000c0
65 End = 0x0000000101200100
66 }
67 second = true
68 }
69 */
70 if (refs_result.second) {// refs_result 的 second 是个 bool 值
71 /* it's the first association we make */
72 object->setHasAssociatedObjects();
73 }
75 /* establish or replace the association */ // 进行 建立或替换 association
76 auto &refs = refs_result.first->second; // 空的桶子
77 auto result = refs.try_emplace(key, std::move(association));
78 if (!result.second) {
79 association.swap(result.first->second);// swap 移动
80 }
81 } else {// value 是空值 --> 进行移除操作
82 auto refs_it = associations.find(disguised);// 通过 disguised 找
83 if (refs_it != associations.end()) {
84 auto &refs = refs_it->second;
85 auto it = refs.find(key);
86 if (it != refs.end()) {
87 association.swap(it->second);
88 refs.erase(it);// 消掉移除
89 if (refs.size() == 0) {
90 associations.erase(refs_it);// 消掉移除
92 }
93 }
94 }
95 }
96 }
98 // release the old value (outside of the lock).
99 association.releaseHeldValue();
100 }

1、先运行一下工程,执行代码通过 lldb 先查看信息具体是什么:

_object_set_associative_reference() –> try_emplace()

2、try_emplace() 代码 - 源码 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1   // Inserts key,value pair into the map if the key isn't already in the map.
2 // The value is constructed in-place if the key is not in the map, otherwise
3 // it is not moved.
4 template <typename... Ts>
5 std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
6 BucketT *TheBucket;
7 if (LookupBucketFor(Key, TheBucket))// 关联的 key 在不在 bucket 中
8 return std::make_pair(
9 makeIterator(TheBucket, getBucketsEnd(), true),
10 false); // Already in map.已经在了
12 // Otherwise, insert the new element.
13 // key 是新的,把 key 插入 bucket 中
14 TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
15 return std::make_pair(
16 makeIterator(TheBucket, getBucketsEnd(), true),
17 true);
18 }

3、LookupBucketFor(),有 2 个方法,外部进入下面的那个参数是非 const 的函数,但最终是会走到上面的函数中,并进行 while(){}:

LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) 代码 - 源码 03:

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
1 template<typename LookupKeyT>
2 bool LookupBucketFor(const LookupKeyT &Val,
3 const BucketT *&FoundBucket) const {
4 const BucketT *BucketsPtr = getBuckets();
5 const unsigned NumBuckets = getNumBuckets();
7 if (NumBuckets == 0) {
8 FoundBucket = nullptr;
9 return false;
10 }
12 // FoundTombstone - Keep track of whether we find a tombstone while probing.
13 const BucketT *FoundTombstone = nullptr;
14 const KeyT EmptyKey = getEmptyKey();
15 const KeyT TombstoneKey = getTombstoneKey();
16 assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
17 !KeyInfoT::isEqual(Val, TombstoneKey) &&
18 "Empty/Tombstone value shouldn't be inserted into map!");
20 unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);// 哈希函数 算下标
21 unsigned ProbeAmt = 1;
22 // 开始 while
23 while (true) {
24 const BucketT *ThisBucket = BucketsPtr + BucketNo;// 指针位置移动
25 // Found Val's bucket? If so, return it.
26 // 找到了 value 的 bucket 则返回 bucket 赋给外部的值->FoundBucket,然后return寻找的结果为true
27 if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
28 FoundBucket = ThisBucket;
29 return true;
30 }
32 // If we found an empty bucket, the key doesn't exist in the set.
33 // Insert it and return the default value.
34 // 找到了一个空的 bucket --> 插入一个空的 bucket 并赋给 FoundBucket,然后return查找结果为false
35 if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
36 // If we've already seen a tombstone while probing, fill it in instead
37 // of the empty bucket we eventually probed to.
38 FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
39 return false;
40 }
42 // 处理 然后进行继续 while 循环操作
43 // If this is a tombstone, remember it. If Val ends up not in the map, we
44 // prefer to return it than something that would require more probing.
45 // Ditto for zero values.
46 if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
47 !FoundTombstone)
48 FoundTombstone = ThisBucket; // Remember the first tombstone found.
49 if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
50 FoundTombstone = ThisBucket;
52 // Otherwise, it's a hash collision or a tombstone, continue quadratic
53 // probing.
54 if (ProbeAmt > NumBuckets) {
55 FatalCorruptHashTables(BucketsPtr, NumBuckets);
56 }
57 BucketNo += ProbeAmt++;
58 BucketNo &= (NumBuckets-1);
59 }
60

4、InsertIntoBucket() 代码:

1
2
3
4
5
6
7
8
1 template <typename KeyArg, typename... ValueArgs>
2 BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
3 ValueArgs &&... Values) {
4 TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
6 TheBucket->getFirst() = std::forward<KeyArg>(Key);
7 ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
8 return TheBucket;
9 }

InsertIntoBucketImpl() 代码:

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
1 template <typename LookupKeyT>
2 BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
3 BucketT *TheBucket) {
4 // If the load of the hash table is more than 3/4, or if fewer than 1/8 of
5 // the buckets are empty (meaning that many are filled with tombstones),
6 // grow the table.
7 //
8 // The later case is tricky. For example, if we had one empty bucket with
9 // tons of tombstones, failing lookups (e.g. for insertion) would have to
10 // probe almost the entire table until it found the empty bucket. If the
11 // table completely filled with tombstones, no lookup would ever succeed,
12 // causing infinite loops in lookup.
13 unsigned NewNumEntries = getNumEntries() + 1;
14 unsigned NumBuckets = getNumBuckets();
15 if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
16 this->grow(NumBuckets * 2);
17 LookupBucketFor(Lookup, TheBucket);
18 NumBuckets = getNumBuckets();
19 } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
20 NumBuckets/8)) {
21 this->grow(NumBuckets);
22 LookupBucketFor(Lookup, TheBucket);
23 }
24 ASSERT(TheBucket);
26 // Only update the state after we've grown our bucket space appropriately
27 // so that when growing buckets we have self-consistent entry count.
28 // If we are writing over a tombstone or zero value, remember this.
29 if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
30 // Replacing an empty bucket.
31 incrementNumEntries();
32 } else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
33 // Replacing a tombstone.
34 incrementNumEntries();
35 decrementNumTombstones();
36 } else {
37 // we should be purging a zero. No accounting changes.
38 ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
39 TheBucket->getSecond().~ValueT();
40 }
42 return TheBucket;
43 }

5、setHasAssociatedObjects() 源码见下面代码:

执行到‘源码 01’ 处,second 值的判断 (try_emplace() 的返回值),是第一次插入则为 true

–> setHasAssociatedObjects(), nopointer isa –> isa.has_assoc 关联标志位设为了 true。 isa 结构见《OC 底层探索 03 中 isa 结构》。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 inline void
2 objc_object::setHasAssociatedObjects()
3 {
4 if (isTaggedPointer()) return;
6 retry:
7 isa_t oldisa = LoadExclusive(&isa.bits);
8 isa_t newisa = oldisa;
9 if (!newisa.nonpointer || newisa.has_assoc) {
10 ClearExclusive(&isa.bits);
11 return;
12 }
13 newisa.has_assoc = true;// isa 的关联标志位
14 if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
15 }

2、执行流程

1)断点调试执行流程

1、处理第一此关联:

走进 try_emplace() 第一次执行 de 流程:

_object_set_associative_reference() - 开始 set 关联

–> try_emplace() - refs_result

–> LookupBucketFor() - 找 bucket : return false –> InsertIntoBucket() - 插入一个空的 bucket :

      –> InsertIntoBucketImpl() –> LookupBucketFor() - 得到一个空的 bucket

–> setHasAssociatedObjects() - isa.hsa_assoc 设为 true

–> 再次 执行到 try_emplace() - refs_result.first->second

继续执行工程,第二次走进入 try_emplace() ,bucket 不为空:

2、添加第 2 个关联时执行:

对象关联流程图

2)关联类的 map 的结构分析

1、lldb 数据分析:

2、类型 de 结构分析:

 

首次 try_emplace() 数据:

第 2 次 try_emplace() 数据

 

总结 (套娃,buckets 中装了 buckets 又装了 bucket):

tips:关联对象移除吗?需要!–> dealloc 流程 如下:

源码: 

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
1 // rootDealloc 
2 inline void
3 objc_object::rootDealloc()
4 {
5 if (isTaggedPointer()) return; // fixme necessary?
7 if (fastpath(isa.nonpointer &&
8 !isa.weakly_referenced &&
9 !isa.has_assoc &&
10 !isa.has_cxx_dtor &&
11 !isa.has_sidetable_rc))
12 {
13 assert(!sidetable_present());
14 free(this);
15 }
16 else {// isa.has_assoc = true - 有关联对象
17 object_dispose((id)this);
18 }
19 }
21 /***********************************************************************
22 * object_dispose
23 * fixme
24 * Locking: none
25 **********************************************************************/
26 id
27 object_dispose(id obj)
28 {
29 if (!obj) return nil;
31 objc_destructInstance(obj);
32 free(obj);
34 return nil;
35 }
37 /***********************************************************************
38 * objc_destructInstance
39 * Destroys an instance without freeing memory.
40 * Calls C++ destructors.
41 * Calls ARC ivar cleanup.
42 * Removes associative references.
43 * Returns `obj`. Does nothing if `obj` is nil.
44 **********************************************************************/
45 void *objc_destructInstance(id obj)
46 {
47 if (obj) {
48 // Read all of the flags at once for performance.
49 bool cxx = obj->hasCxxDtor();
50 bool assoc = obj->hasAssociatedObjects();
52 // This order is important.
53 if (cxx) object_cxxDestruct(obj);
54 if (assoc) _object_remove_assocations(obj);
55 obj->clearDeallocating();
56 }
58 return obj;
59 }
61 // Unlike setting/getting an associated reference,
62 // this function is performance sensitive because of
63 // raw isa objects (such as OS Objects) that can't track
64 // whether they have associated objects.
65 void
66 _object_remove_assocations(id object)
67 {
68 ObjectAssociationMap refs{};
70 {
71 AssociationsManager manager;
72 AssociationsHashMap &associations(manager.get());
73 AssociationsHashMap::iterator i = associations.find((objc_object *)object);
74 if (i != associations.end()) {
75 refs.swap(i->second);
76 associations.erase(i);// 擦除抹掉
77 }
78 }
80 // release everything (outside of the lock).释放所有
81 for (auto &i: refs) {
82 i.second.releaseHeldValue();
83 /**
84 inline void releaseHeldValue() {
85 if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
86 objc_release(_value);
87 }
88 }
89 */
90 }
91 }
93 // clearDeallocating
94 inline void
95 objc_object::clearDeallocating()
96 {
97 if (slowpath(!isa.nonpointer)) {
98 // Slow path for raw pointer isa.
99 sidetable_clearDeallocating();
100 }
101 else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
102 // Slow path for non-pointer isa with weak refs and/or side table data.
103 clearDeallocating_slow();
104 }
106 assert(!sidetable_present());
107 }

以上。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!