zoukankan      html  css  js  c++  java
  • 逐步探究ObjC的Weak技术底层

    前言

    之前的文章有说过 Atomic 原子操作的原理,其作为一个特殊的修饰前缀,影响了存取操作。

    在属性修饰定义中,还有另一类修饰前缀,他们分别是 strong weak assign copy,这些又有什么区别呢?

    平时喜欢探究的同学,可能也见过 unsafe_unretained,这个又是什么呢?

    让我们从属性修饰入手,逐步揭开弱引用的面纱。


    原理

    属性自动生成的实现方法是怎么样的?

    首先我们先创建一个示例代码文件作为样本。

    #import <Foundation/Foundation.h>
    
    @interface PropertyObject : NSObject
    
    @property (nonatomic, strong) NSObject *pStrongObj; //强引用
    @property (nonatomic, copy)   NSObject *pCopyObj;   //拷贝
    @property (nonatomic, weak)   NSObject *pWeakObj;   //弱引用
    @property (nonatomic, assign) NSObject *pAssignObj; //申明
    @property (nonatomic, unsafe_unretained) NSObject *pUnretainedObj; //非持有
    
    @end
    
    @implementation PropertyObject
    @end
    

    然后通过 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m 命令将其解释成 c++ 代码。(注意这里要指定版本,不然weak属性不能翻译)

    展开的代码比较多,我这里截取关键部分探讨。

    struct PropertyObject_IMPL {
    	NSObject *__strong _pStrongObj;
    	NSObject *__strong _pCopyObj;
    	NSObject *__weak _pWeakObj;
    	NSObject *__unsafe_unretained _pAssignObj;
    	NSObject *__unsafe_unretained _pUnretainedObj;
    };
    
    {"pStrongObj","T@"NSObject",&,N,V_pStrongObj"},
    {"pCopyObj","T@"NSObject",C,N,V_pCopyObj"},
    {"pWeakObj","T@"NSObject",W,N,V_pWeakObj"},
    {"pAssignObj","T@"NSObject",N,V_pAssignObj"},
    {"pUnretainedObj","T@"NSObject",N,V_pUnretainedObj"}
    

    从变量结构体的描述和特性可以看出,strongcopy实际都是__strong修饰,但特性不同,assignunsafe_unretained 则完全一致,都是__unsafe_unretainedweak则单独使用__weak修饰。

    下面我们来看一下方法具体实现。

    // @implementation PropertyObject
    
    //根据偏移取值和赋值
    static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); }
    static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; }
    
    static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); }
    extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
    
    //只有Copy不同,setter的实现是objc_setProperty
    static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0, 1); }
    
    static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); }
    static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; }
    
    static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); }
    static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; }
    
    static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); }
    static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; }
    // @end
    

    在代码中,只有copy修饰属性的setter方法使用了objc_setProperty,其他几种都是根据 self + 偏移量 的方式计算出内存地址直接进行存取。

    那问题来了,如果真的是那么简单的话,arc 是怎么实现根据不同修饰从而进行内存管理的呢?

    原来通过 clang -rewrite-objc 的代码只是翻译成 c++ 语言,在之后的编译过程中会进一步处理。

    接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll 命令生成中间码。

    (中间码显示比较杂乱,我根据自己理解整理成简洁版)

    //代码整理后
    id [PropertyObject pStrongObj] {
      return *location; 
    }
    void [PropertyObject setPStrongObj:](self, _cmd, obj) {
      @llvm.objc.storeStrong(*location, obj)
    }
    
    id [PropertyObject pCopyObj] {
      return @objc_getProperty(self, _cmd, offset, atomic)
    }
    void [PropertyObject setPCopyObj:](self, _cmd, obj) {
      @objc_setProperty_nonatomic_copy(self, _cmd, obj, offset)
    }
    
    id [PropertyObject pWeakObj] {
      id obj = @llvm.objc.loadWeakRetained(*location)
      return @llvm.objc.autoreleaseReturnValue(obj)
    }
    void [PropertyObject setPWeakObj:](self, _cmd, obj) {
      @llvm.objc.storeWeak(*location, obj)
    }
    
    id [PropertyObject pAssignObj] {
      return *location
    }
    void [PropertyObject setPAssignObj:](self, _cmd, obj) {
      *location = obj
    }
    
    id [PropertyObject pUnretainedObj] {
      return *location
    }
    void [PropertyObject setPUnretainedObj:](self, _cmd, obj) {
      *location = obj
    }
    

    可以看出分别针对strongweak 都做了处理,而assignunsafe_unretained则不做内存管理直接返回,这也说明这两者的处理方式是一样的,区别在于 assign 针对。

    strong copy weak assign unsafe_unretained
    Ownership __strong __strong __weak __unsafe_unretained __unsafe_unretained
    Getter *location objc_getProperty loadWeakRetained *location *location
    Setter storeStrong objc_setProperty storeWeak *location *location
    对象 NSObject NSObject NSObject NSObject Scalar

    Weak对象怎么实现存取的?

    本文篇幅有限,暂不介绍 storeStrongobjc_setProperty_nonatomic_copy,主要介绍 weak 相关操作。

    打开 objc4-750 开源代码,翻到 NSObject.mm,我们来一探究竟。

    // 初始化弱引用
    id objc_initWeak(id *location, id newObj) {
        // 不存在则不保存
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    // 销毁弱引用
    void objc_destroyWeak(id *location) {
        (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
            (location, nil);
    }
    // 交换原有的值
    id objc_storeWeak(id *location, id newObj) {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    

    可以看到 runtime 中调用的都是一个方法,区别在于使用了不同的模版,那么我们来看下对一个地址的存取方法。

    // 获取操作的具体实现
    id objc_loadWeakRetained(id *location) {
        id obj;
        id result;
        Class cls;
    
        SideTable *table;
        
     retry:
        // 保证地址有数据且不是伪指针
        obj = *location;
        if (!obj) return nil;
        if (obj->isTaggedPointer()) return obj;
        // 根据地址取出对应的表
        table = &SideTables()[obj];
        // 加锁
        table->lock();
        // 如果数据被其他线程改变,则重试
        if (*location != obj) {
            table->unlock();
            goto retry;
        }
        result = obj;
    
        cls = obj->ISA();
        if (! cls->hasCustomRR()) {
            // 如果使用的是系统默认的内存管理,则保证了已经初始化
            // 所以可以直接rootTryRetain
            assert(cls->isInitialized());
            if (! obj->rootTryRetain()) {
                result = nil;
            }
        } else {
            // 如果不是默认的,则需要确保在初始化线程上执行自定义retain操作
            if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
                BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                    class_getMethodImplementation(cls, SEL_retainWeakReference);
                if ((IMP)tryRetain == _objc_msgForward) {
                    result = nil;
                } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                    result = nil;
                }
            } else {
                table->unlock();
                _class_initialize(cls);
                goto retry;
            }
        }
        //完成后解锁
        table->unlock();
        return result;
    }
    // 保存操作的具体实现
    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;
    
        // 由于有锁的机制,如果在期间值被改变了,则重试,直到成功
     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;
        }
    
        // 检查新对象类有没有初始化完,没有则重试
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
    
                // 如果正在初始化,则让下一次绕过这个判断继续运行
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // 清除之前保存的弱引用数据
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // 保存新的弱引用数据
        if (haveNew) {
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // 保存成功就记录到对象指针中,这样可以在释放时检查
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
    
            // 保存到对应位置
            *location = (id)newObj;
        }
        
        // 操作成功后解锁
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        // 返回最终数据
        return (id)newObj;
    }
    

    除去保护方法,其实 objc_loadWeakRetained 方法就是检查后返回 *location,也就是变量指向的实际地址。

    storeWeak 方法则是根据模版,对旧对象执行 weak_unregister_no_lock,对新对象执行 weak_register_no_lock

    //注销引用
    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.
        // 上面为苹果注释,看这意思应该是objc_storeWeak还需要使用引用地址做后续处理。
    }
    //注册引用
    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;   //引用人
        // taggedPointer没有引用计数,不需要处理
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // 保证被引用人不在释放中,不然闪退
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        } else {
            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;
            }
        }
    
        //获取被引用人的引用数组,没有则创建
        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;
    }
    //释放过程清空引用
    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) {
            //这里应该肯定有entry,因为调用前判断了对象的WeaklyReferenced
            //如果确实没有,苹果认为可能是CF/objc原因
            return;
        }
    
        //清空引用数组
        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.
    ", 
                                 referrer, (void*)*referrer, (void*)referent);
                    objc_weak_error();
                }
            }
        }
        //去除节点
        weak_entry_remove(weak_table, entry);
    }
    

    可以发现,对申明是 __weak 的变量进行存取操作,其实都是通过被操作的对象地址查找到相应的表,然后增删表的引用数组内容。

    SideTable表怎么设计的?

    关键就在于怎么申明创建表,以及这个表是怎么设计及使用的。

    // SideTables 类型申明
    // 这里之所以先使用数据的方式申明是因为考虑到加载顺序的问题
    alignas(StripedMap<SideTable>) static uint8_t 
        SideTableBuf[sizeof(StripedMap<SideTable>)];
    // 加载image时执行初始化
    static void SideTableInit() {
        new (SideTableBuf) StripedMap<SideTable>();
    }
    // 数组还原成StripedMap类型
    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    
    // StripedMap 的结构
    enum { CacheLineSize = 64 };
    template<typename T>
    class StripedMap {
    #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
        enum { StripeCount = 8 };
    #else
        enum { StripeCount = 64 };
    #endif
        // 64位对齐
        struct PaddedT {
            T value alignas(CacheLineSize);
        };
        // 手机系统数组个数为8
        PaddedT array[StripeCount];
        // 把指针地址匹配到数组的序号
        static unsigned int indexForPointer(const void *p) {
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
        }
    }
    

    在加载镜像的过程中,通过 SideTableInit 方法创建全局表数组,可以看到手机系统是8个数组。

    源码中使用 &SideTables()[obj] 的方式,其实就是把 obj 的指针地址转成序号获取某一个 table,通过这种方式分散冗余。

    接着我们看 SideTable 类的内部结构。

    // 哈希散列表,使用补码的形式把指针地址作为Key,保存引用计数
    typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
    
    // Template parameters.
    enum HaveOld { DontHaveOld = false, DoHaveOld = true };
    enum HaveNew { DontHaveNew = false, DoHaveNew = true };
    
    struct SideTable {
        spinlock_t slock;       // 自旋锁
        RefcountMap refcnts;    // 引用记数表
        weak_table_t weak_table;// 弱引用表
    
        template<HaveOld, HaveNew>
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template<HaveOld, HaveNew>
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    
    struct weak_table_t {
        weak_entry_t *weak_entries;     //弱引用数组
        size_t    num_entries;          //数组个数
        uintptr_t mask;                 //计算辅助量,数值为数组总数-1
        uintptr_t max_hash_displacement;//哈希最大偏移量
    };
    
    #if __LP64__
    #define PTR_MINUS_2 62
    #else
    #define PTR_MINUS_2 30
    #endif
    
    typedef DisguisedPtr<objc_object *> weak_referrer_t;
    struct weak_entry_t {
        // 被引用者
        DisguisedPtr<objc_object> referent;
        union {
            // 引用者数据结构
            struct {
                // 当数量超过4个时,结构转为指针,每次容量满的时候就扩容两倍
                // 需要与数组作区分,所以有out_of_line_ness标记
                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 {
                // 四个数组
                weak_referrer_t  inline_referrers[4];
            };
        };
    };
    

    SideTable 存储的不仅有对象引用计数表,还有我们关注的弱引用表,其结构顺序如下:

    SideTable->weak_table_t->weak_entry_t->weak_referrer_t

    为了方便理解,我模拟一下找弱引用对象的步骤:

    1. sideTable = &SideTables()[referent] 把对象内存地址按照8取余后找到表

    2. weakTable = &sideTable->weak_table 取出弱引用表

    3. entry = weak_entry_for_referent(weakTable, referent) 根据被引用人地址,遍历弱引用表找出入口

    4. referrer = entry->referrers[index] 入口有特殊的数组,其中保存了所有弱引用者的对象地址

    仔细一点的同学应该发现了 weak_entry_t 中有一个联合体,这又是怎么操作实现的呢?

    // 添加新引用者
    static void append_referrer
        (weak_entry_t *entry, objc_object **new_referrer) {
        // 没有超过4个,就用内敛数组
        if (! entry->out_of_line()) {
            // 遍历数组,如果有空位置,则插入后返回
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i] == nil) {
                    entry->inline_referrers[i] = new_referrer;
                    return;
                }
            }
    
            // 如果超过4个了,就从数组结构转成指针结构
            weak_referrer_t *new_referrers = (weak_referrer_t *)
                calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
            // 拷贝原数据到指针指向的内容
            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;                //最大hash移位次数,用于优化循环
            // 由于只有4个,会在下个判断后执行grow_refs_and_insert初始化并插入新对象
        }
        // 断言必然是指针结构
        assert(entry->out_of_line());
        // 如果指针数量超过3/4,就容量翻倍后再插入
        if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
            return grow_refs_and_insert(entry, new_referrer);
        }
    
        size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
        size_t index = begin;
        size_t hash_displacement = 0;
        //找一个空位置,不够就从头找
        while (entry->referrers[index] != nil) {
            hash_displacement++;
            index = (index+1) & entry->mask; //下标+1后取余
            if (index == begin) bad_weak_table(entry);
        }
        if (hash_displacement > entry->max_hash_displacement) {
            entry->max_hash_displacement = hash_displacement;
        }
        //保存
        weak_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        entry->num_refs++;
    }
    

    总结

    至此对于弱引用的整体结构和逻辑都清楚了,对象根据修饰符进行内存管理,如果是弱引用,则找到其引用地址的引用表操作。

    反过来讲,强对象被引用时在全局引用表中注册一个节点,保存所有引用者的地址,当释放时设置所有地址为空。


    问答

    被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

    对象被释放时执行 obj->rootDealloc(),如果有弱引用标记,则会执行 objc_destructInstance 方法后释放。

    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);           //调用析构函数
            if (assoc) _object_remove_assocations(obj); //移除关联对象关系
            obj->clearDeallocating();                   //处理isa
        }
    
        return obj;
    }
    inline void objc_object::clearDeallocating() {
        if (slowpath(!isa.nonpointer)) {
            // Slow path for raw pointer isa.
            sidetable_clearDeallocating();
        } 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());
    }
    void objc_object::sidetable_clearDeallocating() {
        SideTable& table = SideTables()[this];
    
        // 删除强引用和弱引用
        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();
    }
    

    可以看到在 sidetable_clearDeallocating 方法中,最后执行了 weak_clear_no_lock 清空了所有引用关系。

    SideTable 表结构如下图:


    总结

    weak原理是绕不开的经典课题,通过阅读开源代码对苹果如何实现有了大致的了解,受益匪浅。

    阅读过程中还惊叹于苹果各种花式小技巧,由于文章篇幅有限没来得及介绍,感兴趣可以了解一下,比如 DisguisedPtr

    资料分享

    Objective-C Class Ivar Layout 探索

    理解 ARC 实现原理

    weak 弱引用的实现方式

  • 相关阅读:
    PLSQL Developer oracle导入导出表及数据
    Java之设计模式
    mySQL:两表更新(用一个表更新另一个表)的SQL语句
    Java中对图片文件的类型的获取
    JAVA判断文件的内容类型
    正确理解Mysql的列索引和多列索引
    eclipse快捷键
    C# 利用Jmail接收邮件
    github常见操作和常见错误!错误提示:fatal: remote origin already exists.
    css3特殊图形(气泡)
  • 原文地址:https://www.cnblogs.com/vanch/p/12012242.html
Copyright © 2011-2022 走看看