Weak, Runtime

iOS 中使用引用计数来判断一个对象是否该释放.

void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
    if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;

    ptrdiff_t offset;
    objc_ivar_memory_management_t memoryManagement;
    _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);

    if (memoryManagement == objc_ivar_memoryUnknown) {
        if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
        else memoryManagement = objc_ivar_memoryUnretained;
    }

    id *location = (id *)((char *)obj + offset);

    switch (memoryManagement) {
    case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
    case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
    case objc_ivar_memoryUnretained: *location = value; break;
    case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
    }
}

Strong对象是通过objc_storeStrong来处理的, 如下代码,
引用计数具体的代码可以下载源码查看, 这里直接说objc_retain(obj);引用计数会加一.

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

weak 对象是通过 objc_storeWeak 处理的, 如下代码, 具体 weak 处理后面讲, 先说一下 weak 对象最后返回的是 newObj, 就是你要置为 weak 的对象, *location = (id)newObj; , 发现*location 指向的也是 newObj, 如下一小段代码, weakPobj 指向的内存地址是相同的.
但这里没有调用objc_retain进行引用计数加一.
objc_storeWeak(&weakP, obj); 目前看来和 NSObject *a = obj的区别就是 weak 修饰后没有引用计数加一.

简单解释一次 objc_storeWeak 参数, location 是新对象的地址, newObj 是想要 weak 的对象.

__weak NSObject *weakP;
objc_storeWeak(&weakP, obj);
static id 
storeWeak(id *location, objc_object *newObj)
{
    /* 这里把 location 加入到了weak 表中 */
// Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

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

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    return (id)newObj;
}

想一个问题, weak 修饰的对象并没有造成引用计数加一, 当所有没有用weak 修饰的对象都置为 nil 的时候, 指向的内存会释放掉, weak 修饰的对象还没有被置为 nil, ok 造成野指针.
接下来说, 是如何在对象在释放的时候weak 修饰的对象也被置为 nil.

代码层面, 调用的也是 storeWeak, 只是赋值为 nil. 如下:

void
objc_destroyWeak(id *location)
{
    (void)storeWeak
        (location, nil);
}

当调用 storeWeak的时候, 会调用weak_unregister_no_lock解绑之前的绑定的对象, 然后调用weak_register_no_lock, 绑定新的对象,
这里说的绑定是指, 把 location 加入到 weak_table 中, weak_grow_maybe(weak_table) 扩容, 具体可以看源码.

// 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);
        // weak_register_no_lock returns nil if weak store should be rejected

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

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }

weak_entry_t 就是一个对象的 weak 信息.
DisguisedPtr referent;就是对象的指针.
referrers 和 inline_referrers 就是 referent 对应的所有 location 的指针(location 是 weak 指针的地址)

每次查找对象, 就首先通过 referent, 找到对应的 entry 对象, 然后再通过 location, 在 inline_referrers 中新增或者删除 weak 对象.

struct weak_entry_t {
    DisguisedPtr referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

weak 对象什么时候释放?
当一个对象 dealloc 的时候会调用 weak_clear_no_lock, 通过对象referent_id找到 entry, 并释放所有 weak 对象 *referrer = nil;, 通过 weak_entry_remove 删除 weak_table 中对应的 entry.

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 

关于 NSHashTable weakObjectsHashTable, 可以弱引用存放 weak 对象, 很棒!

__weak NSObject *weakObj;
    NSHashTable *table = [NSHashTable weakObjectsHashTable];
    {
        NSObject *obj = [NSObject new];
        weakObj = obj;
        [table addObject:obj];
        NSLog(@"all: %@", obj);
    }
    
    NSLog(@"all: %@", weakObj);

你可能感兴趣的:(Weak, Runtime)