iOS进阶专项分析(十)、iOS内存的布局管理及优化

对于学习来说,最大的成本不是金钱,而是时间。低质量低效率的学习不仅是对金钱的浪费,更是对时间、生命的浪费。

先来看一系列大厂必问的iOS的高阶面试题:

  1. 什么是ARC&MRC?底层是如何实现的?
  2. 对象调用alloc和init方法之后,引用计数是0还是1?为什么?
  3. weak实现原理以及weak指针是怎样移除的?何时移除?

然后带着这三个问题开始本篇干货:

  1. iOS内存布局及优化技巧
  2. 内存管理机制ARC&MRC
  3. 内存管理之引用计数retain/release底层实现
  4. 内存管理之dealloc底层实现
  5. 内存管理之weak底层实现

一、内存布局及优化


三图看懂内存布局及优化

1.内存布局及存储类型

1.1内存布局.png

1.2内存布局.png

2.内存布局方向的优化技巧

1.3内存优化.png

二、内存管理机制ARC&MRC


1、内存管理机制:

引用计数机制,创建时引用计数为1,被持有会对引用计数+1,对象不再使用或者手动release会对引用计数-1,当引用计数为0的时候由系统进行销毁。(注意释放的条件,不要和release混淆,release只是引用计数-1,而不是释放)

引用计数管理机制:谁创建,谁释放;谁引用,谁管理。

2、MRCARC的异同:

MRC(全称Manual Reference Counting 手动引用计数)和ARC(全称Automatic Reference Counting,自动引用计数,iOS5推出)底层都是引用计数机制。

ARC是编译属性,是编译器和runtime结合(对象的持有和释放)实现的结果。

三、内存管理之引用计数retain/release底层实现


打开Objc源码,在objc-object.h中找到这俩的实现

retain的实现:

// Equivalent to calling [this retain], with shortcuts if there is no override
inline id 
objc_object::retain()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return sidetable_retain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

release的实现部分:

// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        sidetable_release();
        return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}

明显看出底层分别调用了sidetable_retain()sidetable_release()

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    //根据对象的地址,从一大堆散列表中,获取当前对象引用计数的散列表
    SideTable& table = SideTables()[this];
    
    //自旋锁,加锁
    table.lock();
    //获取引用计数
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        //引用计数增加
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    //自旋锁,解锁
    table.unlock();

    return (id)this;
}
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    //根据对象的地址,从大散列表中,获取当前对象引用计数的散列表
    SideTable& table = SideTables()[this];
    //定义局部变量,是否需要dealloc
    bool do_dealloc = false;
    
    //自旋锁加锁
    table.lock();
    //获取当前对象的引用计数
    RefcountMap::iterator it = table.refcnts.find(this);
    
    //判断是否需要dealloc
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        //如果不需要dealloc,则引用计数减1
        it->second -= SIDE_TABLE_RC_ONE;
    }
    //自旋锁解锁
    table.unlock();
    
    //如果需要dealloc则发送消息,调用SEL_dealloc
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

存储SideTable的全局哈希映射表StripedMap

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

散列表SideTable的结构:

struct SideTable {
    spinlock_t slock;//内核自旋锁spinlock_t
    RefcountMap refcnts;//引用计数字典map
    weak_table_t weak_table;//weak表

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
    
