美文网首页
iOS Weak 原理杂谈

iOS Weak 原理杂谈

作者: End_枫 | 来源:发表于2021-03-04 16:05 被阅读0次

关于 weak 的实现原理,网上已经有各种详细的文章了,大多贴了一段代码,然后根据代码解释流程,这里随便找了一篇供参考:iOS weak的实现原理

就我个人而言,看完这些文章感觉还是理解不深,特别是数据结构方面的,比如 weak 表,可能很多人都只有一个大概的认知:weak 指针存放在weak表中,这是一种哈希表,对象作为key,weak指针数组作为value,然后就没有了,没有更清晰的认知,这里根据源码调试结果,主要谈谈weak表相关的结构。

以下面代码为例子:

NSObject *obj = [NSObject new];
__weak id weakObj = obj;

通过 objc_initWeak 最后走到关键方法 storeWeak(这里去掉部分代码,保留关键逻辑)

static id storeWeak(id *location, objc_object *newObj)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    return (id)newObj;
}

先看代码 newTable = &SideTables()[newObj];
这里的 newObj 就是我们上面的NSObject 对象 obj,这段代码可以看成如下三行:

// newTable = &SideTables()[newObj];

SideTable * newTable = nil;
StripedMap *map = &SideTables();
newTable = map[newObj];

其中 StripedMap 是一个全局的哈希表(我们常说的weak表就是这个),这是一个C++类,通过重写下标操作符[]来实现key-value存取,其内部维护了一个数组,大小是 64,也就是最多可以存储 64 个SideTable,这里以对象地址为key,返回一个SideTable,内部原理大概是这样的:

// StripedMap *map = &SideTables();
// newTable  = map[newObj];
unsigned int index = someHashCode(newObj)%64;
newTable = map.array[index];

哈希表的大小是64,哈希冲突时采用数组存储冲突的对象,再看 SideTable 结构(简化版):

struct SideTable {
    spinlock_t slock;         // 自旋锁
    RefcountMap refcnts;      // 引用计数
    weak_table_t weak_table;  // weak 指针
};
struct weak_table_t {
    weak_entry_t *weak_entries;
};

其中 weak_table 用来存放weak指针相关的信息,内部是一个数组weak_entries,数组的元素是 weak_entry_t,这个 weak_entry_t 是最后weak指针存放的位置,一个对象对应一个 weak_entry_t,这里的数组 weak_entries 就是用来存放不同对象的weak指针,再来看 weak_entry_t:

struct weak_entry_t {
    // 存储对象 obj
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // 存储weak指针 weakObj
            weak_referrer_t *referrers;
        };
        struct {
            // 存储weak指针 weakObj
            // WEAK_INLINE_COUNT = 4;
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

其中 referent 用来存放对象,referrers ,inline_referrers 用来存储 weak指针,一个对象可以有多个weak指针指向它,默认情况下,weak指针存放在 inline_referrers 中,当 inline_referrers 存满后(超过4个)就会转到 referrers 中动态分配内存存储。

NSObject *obj = [NSObject new];
__weak id weakObj = obj;

用伪伪伪代码表示上面代码weak指针的存储过程:

storeWeak(id * weakObj, id obj) {
    // 获取全局 StripedMap 哈希表对象
    StripedMap *map = &SideTables();
    // 获取 obj 所映射位置的 SideTable
    SideTable * newTable = map[obj];
    // 获取 weak指针数组
    weak_table_t table_t = newTable->weak_table;
    
    // 创建 entry_t 存储 weakObj 和 obj 的对应关系
    weak_entry_t entry_t;
    entry_t.referent = obj;
    entry_t.inline_referrers[0] = weakObj;
    // 插入
    table_t.weak_entries->insert(entry_t);

    // SideTable * newTable = map[obj];
    // newTable->weak_table.weak_entries->insert(entry_t);
}

上述就是存储一个weak指针的基本过程,剔除了海量细节,比如已存在obj weak指针的处理,但是只要清楚了数据结构,其他方面没有太多的黑魔法,都是正常的操作。

至于weak指针的释放,在 obj dealloc 方法里,通过一系列调用,最后会调用到 weak_clear_no_lock 中,核心代码如下(删删删):

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    // 找到对象 referent_id(就是上面的obj) 对应的 weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) { return;}
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    // 前面说过,对象 referent_id  的 weak指针超过4个就会转移到 referrers 中
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else { // 否则在存在 inline_referrers 中
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        // 找到所有的 weak 指针
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                // weak 指针置为 nil
                *referrer = nil;
            }
        }
    }
    // 从 weak_table 的 weak_entries 数组中移除 entry
    weak_entry_remove(weak_table, entry);
}

weak 的原理大体上就是这样,需要注意的是对于 TaggedPointer 对象,不走这一套流程,也就是不存储 TaggedPointer 对象的 weak 指针,因为 TaggedPointer 对象是存储在指针本身里的,是在栈区,不是堆区,内存由系统自动管理。

相关文章

网友评论

      本文标题:iOS Weak 原理杂谈

      本文链接:https://www.haomeiwen.com/subject/gwkkqltx.html