[The underlying principle of iOS-weak]

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:
Insert image description here

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 objects hash(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)

Insert image description here

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:hashArray, used to store information about weak reference objects weak_entry_t.
  • num_entries:hashThe number of elements in the array.
  • mask:hashArray 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_tis a typical hashstructure. weak_entriesIt is a dynamic array used to store weak_entry_telements of a type. These elements are actually weak reference information of OC objects.

1.4.1 .weak_entry_t structure

weak_entry_tThe structure of is also a hashstructure, and its stored elements are pointers to weakly referenced object pointers. By manipulating the pointer of the pointer, the referenced pointer can be weakpointed 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_tthe structure, DisguisedPtr referentthe 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_tand weak_table_toutside 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_tthe 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_linevalue is usually equal to zero, so the weak reference table is always an objc_objectivearray of pointers. When it exceeds 4, it will become a hash table.

The structure of weak is as shown in the figure
Insert image description here

2.1 Implementation of weak and related functions called

  1. 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.
  2. 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 runtimewill be called objc_initWeakto initialize a new weak pointer pointing to the address of the object.
2. When adding a reference: objc_initWeakThe function will call objc_storeWeakthe () function, objc_storeWeak()which is used to update the pointer and create the corresponding weak reference table.
3. When released, call clearDeallocatingthe function. clearDeallocatingThe function first obtains weakan 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 entryfrom weakthe table and cleans up the object's records.

2.1 During initialization:

1. During initialization:

runtimeThe function will be called objc_initWeak, and objc_initWeakthe function will initialize a new weakpointer 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 locationand newObj.

  • location: __weakThe 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_initWeakthe method is just the entrance to a deep function call, and the method is called inside the storeWeakmethod. Let's take a look at storeWeakthe implementation code of the method.

Note⚠️: objc_initWeakThe function has a prerequisite: it objectmust be a __weakvalid pointer that has not been registered as an object. And valueit 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:

  1. storeWeakThe method actually receives 5 parameters, namely haveOld, , haveNewand crashIfDeallocating. These three parameters are all passed in in the form of templates and are three bool type parameters. They respectively indicate weakwhether 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. Also id *location,objc_object *newObj
  2. This method maintains oldTableand newTablerepresents the old weak reference table and the new weak reference table respectively, both of which are SideTablehash tables.
  3. If the weak pointer previously pointed to a weak reference, weak_unregister_no_locka method will be called to remove the old weakpointer address.
  4. If the weak pointer needs to point to a new reference, weak_register_no_locka method will be called to add the new weakpointer address to the weak reference table.
  5. Call setWeaklyReferenced_nolockthe method to modify weakthe bit flag of the newly referenced object.

So the focus of this method is weak_unregister_no_lockand weak_register_no_lockthese two methods. Both of these methods operate on SideTablevariables of such a structure.

2.2 When adding a reference:

objc_initWeakThe function will call objc_storeWeak()the function, and objc_storeWeakthe 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_lockregister 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 referentit is nilor referentuses TaggedPointercounting mode, it returns directly without any operation.
  • If the object cannot be weakreferenced, return it directly nil.
  • If the object is being destroyed, an exception is thrown.
  • If the object is not destructed and can be referenced weak, the calling weak_entry_for_referentmethod finds the corresponding weak reference object from the weak reference table based on the address of the weak reference object weak_entry. If it can be found, the calling method inserts the pointer address append_referrerinto it . weakOtherwise create a new one weak_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_locka method will be called to remove the old weakpointer 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_tRemoved in referrer.
  • After removing the element, determine weak_entry_twhether there are still elements at this time (empty==true?).
  • If weak_entry_tthere are no elements left at this time, they need weak_entry_tto be weak_tableremoved 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 clearDeallocatingthe function. clearDeallocatingThe function first obtains an array of all weakpointer addresses based on the object address, then traverses the array and sets the data in it to nil, finally deletes this entryfrom weakthe 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_rootDeallocmethod will be called to release the object, and the method _objc_rootDeallocwill be called inside the method rootDealloc. The following is rootDeallocthe 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_disposemethods

3.1.3 object_dispose method

object_disposeobjc_destructInstanceThe 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_lockmethod. weak_clear_no_lockThe 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_deallocatingThe 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

  1. weakThe principle is that the bottom layer maintains a weak_table_thash table of structure. The key is the address of the pointed object and valuethe weakaddress array of the pointer.
  2. weakThe 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 released nil.
  3. When the object is released, the calling clearDeallocatingfunction obtains the array of all pointer addresses based on the object address weak, then traverses the array to set the data in it nil, and finally deletes this entryfrom weakthe table, and finally cleans up the object's records.
  4. The article introduces SideTable、weak_table_t、weak_entry_tsuch three structures, and the relationship between them is shown in the figure below.
    Insert image description here
  5. 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.

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_61639290/article/details/131920032