Article directory
1. SideTables
Reference from: iOS Memory Management (3) Detailed explanation of SideTables. SideTables
are closely related to iOS memory management. Let’s study SideTables today. First, let’s take a look at the definition of SideTables.
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
The actual type of SideTables is the StripedMap that stores SideTbale . In the StripedMap class, StripeCount defines the maximum number of sidetables to be stored, so each SideTables can correspond to multiple objects, and each object corresponds to a sidetable.
1.1 StripedMap
From the above we know that SideTables is actually a global hash bucket and returns a StripedMap& type reference:
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum {
StripeCount = 8 };
#else
enum {
StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
...
}
1 StripeCount defines the maximum number of objects stored in it.
enum {
StripeCount = 8 };
2. Define the structure PaddedT to wrap the incoming generic (here refers to SideTable), and use the alignas(CacheLineSize) method to align the bytes. The purpose of guessing byte alignment is to improve the efficiency of accessing hash values.
struct PaddedT {
T value alignas(CacheLineSize);
};
3. Implemented the hash algorithm indexForPointer for index calculation.
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
4. Get the operation getLock method of sidetable
const void *getLock(int i) {
if (i < StripeCount) return &array[i].value;
else return nil;
}
5. Other lock operations, array[i].value.lock, or array[i].value.unlock(), call the lock in the sidetable.
void lockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.lock();
}
}
That is to say, the StripedMap returned by SideTables() is a hash bucket whose value is SideTable (since SideTable maintains an array internally, this is a hash bucket structure), and the hash value is calculated from the address of the object.
The SideTables structure diagram is shown below:
1.2 SideTable
SideTable contains three members, spin lock, reference counting table and weak reference table.
- spinlock_t slock; // 保证原子操作的自旋锁
- RefcountMap refcnts; // 引用计数的hash 表
- weak_table_t weak_table;
It is mainly used to manage reference counts and weak tables of objects.
slock
: A spin lock selected to prevent competition.refcnts
: A table used to store the reference count of OC objectshash
(only used when isa optimization is not turned on or the reference count of isa_t overflows under isa optimization).weak_table
: A hash table that stores weak reference pointers to objects. It is the core data structure for realizing the weak function in OC.
Slock
- slock is a spin lock. In order to ensure the security of multi-threaded access: lock SideTable when operating reference counting to avoid data errors.
- ⚠️: The essence of the slock here is a spin lock. We add a lock to each SideTable to prevent a certain SideTable from being accessed multiple times. This is a detached lock.
refcnts
- Essentially, it is a hash table that stores object reference counts. The key is the object and the value is the reference count (it has been optimized in isa, and the reference count is mainly stored in isa)
weak_table
- It is a structure that stores weak references to objects. The members of the structure are as follows
- weak_entry_t *weak_entries;
- size_t num_entries;
- uintptr_t mask;
- uintptr_t max_hash_displacement;
1.3 Reference counting refcnts storage structure RefcountMap
RefcountMap is defined as follows, its type is objc::DenseMap.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
The three parameters respectively represent the hash key and reference count of the object , and whether the corresponding hash node needs to be automatically released when the reference count reaches 0. True is passed here by default.
Therefore, the object's reference count and refcnts do not necessarily exist: only the optimized isa uses extea_rc to store the reference count, and only when its storage count overflows will the refcnts of the sidetable be stored.
1.4 weak_table_t structure
/**
全局的弱引用表, 保存object作为key, weak_entry_t作为value
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
// 保存了所有指向特地对象的 weak指针集合
weak_entry_t *weak_entries;
// weak_table_t中有多少个weak_entry_t
size_t num_entries;
// weak_entry_t数组的count
uintptr_t mask;
// hash key 最大偏移值,
// 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_t
uintptr_t max_hash_displacement;
};
weak_entries:hash
Array, used to store information about weak reference objects weak_entry_t.num_entries:hash
The number of elements in the array.mask:hash
Array length -1, will participate in hash calculation. (Note that this is the length of the hash array, not the number of elements. For example, the array length may be 64, but the number of elements is only 2).max_hash_displacement
: The maximum number of hash conflicts that may occur, used to determine whether a logical error has occurred (the number of conflicts in the hash table will never exceed the changed value).
weak_table_t
is a typical hash
structure. weak_entries
It is a dynamic array used to store weak_entry_t
elements of a type. These elements are actually weak reference information of OC objects.
1.4.1 .weak_entry_t structure
weak_entry_t
The structure of is also a hash
structure, and its stored elements are pointers to weakly referenced object pointers. By manipulating the pointer of the pointer, the referenced pointer can be weak
pointed to after the object is destructed nil
. The implementation code is as follows:
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
// 所有weak指针指向的特定对象
DisguisedPtr<objc_object> referent; // 被弱引用的对象
// 共用体,保存weak指针的集合,
// 引用个数小于4,用inline_referrers数组。用个数大于4,用动态数组weak_referrer_t *referrers
union {
struct {
weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组
uintptr_t out_of_line : 1; // 是否使用动态hash数组标记位
uintptr_t num_refs : PTR_MINUS_1; // hash数组中的元素个数
uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
In weak_entry_t
the structure, DisguisedPtr
referent
the pointer of the generic object is encapsulated, and the problem of memory leakage is solved through this generic class. Moreover, there is a table inside weak_entry_t
and weak_table_t
outside hash
, and the hash conflicts are resolved using the open value method. The member is written as the least significant bit from the comment out_of_line
. When it is 1, weak_referrer_t
the member will be expanded to hash table
. The weak_referrer_t is an alias of an array.
out_of_line
: Flag bit. Marks whether weak_entry_t is saved in an array or hash table to save the weak pointer.num_refs
: Reference counting. The number of weak pointers in the weak_entry_t table is recorded here.mask
: weak_entry_t->count of referrers array.max_hash_displacement
: The maximum offset value of hash key, using the open customization method to resolve hash conflicts. If it exceeds max_hash_displacement, it means that the weak_entry_t you are looking for does not exist in weak_entry_t.
The out_of_line
value is usually equal to zero, so the weak reference table is always an objc_objective
array of pointers. When it exceeds 4, it will become a hash table.
The structure of weak is as shown in the figure
2.1 Implementation of weak and related functions called
- Basic concept: Based on our previous experience: weak is a weak reference. The counter of the referenced object will not be increased by one and will be automatically set to nil when the referenced object is released.
- Implementation principle: Runtime maintains a weak table to store all weak pointers pointing to an object. The weak table is actually a hash table. Key is the address of the pointed object, and Value is the address of the weak pointer (the value of this address is the address of the pointed object pointer, which is the address of the address). The collection (when the weak pointer is When the number is less than or equal to 4, it is an array, and when it exceeds, it becomes a hash table).
The implementation principle of weak is summarized in the following three steps:
1. During initialization: a function runtime
will be called objc_initWeak
to initialize a new weak pointer pointing to the address of the object.
2. When adding a reference: objc_initWeak
The function will call objc_storeWeak
the () function, objc_storeWeak()
which is used to update the pointer and create the corresponding weak reference table.
3. When released, call clearDeallocating
the function. clearDeallocating
The function first obtains weak
an array of all pointer addresses based on the object address, then traverses the array and sets the data in it to nil
, and finally deletes this entry
from weak
the table and cleans up the object's records.
2.1 During initialization:
1. During initialization:
runtime
The function will be called objc_initWeak
, and objc_initWeak
the function will initialize a new weak
pointer pointing to the address of the object.
2.1.1 objc_initWeak method
The underlying source code of the objc_initWeak method is as follows:
// location指针objc , newObj原始对象object
id objc_initWeak(id *location, id newObj) {
// 查看原始对象实例是否有效
// 无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
This method has two parameters location
and newObj
.
location
:__weak
The address of the pointer, which stores the address of the pointer so that the object it points to can be set to nil at the end.newObj
: The object referenced. That is the object in the example.
From the above code, we can see that objc_initWeak
the method is just the entrance to a deep function call, and the method is called inside the storeWeak
method. Let's take a look at storeWeak
the implementation code of the method.
Note⚠️: objc_initWeak
The function has a prerequisite: it object
must be a __weak
valid pointer that has not been registered as an object. And value
it can be nil
, or point to a valid object.
2.1.2 storeWeak method
The underlying source code of the storeWeak method is as follows:
// HaveOld: true - 变量有值
// false - 需要被及时清理,当前值可能为 nil
// HaveNew: true - 需要被分配的新值,当前值可能为 nil
// false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
// false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 该过程用来更新弱引用指针的指向
// 初始化 previouslyInitializedClass 指针
Class previouslyInitializedClass = nil;
id oldObj;
// 声明两个 SideTable
// ① 新旧散列创建
SideTable *oldTable;
SideTable *newTable;
// 获得新值和旧值的锁存位置(用地址作为唯一标示)
// 通过地址来建立索引标志,防止桶重复
// 下面指向的操作会改变旧值
retry:
// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable,即获取其旧的Table
if (HaveOld) {
// 更改指针,获得以 oldObj 为索引所存储的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
// 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
oldTable = nil;
}
// 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
if (HaveNew) {
// 更改新值指针,获得以 newObj 为索引所存储的值地址
newTable = &SideTables()[newObj];
} else {
// 如果weak ptr不需要引用一个新obj,则newTable = nil
newTable = nil;
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
// 避免线程冲突重处理
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改,需要返回上边重新处理
if (HaveOld && *location != oldObj) {
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用间死锁
// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 获得新对象的 isa 指针
Class cls = newObj->getIsa();
// 如果cls还没有初始化,先初始化,再尝试设置weak
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解锁
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
// 对其 isa 指针进行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果该类已经完成执行 +initialize 方法是最理想情况
// 如果该类 +initialize 在线程中
// 例如 +initialize 正在调用 storeWeak 方法
// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记,防止改if分支再次进入
previouslyInitializedClass = cls;
// 重新获取一遍newObj,这时的newObj应该已经初始化过了
goto retry;
}
}
// ② 清除旧值
// 如果之前该指针有弱引用过一个obj那就得需要清除之前的弱引用
if (HaveOld) {
// 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ③ 分配新值
// 如果weak_ptr需要弱引用新的对象newObj
if (HaveNew) {
// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
// 如果弱引用被释放 weak_register_no_lock 方法返回 nil
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// (2) 更新newObj的isa的weakly_referenced bit标志位
if (newObj && !newObj->isTaggedPointer()) {
// 弱引用位初始化操作
// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
newObj->setWeaklyReferenced_nolock();
}
// (3)*location 赋值,也就是将weak ptr直接指向了newObj,也就是确保其指针指向是正确的。可以看到,这里并没有将newObj的引用计数+1
*location = (id)newObj;
}
else {
// 没有新值,则无需更改
}
// 解锁,其他线程可以访问oldTable, newTable了
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
// 返回newObj,此时的newObj与刚传入时相比,设置了weakly-referenced bit位置1
return (id)newObj;
}
The storeWeak method does the following:
storeWeak
The method actually receives 5 parameters, namelyhaveOld
, ,haveNew
andcrashIfDeallocating
. These three parameters are all passed in in the form of templates and are three bool type parameters. They respectively indicateweak
whether the pointer pointed to a weak reference before, whether the weak pointer needs to point to a new reference, and if the weakly referenced object is being destructed, whether the weakly referenced object should crash at this time. Alsoid *location
,objc_object *newObj
- This method maintains
oldTable
andnewTable
represents the old weak reference table and the new weak reference table respectively, both of which areSideTable
hash tables. - If the weak pointer previously pointed to a weak reference,
weak_unregister_no_lock
a method will be called to remove the oldweak
pointer address. - If the weak pointer needs to point to a new reference,
weak_register_no_lock
a method will be called to add the newweak
pointer address to the weak reference table. - Call
setWeaklyReferenced_nolock
the method to modifyweak
the bit flag of the newly referenced object.
So the focus of this method is weak_unregister_no_lock
and weak_register_no_lock
these two methods. Both of these methods operate on SideTable
variables of such a structure.
2.2 When adding a reference:
objc_initWeak
The function will call objc_storeWeak()
the function, and objc_storeWeak
the function of () is to update the pointer and create the corresponding weak reference table.
2.2.1 weak_register_no_lock method
Add a registration operation for a new object weak_register_no_lock
, weak_register_no_lock
register the new object through a function, and complete the binding operation with the corresponding weak reference table.
The implementation code is as follows:
/* weak_table:weak_table_t结构类型的全局的弱引用表。
referent_id:weak指针所指的对象。
*referrer_id:weak修饰的指针的地址。
crashIfDeallocating:如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
if (!referent || referent->isTaggedPointer()) return referent_id;
// 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
//不能被weak引用,直接返回nil
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 正在析构的对象,不能够被弱引用
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 如果能找到weak_entry,则讲referrer插入到weak_entry中
append_referrer(entry, referrer); // 将referrer插入到weak_entry_t的引用数组中
}
else {
// 如果找不到,就新建一个
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
weak_register_no_lock works as follows:
- If
referent
it isnil
orreferent
usesTaggedPointer
counting mode, it returns directly without any operation. - If the object cannot be
weak
referenced, return it directlynil
. - If the object is being destroyed, an exception is thrown.
- If the object is not destructed and can be referenced
weak
, the callingweak_entry_for_referent
method finds the corresponding weak reference object from the weak reference table based on the address of the weak reference objectweak_entry
. If it can be found, the calling method inserts the pointer addressappend_referrer
into it .weak
Otherwise create a new oneweak_entry
.
weak_entry_for_referent takes the element method and append_referrer adds the element
weak_entry_for_referent takes elements
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask; // 这里通过 & weak_table->mask的位操作,来确保index不会越界
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
// index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
if (index == begin) bad_weak_table(weak_table->weak_entries); // 触发bad weak table crash
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
// 当hash冲突超过了可能的max hash 冲突时,说明元素没有在hash表中,返回nil
return nil;
}
}
//返回找到的元素
return &weak_table->weak_entries[index];
}
.append_referrer adds elements
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
// 如果weak_entry 尚未使用动态数组,走这里
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
// 找到一个空位直接插入,结束返回
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。
// Couldn't insert inline. Allocate out of line.
// 创建一个动态数组,并将之前的静态数组的值都赋给动态数组
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[I];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
// 对于动态数组的附加处理:
assert(entry->out_of_line()); // 断言:此时一定使用的动态数组
// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
// 扩容,并插入
return grow_refs_and_insert(entry, new_referrer);
}
// 如果不需要扩容,直接插入到weak_entry中
// 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
// 细心的人可能注意到了,这里weak_entry_t 的hash算法和 weak_table_t的hash算法是一样的,同时扩容/减容的算法也是一样的
size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 确保了 begin的位置只能大于或等于 数组的长度
size_t index = begin; // 初始的hash index
size_t hash_displacement = 0; // 用于记录hash冲突的次数,也就是hash再位移的次数
// 使用循环找到一个合适的空位
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask; // index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)
if (index == begin) bad_weak_table(entry); // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
}
// 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
// 将值插入刚才找到的hash表的空位,同时,更新元素个数num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
2.2.2 weak_unregister_no_lock removes references
If the weak pointer previously pointed to a weak reference, weak_unregister_no_lock
a method will be called to remove the old weak
pointer address.
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
// 弱引用对象为nil不存在,直接返回
if (!referent) return;
// 查找到referent所对应的weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer); // 在referent所对应的weak_entry_t的hash数组中,移除referrer
// 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
return;
}
The weak_unregister_no_lock method does the following when removing the reference:
referent(弱引用对象)·
First, it will find the corresponding weak_entry_t in the weak_table .weak_entry_t
Removed inreferrer
.- After removing the element, determine
weak_entry_t
whether there are still elements at this time (empty==true?). - If
weak_entry_t
there are no elements left at this time, they needweak_entry_t
to beweak_table
removed from them.
So far, this is what is done at the bottom when making a weak reference to an object. After using a weak reference to the object, the reference count will not be increased by 1. When the object is released, how are all pointers that weak references it automatically set to nil?
3.1 When released:
Call clearDeallocating
the function. clearDeallocating
The function first obtains an array of all weak
pointer addresses based on the object address, then traverses the array and sets the data in it to nil
, finally deletes this entry
from weak
the table, and finally clears the object's records.
3.1.1. When the object pointed to by the weak reference is released, how is the weak pointer handled?
When an object is released, the basic process is as follows:
1. Call objc_release
.
2. Because the reference count of the object is 0, execute dealloc
.
3. In dealloc
, the function is called _objc_rootDealloc
.
4. In _objc_rootDealloc
, the object_dispose function is called.
5. Call objc_destructInstance
.
6. Final call objc_clear_deallocating
.
3.1.2 dealloc method
When the object's reference count is 0, the underlying _objc_rootDealloc
method will be called to release the object, and the method _objc_rootDealloc
will be called inside the method rootDealloc
. The following is rootDealloc
the code implementation of the method:
inline void
objc_object::rootDealloc()
{
// 判断对象是否是Tagged Pointer,如果是则直接返回。
if (isTaggedPointer()) return; // fixme necessary?
//如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
// 调用object_dispose方法。
object_dispose((id)this);
}
}
This function also involves object_dispose
methods
3.1.3 object_dispose method
object_dispose
objc_destructInstance
The method is simple, mainly because the method is called internally
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
// 是否有自定义的C++析构方法
bool cxx = obj->hasCxxDtor();
// 是否有关联对象
bool assoc = obj->hasAssociatedObjects();
// This order is important.
// 如果有自定义的C++析构方法,则调用C++析构函数。
if (cxx) object_cxxDestruct(obj);
// 如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
3.1.4 .clearDeallocating method
inline void
objc_object::clearDeallocating()
{
// 判断对象是否采用了优化isa引用计数
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
// 如果没有的话则需要清理对象存储在SideTable中的引用计数数据
sidetable_clearDeallocating();
}
// 如果对象采用了优化isa引用计数,则判断是否有使用weak引用(isa.weakly_referenced)或者有使用SideTable的辅助引用计数(isa.has_sidetable_rc),符合这两种情况中一种的,调用clearDeallocating_slow方法。
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
3.1.5 .clearDeallocating_slow Method
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 在全局的SideTables中,以this指针为key,找到对应的SideTable
SideTable& table = SideTables()[this];
// 上锁
table.lock();
// 如果obj被弱引用
if (isa.weakly_referenced) {
// 在SideTable的weak_table中对this进行清理工作
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 如果采用了SideTable做引用计数
if (isa.has_sidetable_rc) {
// 在SideTable的引用计数中移除this
table.refcnts.erase(this);
}
// 解锁
table.unlock();
}
What we are concerned with here is weak_clear_no_lock
method. weak_clear_no_lock
The cleanup work is called here weak_table
.
3.1.6 weak_clear_no_lock method
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 找到referent在weak_table中对应的weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
// 找出weak引用referent的weak 指针地址数组以及数组长度
if (entry->out_of_line()) {
// 如果是动态数组
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
// 如果是静态数组
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
// 取出每个weak ptr的地址
objc_object **referrer = referrers[i];
if (referrer) {
// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
// 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
// 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
weak_entry_remove(weak_table, entry);
}
objc_clear_deallocating
The action of this function is as follows:
1. From the weak table, use the dealloc object as the key to find the corresponding weak_entry_t.
2. Assign all addresses in weak_entry_t with weak modifier variables to nil.
3. Remove the object from the weak table.
Summarize
weak
The principle is that the bottom layer maintains aweak_table_t
hash table of structure. The key is the address of the pointed object andvalue
theweak
address array of the pointer.weak
The function of the keyword is a weak reference. The counter of the referenced object will not be increased by 1 and will be automatically set when the referenced object is releasednil
.- When the object is released, the calling
clearDeallocating
function obtains the array of all pointer addresses based on the object addressweak
, then traverses the array to set the data in itnil
, and finally deletes thisentry
fromweak
the table, and finally cleans up the object's records. - The article introduces
SideTable、weak_table_t、weak_entry_t
such three structures, and the relationship between them is shown in the figure below.
- For the initialization of weak references, it can be seen from the above analysis that the main operation part is to obtain the value of the weak reference table, query the sequence, create the weak reference table and other operations, which are summarized as follows.