    //自旋锁提供的加锁解锁方法
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    //提供给weak操作的地址顺序锁
    // Address-ordered lock discipline for a pair of side tables.
    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

思考:为什么Runtime会映射维护多张散列表SideTable?而不是维护一张散列表?

回答:由于散列表中的操作会加锁(比如引用计数增加、减少都会加自旋锁),如果只有一个表的话,假如我有两个类Person,Student,那么我对Person对象进行操作的时候,再想对Student对象进行操作,就只能等待Person的操作完成解锁后才能操作,这样效率会大大下降。并且多次对同一张表进行操作,提高了使用频率,可能会造成稍微有修改就需要动整张表的不够合理的操作。

而使用了多张表,就避免了以上的几种问题,哪个类需要处理就去改对应的表,而且避开了锁的问题,效率上也提升了很高,用空间换时间。

分析源码总结retain和release的实现逻辑如下:

Runtime维护了一个全局哈希映射表StripedMap,根据对象可以在全局映射表中可以获取该对象对应的散列表SideTable,该SideTable拥有三个成员变量,一个自旋锁spinlock_t,一个引用计数表RefcountMap,以及一个weak表weak_table_t。引用计数表RefcountMap以对象的地址作为key,以引用计数作为value。

retainobjc_object在底层调用了sidetable_retain(),查找引用计数表,做了引用计数refcntStorage+=SIDE_TABLE_RC_ONE;的操作;

releaseobjc_object在底层调用了sidetable_release(),查找引用计数表,做了引用计数refcntStorage-=SIDE_TABLE_RC_ONE;的操作;如果引用计数小于阀值SIDE_TABLE_DEALLOCATING,就调用SEL_dealloc

图文总结如下:

retain&release底层实现.png

由于SideTable结构体中包含一个自旋锁,笔者此处拓展一下自旋锁相关知识:互斥锁的作用,以及和自旋锁的区别

自旋锁是互斥锁的一种实现,互斥锁的作用就是确保同一时间只有一个线程访问数据,对资源加锁后,会等待资源解锁,在这期间会阻塞线程,直到解锁;而自旋锁则是忙等,在加锁后,会不断地去询问判断是否解锁。两者的区别主要是:在等待期间互斥锁会放弃CPU,而自旋锁会不断的循环测试锁的状态,会一直占用CPU。

总结之后,来看一个retainCount经典的面试题:

对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?

带着问题,我们源码中全局搜索retainCount {找到实现,并进入rootRetainCount

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

rootRetainCount源码,注意代码逻辑

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    //散列表SideTable提供的自旋锁,加锁
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    
    //判断bits.nonpointer
    if (bits.nonpointer) {
        //这里先直接+1
        uintptr_t rc = 1 + bits.extra_rc;
        //判断有没有使用sideTable存储引用计数,如果有,就加上
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        //散列表自旋锁解锁
        sidetable_unlock();
        
        //没有就直接返回,
        return rc;
    }
    
    //散列表SideTable提供的自旋锁,解锁
    sidetable_unlock();
    
    //直接返回sidetable_retainCount
    return sidetable_retainCount();
}

下面是sidetable_retainCount的实现

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];
    //设置初始值1
    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

打上断点,对象创建走的是bits.nonpointer为1的逻辑,此时bits.extra_rc存储的引用计数为0,但是引用计数rc = 1 + bits.extra_rc,所以返回了1

从这个源码中我们又看出一件事,引用计数不一定存储在哈希表中,还有可能存储在类isa的
bits.extra_rc中,找到isa的结构isa_t以及isa_t的结构中找到宏定义ISA_BITFIELD笔者把宏定义整理了一下,注意其中的
has_sidetable_rcextra_rc

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
    uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
    uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
    uintptr_t shiftcls          : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
    uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
    uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    uintptr_t deallocating      : 1;  // 对象是否正在释放
    uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
    uintptr_t extra_rc          : 19;  // 引用计数能够用 19 个二进制位存储时,直接存储在这里
    };
#endif
};

经过分析,isa_t里面的extra_rc也是用来存储引用计数的,只不过大小有限,如果超过了大小,就会存储到散列表SideTable中。

总结刚才的答案如下:

问题:对象进行alloc和init之后的引用计数值为多少?到底是0还是1?为什么?

回答:对象alloc之后,在引用计数表中的引用计数其实为0,只是在获取retainCount的方法rootRetainCount的内部进行了+1的操作

总结对象生命周期引用计数的变化图如下:

retainCount流程.png

四、内存管理之dealloc底层实现


直接进入源码搜索dealloc {,找到底层函数调用流程如下:

  1. dealloc底层调用_objc_rootDealloc()
  2. _objc_rootDealloc()调用objc_object::rootDealloc()
  3. objc_object::rootDealloc()调用object_dispose()
  4. object_dispose()进行了free(obj)释放对象,同时调用objc_destructInstance()
  5. objc_destructInstance()函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()
  6. clearDeallocating()中进行引用计数refcnt的清除和weak指针的移除,并调用weak_clear_no_lock()(这个weak指针移除具体步骤在下面的weak指针清除的时候进行详细分析。)

下面贴出上述步骤的所有源码:

1、dealloc底层调用_objc_rootDealloc()

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

2、_objc_rootDealloc()调用objc_object::rootDealloc()

void
_objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}

3、objc_object::rootDealloc()调用object_dispose()

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    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((id)this);
    }
}

4、object_dispose()进行了对象的释放free(obj),同时调用objc_destructInstance()

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);  
    //释放obj  
    free(obj);

    return nil;
}

5、在objc_destructInstance()函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
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();
    }

    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];

    // 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();
}

objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

总结一下dealloc主要做了下面这些内容:

  1. 首先判断并调用对象C++的析构函数,释放对象占用的空间,顺序是:先子类->后父类 ----- object_cxxDestruct()
  2. 然后清除对象关联引用 ----- _object_remove_assocations()
  3. 然后从weak表中清空并移除对象对应的所有weak指针 -----weak_clear_no_lock()
  4. 然后移除引用计数 ----- table.refcnts.erase(it);
  5. 最后释放对象 free(obj)

对应流程图总结如下:

dealloc流程图.png

五、内存管理之weak底层实现


