美文网首页
iOS - weak 底层原理

iOS - weak 底层原理

作者: felix6 | 来源:发表于2020-07-05 20:00 被阅读0次

    [toc]

    参考

    weak

    https://blog.csdn.net/future_one/article/details/81606895

    https://www.cnblogs.com/guohai-stronger/p/10161870.html

    https://www.jianshu.com/p/2b12666b351f

    问答

    weak作用?

    weak 是弱引用, 用weak来修饰、描述所引用对象的计数器并不会加1, 而且weak会在引用对象被释放的时候自动置为nil, 这也就避免了野指针访问坏内存而引起崩溃的情况,

    weak也可以解决循环引用。

    为什么修饰代理使用 weak 而不是用assign?

    assign 可用来修饰基本数据类型, 也可修饰OC的对象, 但如果用 assign 修饰对象类型指向的是一个强指针, 当指向的这个指针释放之后, 它仍指向这块内存, 必须要手动给置为nil, 否则会产生野指针, 如果还通过此指针操作那块内存, 会导致 EXC_BAD_ACCESS 错误, 调用了已经被释放的内存空间;

    而 weak 只能用来修饰OC对象, 而且相比assign比较安全, 如果指向的对象消失了, 那么它会自动置为nil, 不会导致野指针。

    weak 本质?
    ARC帮我们做了什么?

    ARC 是 LLVM + Runtime 互相协作的结果

    开启ARC后, LLVM编译器会自动帮我们在相应位置生成 release、retain、autorelease

    在作用域 (大括号) 结束的位置添加 release

    弱引用这种, 是通过runtime处理的

    编译解析

    OC代码
    int main(){
        NSObject *obj = [[NSObject alloc] init];
        id __weak obj1 = obj;
    }
    
    C++代码
    int main(){
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        id __attribute__((objc_ownership(weak))) obj1 = obj;
    }
    

    编译之后的weak, 通过 objc_ownership(weak) 实现 weak 方法, objc_ownership 字面意思是:获得对象的所有权, 是对对象weak的初始化的一个操作。

    通过 weak 编译解析, 可以看出 weak 是通过 runtime 初始化并维护的;

    weak 和 strong 都是 OC ARC 的修饰词, 而 strong 是通过 runtime 维护的一个自动计数表结构。

    结构

    weak_table_t
    • weak_table_t 存储在 struct SideTable 中。(参考《isa指针》)
    • weak_table_t 是 Runtime 维护的一个 weak 引用的全局hash表, 用于存储指向某个对象的所有weak指针。★
      • key: 所指向对象的地址
      • value: weak_entry_t 类型结构体数组(weak指针的数组) 。 value 是数组的原因是: 因为一个对象可能被多个弱引用指针指向 ★

    weak 指针的地址值是所指对象指针的地址

    // objc-weak.h
    /**
     * The global weak references table.  全局的, 内部维护了一个哈希表 ★
     * Stores object ids as keys, and weak_entry_t structs as their values.
     */
    struct weak_table_t {
        weak_entry_t *weak_entries; // 保存了所有指向指定对象的weak指针   weak_entries的对象
        size_t    num_entries; // 弱引用数量
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    
    weak_entry_t

    weak 全局表 weak_table_t 中的存储 weak 定义的对象的表结构 weak_entry_t,

    weak_entry_t 是存储在弱引用表中的一个内部结构体, 它负责维护和存储指向一个对象的所有弱引用hash表

    // 单个弱引用, 存放在哈希表中
    struct weak_entry_t {
        // 对泛型对象的指针做了一个封装, 通过这个泛型类来解决内存泄漏的问题。
        // 相当于 objc_object *referent;
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                // weak_referrer_t 是 objc_object ** 的别名, 通过一个二维指针地址偏移, 用下标作为 hash 的 key, 做成了一个弱引用散列。
                weak_referrer_t *referrers; // weak变量地址
                uintptr_t        out_of_line_ness : 2;
                // 引用数值。这里记录弱引用表中引用有效数字, 因为弱引用表使用的是静态 hash 结构, 所以需要使用变量来记录数目。
                uintptr_t        num_refs : PTR_MINUS_2; 
                uintptr_t        mask;
                // hash 元素上限阀值。
                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];
            };
        };
    
        // 最低有效位, 也是标志位。当标志位 0 时, 增加引用表指针纬度。
        // 当其为0的时候,  weak_referrer_t 成员将扩展为多行静态 hash table。
        // 通常情况下是等于零的, 所以弱引用表总是一个 objc_objective 指针二维数组。
        // 一维 objc_objective 指针可构成一张弱引用散列表, 通过第三纬度实现了多张散列表, 并且表数量为 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;
        }
        // objc_object: weak_entry_t对象中的范型对象, 用于标示weak引用的对象。
        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;
            }
        }
    };
    
    #define WEAK_INLINE_COUNT 4
    #define REFERRERS_OUT_OF_LINE 2
    
    
    
    weak_referrer_t
    // The address of a __weak variable.
    // These pointers are stored disguised(伪装的) so memory analysis tools
    // don't see lots of interior(内部的) pointers from the weak table into objects.
    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    
    /* 苹果对 DisguisedPtr 的注释
    DisguisedPtr<T> acts like pointer type T*, except the stored value is disguised to hide it from tools like `leaks`.
    nil is disguised as itself so zero-filled memory works as expected, 
    which means 0x80..00 is also disguised as itself but we don't care.
    Note that weak_entry_t knows about this encoding.
    */
    

    可以看到 DisguisedPtr<objc_object *> 相当于 objc_object **;

    weak_referrer_t 就是 objc_object ** 的别名

    存储过程

    初始化weak变量时, runtime会调用 objc_initWeak 函数, 初始化新的weak指针指向对象的地址;

    objc_initWeak 函数内部会调用 objc_storeWeak() 函数, objc_storeWeak() 函数的作用是用来更新指针的指向, 创建弱引用表。

    objc_initWeak
    id objc_initWeak(id *location, id newObj) {
        // 首先判断指针指向的类对象是否有效, 若无效直接释放指针并返回;
        if (!newObj) {
            *location = nil;
            return nil;
        }
        // 注册为一个指向value的_weak对象
        // 更新指针的指向, 创建弱引用表。
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    storeWeak()
    // 更新指针指向, 创建对应弱引用表
    static id  storeWeak(id *location, objc_object *newObj) {
        ASSERT(haveOld  ||  haveNew);
        if (!haveNew) ASSERT(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
        // Retry if the old value changes underneath us.
     retry:
        // 获取旧引用散列
        if (haveOld) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        // 获取新引用散列
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
        // 加锁操作, 解决选择竞争; 
        // 为了解决死锁问题, 可能会开启二次尝试
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        if (haveOld  &&  *location != oldObj) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            goto retry;
        }
    
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                class_initialize(cls, (id)newObj);
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
        // 解除旧对象与弱引用表关联绑定
        // 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;
        }
        else {
            // No new value. The storage is not changed.
        }
        
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj;
    }
    
    weak_unregister_no_lock()
    /** 
     * Unregister an already-registered weak reference.
     * This is used when referrer's storage is about to go away, but referent
     * isn't dead yet. (Otherwise, zeroing referrer later would be a
     * bad memory access.)
     * Does nothing if referent/referrer is not a currently active weak reference.
     * Does not zero referrer.
     * 
     * FIXME currently requires old referent value to be passed in (lame)
     * FIXME unregistration should be automatic if referrer is collected
     * 
     * @param weak_table The global weak table.
     * @param referent The object.
     * @param referrer The weak reference.
     */
    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;
    
        if (!referent) return;
    
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            remove_referrer(entry, referrer);
            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;
                    }
                }
            }
    
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    
        // Do not set *referrer = nil. objc_storeWeak() requires that the 
        // value not change.
    }
    
    weak_register_no_lock()
    /** 
     * Registers a new (object, weak pointer) pair. Creates a new weak
     * object entry if it does not exist.
     * 
     * @param weak_table The global weak table.
     * @param referent The object pointed to by the weak reference.
     * @param referrer The weak pointer address.
     */
    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;
    
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // ensure that the referenced object is viable
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            BOOL (*allowsWeakReference)(objc_object *, SEL) = 
                (BOOL(*)(objc_object *, SEL))
                object_getMethodImplementation((id)referent, 
                                               @selector(allowsWeakReference));
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
                ! (*allowsWeakReference)(referent, @selector(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_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            append_referrer(entry, referrer);
        } 
        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;
    }
    

    在最后会调用 clearDeallocating 函数。而 clearDeallocating 函数首先根据对象的地址获取weak指针地址的数组, 然后紧接着遍历这个数组, 将其中的数组开始置为nil, 把这个 entry从weak 表中删除, 最后一步清理对象的记录。

    释放过程

    weak 指向的对象被释放时, 编译器如何让 weak 指针置为nil的呢?

    将弱引用存到一个弱引用表(哈希表)中, 对象销毁时, 就从表中取出当前对象的弱引用并清除

    1. 调用 objc_release

    2. 因为对象的引用计数为0, 所以执行 dealloc

    3. 在 dealloc 中, 调用了 _objc_rootDealloc 函数

    4. _objc_rootDealloc 中, 调用了object_dispose 函数

    5. 调用 objc_destructInstance

    6. 最后调用objc_clear_deallocating,详细过程如下:

      a. 从weak表中获取废弃对象的地址为键值的记录

      b. 将包含在记录中的所有附有 weak修饰符变量的地址, 赋值为 nil

      c. 将weak表中该记录删除

      d. 从引用计数表中删除废弃对象的地址为键值的记录

    释放过程 - 实验代码

    @implementation Person
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __strong Person *person1;
        __weak Person *person2;
        __unsafe_unretained Person *person3;
        
        NSLog(@"111");
        
        {
            Person *person = [[Person alloc] init];
            person2 = person;
            person3 = person;
        }
        
        NSLog(@"222 - %@", person2); // 打印 null , 说明弱指针 person2 在其指向的对象销毁时, 会自动置空
        NSLog(@"333 - %@", person3); // 会报坏EXC_BAD_ACCESS内存访问, 在这里person3依然有值, 只不过是个野指针, 指向的person对象已被销毁
    }
    @end
    

    释放过程 - 源码

    注: 关于 SideTableweak_table_tweak_entry_t 参考 《isa指针

    dealloc
    // Replaced by CF (throws an NSException)
    + (void)dealloc {
    }
    
    // Replaced by NSZombies
    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    
    rootDealloc()
    void _objc_rootDealloc(id obj) {
        ASSERT(obj);
        obj->rootDealloc();
    }
    
    inline void objc_object::rootDealloc() {
        if (isTaggedPointer()) return;  // fixme necessary?
        
        // 如果该对象是优化的isa(arm64)且 没有 弱引用 / 关联对象 / C++析构器 / 引用计数表, 直接释放
        if (fastpath(isa.nonpointer  &&  
                     !isa.weakly_referenced  &&  
                     !isa.has_assoc  &&  
                     !isa.has_cxx_dtor  &&  
                     !isa.has_sidetable_rc)) {
            assert(!sidetable_present());
            free(this);
        } 
        // 普通isa不管有没有用过上述资源, 都走这里
        // 需要释放该对象相关的其他资源
        else {
            object_dispose((id)this);
        }
    }
    
    object_dispose()
    // 有弱引用必然会走这里
    id object_dispose(id obj) {
        if (!obj) return nil;
        // 释放 obj 相关资源
        objc_destructInstance(obj); 
        // 释放自身
        free(obj);
    
        return nil;
    }
    
    objc_destructInstance()
    // 释放 obj 相关资源
    void *objc_destructInstance(id obj)  {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            // This order is important.
            if (cxx) object_cxxDestruct(obj); // 清除c++析构函数相关成员变量
            if (assoc) _object_remove_assocations(obj); // 移除关联对象
            
            // 根据当前对象的地址值, 找到对应的
            obj->clearDeallocating(); // 将指向当前对象的 weak 弱指针置为nil ★
        }
        return obj;
    }
    
    clearDeallocating()
    // 
    inline void objc_object::clearDeallocating() {
        // 普通isa指针, 直接指向 class / metaclass 
        if (slowpath(!isa.nonpointer)) { 
            // Slow path for raw pointer isa.
            sidetable_clearDeallocating();
        }
        // arm64优化过的isa
        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());
    }
    
    clearDeallocating_slow()
    // arm64优化过的isa 走这里
    NEVER_INLINE void objc_object::clearDeallocating_slow() {
        ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
        // 
        SideTable& table = SideTables()[this];
        table.lock();
        // 有弱引用 ★
        if (isa.weakly_referenced) {
            // SideTable 有个弱引用表 weak_table, 是个哈希表 
            // (SideTable 数据结构可到《isa》中查看)
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // 引用计数长度超出19bit, 存放在 SideTable
        if (isa.has_sidetable_rc) {
            // 清除引用计数
            table.refcnts.erase(this);
        }
        table.unlock();
    }
    
    weak_clear_no_lock()
    /** 
     * 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) {
        // 要销毁的对象
        objc_object *referent = (objc_object *)referent_id;
        // 根据 要销毁的对象地址值 获取 弱引用入口
        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;
        
        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) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                if (*referrer == referent) {
                    *referrer = nil;
                }
                else if (*referrer) {
                    _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();
                }
            }
        }
        // 移除弱引用 ★
        weak_entry_remove(weak_table, entry);
    }
    
    weak_entry_for_referent()
    /** 根据 要销毁的对象地址值 获取 弱引用入口
     * Return the weak reference table entry for the given referent. 
     * If there is no entry for referent, return NULL. 
     * Performs a lookup.
     *
     * @param weak_table 
     * @param referent The object. Must not be nil.
     * 
     * @return The table of weak referrers to this object. 
     */
    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;
        
        // 用 以要销毁的对象地址值 referent, 按位与 weak_table的掩码, 得出哈希表索引
        // 可以看出, weak_table 内部有一个哈希表 weak_entries
        size_t begin = hash_pointer(referent) & weak_table->mask;
        size_t index = begin;
        size_t hash_displacement = 0;
        while (weak_table->weak_entries[index].referent != referent) {
            index = (index+1) & weak_table->mask;
            if (index == begin) bad_weak_table(weak_table->weak_entries);
            hash_displacement++;
            if (hash_displacement > weak_table->max_hash_displacement) {
                return nil;
            }
        }
        // 从哈希表中取出index对应的弱引用
        return &weak_table->weak_entries[index];
    }
    
    weak_entry_remove()
    /**
     * Remove entry from the zone's table of weak references.
     */
    static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) {
        // remove entry
        // 释放weak变量地址, entry->referrers ★★
        if (entry->out_of_line()) free(entry->referrers);
        // 释放entry
        bzero(entry, sizeof(*entry));
        // 弱引用数量-1
        weak_table->num_entries--;
    
        weak_compact_maybe(weak_table);
    }
    
    // Shrink(收缩) the table if it is mostly empty.  // 缩容
    static void weak_compact_maybe(weak_table_t *weak_table) {
        size_t old_size = TABLE_SIZE(weak_table);
    
        // Shrink if larger than 1024 buckets and at most 1/16 full.
        if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
            weak_resize(weak_table, old_size / 8);
            // leaves new table no more than 1/2 full
        }
    }
    
    sidetable_clearDeallocating
    // arm64之前的 普通isa 使用该方法释放
    void objc_object::sidetable_clearDeallocating() {
        SideTable& table = SideTables()[this];
    
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                weak_clear_no_lock(&table.weak_table, (id)this);
            }
            table.refcnts.erase(it);
        }
        table.unlock();
    }
    

    相关文章

      网友评论

          本文标题:iOS - weak 底层原理

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