美文网首页
内存管理(二)

内存管理(二)

作者: DoBetter1 | 来源:发表于2020-02-19 14:06 被阅读0次

ARC&MRC主要与allocretainrealeseretainCountautorealesedealloc这些方法的调用有关。

ARC:是LLVM和Runtime配合的结果,ARC中禁止使用手动调用reatain/release/retainCount/dealloc,ARC新加了weak,strong属性关键字。

这里穿插一个问题:

FXTank *p = [[FXTank alloc] init];
NSLog(@"%lu", (unsigned long)[p retainCount]);

这段代码输出多少?(首先需要知道引用计数存在isa.bits. extra_rc或者散列表)

打印如下:

为啥输出1,从源码来找答案:

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

进入rootRetainCount方法:

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

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc; 
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

总结:从这里可以看到,uintptr_t rc = 1 + bits.extra_rc;引用计数手动加1(其中bit.extra_rc打印为nil),如果bits.has_sidetable_rc哈希表没有引用计数,就直接返回1,所以前面的的答案是1,至于这里为啥为1,原因是如果为0,就直接析构了释放了。

下面继续测试:

FXTank *p = [[FXTank alloc] init];
[p retain]; //这里的引用计数+1
NSLog(@"%lu", (unsigned long)[p retainCount]);

打印如下:


分析:retain是加到extrac,因为本来retainCount就会1+extra_rc,这里extra_rc由加了1,所以输出结果为2。
查看retain源码:
// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}

进入rootRetain

// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super retain].
//
// tryRetain=true is the -_tryRetain path.
// handleOverflow=false is the frameless fast path.
// handleOverflow=true is the framed slow path including overflow to side table
// The code is structured this way to prevent duplication.

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

进入rootRetain:

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this; //TaggedPointer是不会走下面retain,release,不会使用到引用计数的加减操作

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry; //这个是用来判断是否溢出的
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) { //如果溢出了存不下了
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

分析:首先整体是一个do-while循环,这里while判断是引用计数是否储备完整,如果没有储备完整,就一直在do循环里面处理ARC的引用计数。newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ 这段代码就是用来引用计数+1的,RC_ONE(宏定义 )就是1,# define RC_ONE (1ULL<<56),为什么是左移动56位呢?其实在上篇博客里面提到过,在ISA_BITFIELD宏定义里面使用了联合体,其中uintptr_t extra_rc : 8的意思就是代表占用了64位中的后8位,所以这里的1要加到后8位中,左移56位代表的就是后8位中的1。

newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true; //标记需要存哈希表
sidetable_addExtraRC_nolock(RC_HALF);

这段代码的意思是,如果extra_rc存满了,就取出一半存到SideTable哈希表里面的RefcountMap refcnts;引用计数map里面。
注意:其中# define RC_HALF (1ULL<<7)为8位的一半,差一个最高位就是二进制差一半。

进入sidetable_addExtraRC_nolock方法

// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this]; //取出响应的散列表

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); //引用计数+1
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

分析:refcnts中取出散列表,addc方法引用计数+1。
注意这里的SIDE_TABLE_RC_SHIFT为2,因为这里是前两位做其他用途,所以第2位开始,和前面有所不同。(如果散列表也存满了,就会处理引用计数传存储,这里不再深入探讨了)

总结:回到最开始的引用计数打印,就很明确了,先取bits. extra_rc里面的引用计数,然后判断has_sidetable_rc里面是否有引用计数,最后相加返回最终的引用计数个数。

接下来我们在试下下面这段代码:

FXTank *p = [[FXTank alloc] init];
[p retain];
NSLog(@"%lu", (unsigned long)[p retainCount]);
[p release];
NSLog(@"%lu", (unsigned long)[p retainCount]);

下面来看下release方法源码:

// Replaced by ObjectAlloc
- (oneway void)release {
    ((id)self)->rootRelease();
}

进入rootRelease方法:

// Base release implementation, ignoring overrides.
// Does not call -dealloc.
// Returns true if the object should now be deallocated.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super release].
// 
// handleUnderflow=false is the frameless fast path.
// handleUnderflow=true is the framed slow path including side table borrow
// The code is structured this way to prevent duplication.

ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

进入rootRelease方法:

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); //发送析构消息
    }
    return true;
}

分析:前面引用计数-1和+1类似,这里也有array溢出,只不过是下溢出,就是减到0继续减,下溢出之后会调用underflow,判断SideTable是否有引用计数,如果有就从散列表里面borrowed借用RC_HALF-1(也就是RC_HALF一半),然后在调用retry递归继续引用计数-1,如果散列表里面也没有了,调用析构方法,给this对象发SEL_dealloc消息。

注意:这里有一个问题,在对象刚创建的时候引用计数为0,但是不会走析构函数,只有在调用release函数把引用计数减到0的时候才会调用析构函数,也就是说引用计数为0是调用析构函数必要条件,但不是充分条件,必须要有一个release触发的过程。

引用计数总结:isa是一个NONPOINTER_ISA,他是按位存储的。我们的引用计数存储是有两个位置的,第一个是在isa.bits.extra_rc,第二个是在散列表里面,retain是会进行extra_rc+1返回的,extra_rc只有8给位置,会发送carry上溢出,就会吧一半的内容放到散列表里面,如果散列表也满了,会调整容量结构。release会进行extra_rc-1,如果extra_rc为0,就会发生下溢出,下溢出会判断散列表,然后借用一半存到extra_rc里面,如果散列表里面也没有,就会调用这个对象的析构函数。