分析完retain/release以及dealloc的底层实现,我们来继续分析weak的底层实现及weak表的插入和移除操作:

当我们开发时用__weak修饰变量,其实Runtime会在底层调用objc_initWeak函数,objc_initWeak()底层又调用了storeWeak()storeWeak()的作用就是向表中注册弱引用指针,或者更新表,下面是storeWeak()的实现部分,笔者把注释写在里面:

/** 
 * 注意注释部分!!!注释已经说的很直白了
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

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(_class_getNonMetaClass(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;
}

然后我们来看一下SideTable中weak表weak_table_t的源码:

/**
 * 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;
    //存储空间
    size_t    num_entries;
    //引用计数辅助量
    uintptr_t mask;
    //最大偏移值
    uintptr_t max_hash_displacement;
};

从之前引用计数的分析以及weak表的源码中分析得出:Runtime维护了一个全局哈希映射表StripedMap,不同对象映射着对应的散列表SideTable,散列表中包含引用计数map以及weak弱引用表weak_table_tweak_table_t中保存了所有指定对象的weak指针,用对象的地址作为key,weak_entry_t结构体对象作为value;weak_entry_t负责维护和存储指向一个对象的所有弱引用的散列表。

weak_entry_t源码:

/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
 * is instead a small inline array.
 */

typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t {
    DisguisedPtr<objc_object> 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表的存储结构思维导图总结如下:

weak底层存储结构.png

针对weak_table_t还提供了三个主要的方法:向表中添加元素的方法weak_register_no_lock、从表中移除元素的weak_unregister_no_lock、以及清空weak指针weak_clear_no_lock

向添加weak_table_t中添加方法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;
    //weak指针地址
    objc_object **referrer = (objc_object **)referrer_id;

    ......
    
    // 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实体
        //根据对象及弱指针生成一个weak_entry_t结构体对象,并插入表中
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        //插入到表中
        weak_entry_insert(weak_table, &new_entry);
    }

    return referent_id;
}


移除方法weak_unregister_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))) {
        //移除表中的weak指针
        remove_referrer(entry, referrer);
        
        ......

        if (empty) {
            //移除weak_table表中对应的的entry
            weak_entry_remove(weak_table, entry);
        }
    }
}

清空对象的weak指针,调用时机就是对象dealloc的时候:

/** 
 * 调用时机:
 * 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
    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;
    }
    //获取weak_entry_t中的weak_referent_t,遍历weak_referrer_t,将其中的weak指针置为nil
    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);
}

总结一下:

1、weak底层实现的流程

  1. Runtime全局维护了一个全局映射表StripedMap,根据对象的地址能够获取对应的散列表SideTable(注意!!!也有可能是多个对象共用一个散列表),散列表SideTable之中包含有weak表weak_table_tweak_table_t中根据对象的地址能够查到该对象对应的weak_entry_t实体,weak_entry_t用来管理对象的所有的weak指针,weak指针存储在weak_referrer_t中。

  2. 当我们在用__weak修饰对象的时候,运行时Runtime会在底层调用objc_initWeak()方法

  3. objc_initWeak()方法会调用storeWeak()

  4. storeWeak()这个函数会先判断对象是否初始化,如果未初始化,则进行对象初始化,然后创建对应的SideTable;如果对象已经有SideTable,那么判断weak指针是否需要更新,更新操作就是删除对应location位置的weak_entry_t对象,创建新的weak_entry_t,然后插入到weak表weak_table_t中。

2、weak指针移除原理

1、移除时机:调用对象的dealloc方法时,中间会调用clearDeallocating,其中会调用weak_clear_no_lock对weak指针进行移除。

2、移除原理:weak_clear_no_lock底层会获取weak表weak_table_t中的实体weak_entry_t,然后拿到其中的weak_referrer_t,拿到weak_referrer_t之后,遍历并将其中的所有weak指针置为nil,最后把这个weak_entry_tweak_table_t中移除。

3、weak指针本质:从源码中可以看出weak指针的类型为是objc_object**,是对象的二维指针,就是指向对象地址的指针。

接下来看一个问题:

如果在dealloc中使用__weak会有什么样的结果?

答案:会crash!

回到源码,在storeWeak函数源码的上边找到下面这部分代码

// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
//   that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
//   If CrashIfDeallocating is false, nil is stored instead.

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
          

以及注册weak指针的方法

/** 
 * 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)
{
    
    ......
    
    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;
        }
    }

    ......

    return referent_id;
}

结合第一部分注释部分以及第二部分的注册函数weak_register_no_lock进行分析:如果一个对象正在进行dealloc的时候,进行weak指针的更新操作,就会直接crash!并报错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.

注册时这么判断作用就是:在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这样就是为了防止野指针的出现。

-END-

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335