疑问:这里有一个问题就是为什么要把extra_rc中的一半放到散列表里面,而不是全部?原因是不能过于平凡的操作散列表,如果全部都放到散列表里面,每次release的时候都会访问散列表,虽然extra_rc操作简单了,但是散列表的操作增多了,这样对于性能不是很好(感觉一半的话有点像二分法的思想,玄学)。

接下来我们看下Dealloc,首先是应该在Dealloc函数里面做什么事情?(底层实现)

  1. free函数,释放相关对象。
  2. weak引用计数表需要做处理。
  3. 关联对象associated对象需要释放。

首先找到源码:

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

进入_objc_rootDealloc:

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

继续进入rootDealloc:

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

分析:首先判断是否是nonpointer(小类型特殊处理),weakly_referenced(弱引用),has_assoc(关联对象),has_cxx_dtor(c++),has_sidetable_rc(散列表引用计数),如果不是直接free,否则走一般对象释放流程object_dispose

继续进入object_dispose:

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);   //处理对象,为释放做准备
    free(obj); //处理完成后释放

    return nil;
}

分析:主要是objc_destructInstance为释放做准备,然后直接free释放对象。

继续进入objc_destructInstance:

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

分析:如果有c++的析构函数就先调用c++析构,如果有关联对象的就移除。

然后进入clearDeallocating方法:

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

继续进入sidetable_clearDeallocating:

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

分析:如果it不再table.refcnts.end,就是说it不是最后一个的话,就一个一个从头开始往后遍历移除,因为对象已经不存在了,所以然后就是table.refcnts.erase(it);引用计数移除。

进入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);
}

分析:首先拿到weaktable里面的entry,然后再取出entry里面的referrers,然后for循环遍历referrers,如果referrer存在,直接置为nil,回收内存。(这里解释了经常说的weak一旦被释放,就会职位nil,不存在所谓的循环引用,因为它是一个nil对象),因为referrers已经空了,所以entry存在的意义也就没有了,所以调用weak_entry_remove(方法内部还有移除容量等)移除entry

析构总结:(总流程图)
后面是补充的weak原理:

weak原理:

NSObject *obj = [[NSObject alloc] init];
id __weak obj2 = obj;

weak修饰变量在断点处可以看到调用objc_initWeak方法,如下:

下面我们在源码中查看objc_initWeak这个方法:

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

继续进入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>
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;
}

分析:这里的重点是,在添加新的weak对象的时候,先要判断是否存在旧值,如果存在旧要先移除旧的,然后调用weak_register_no_lock添加新的weak对象,下面进入weak_register_no_lock方法继续分析源码。

进入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, 
                                           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;
        }
    }

    // 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); //创建新entry
        weak_grow_maybe(weak_table); //空间是否要扩容
        weak_entry_insert(weak_table, &new_entry); //插入新entry
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

分析:这里有两个逻辑,第一个是如果有entry就调用append_referrer加入到weak_table里面,如果weak表里面没有entry就创建新weak_entry_t的,然后加入到weak_table里面。

继续进入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;

    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;
        }
    }
    
    return &weak_table->weak_entries[index];
}

分析:这里的结构是一张哈希表,while循环一直找到和referent的key值不一样的index然后从返回 &weak_table->weak_entries[index] (entry地址),返回的&entry就会调用下面的append_referrer方法添加到weak_table里面。

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        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;
    }

    assert(entry->out_of_line());

    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;
        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总结下:

结构流程:
sidetabless -> sidetable -> 弱引用表
weakTable -> entry - >数组 ->弱引用对象指针

存储流程:

  1. weak_entry *weak_entries 取出entry
  2. entry->inline_referres[i] = new_referrer
  3. new_referrers[i] = entry->inline_referrers[i]
  4. entry->referrers = new_referrers

注意:这里weak是没有操作引用计数的。

流程图示:

相关文章

  • OC - OC的内存管理机制

    导读 一、为什么要进行内存管理 二、内存管理机制 三、内存管理原则 四、MRC手动内存管理 五、ARC自动内存管理...

  • iOS/OS X 内存管理(二):借助工具解决内存问题

    iOS/OS X 内存管理(二):借助工具解决内存问题 iOS/OS X 内存管理(二):借助工具解决内存问题

  • iOS内存管理(二)alloc、retain、release、d

    iOS内存管理(一)、内存分区和引用计数iOS内存管理(二)alloc、retain、release、deallo...

  • iOS内存管理(一)、内存分区和引用计数

    iOS内存管理(一)、内存分区和引用计数iOS内存管理(二)alloc、retain、release、deallo...

  • 内存管理

    目录一、内存分区 1、RAM和ROM 2、内存的五大分区二、内存管理 1、OC内存管理是指什么?OC内存管理的本质...

  • 内存优化(app专项测试)

    1.优化内存的常见操作 (一)内存管理机制 (二)常用内存监控工具 (三)内存优化案例分析 2.内存管理机制 AR...

  • 内存管理(二)

    Q1:在UWA的帮助下,我们追踪到了一个Reserved GFX的内存占用,并且显示比较高。我们应当如何降低该内存...

  • 内存管理二

    Autoreleasepool Autoreleasepool的结构 我们的main函数中定义一个自动释放池 然后...

  • 内存管理(二)

    ARC&MRC主要与alloc,retain,realese,retainCount,autorealese,de...

  • 内存管理(二)

    上一篇我们简单的介绍了NSTimer、NSProxy、GCD定时器、自定义time、iOS程序的内存布局、Tagg...

网友评论

      本文标题:内存管理(二)

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