iOS内存管理及ARC相关实现学习

1. 对象与类

1.1 对象
struct objc_object {
    isa_t isa;
    ...
}

对象(Class或id)内部只有一个isa_t联合体指针。
isa_t联合体内部只有两种成员: Class和bits。其内存结构依照下面的匿名struct进行分配(使用“位域”方式)。

union isa_t {
    ...
    
    Class cls;
    uintptr_t bits;
    
    ...
    
    struct {
        ...
    };
}

以x86_64架构为例,bits的内存结构如下:

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        // 指针优化标识
        uintptr_t nonpointer        : 1;
        // 存在相关对象标识
        uintptr_t has_assoc         : 1;
        // 包含c++析构函数标识(.cxx_destruct)
        uintptr_t has_cxx_dtor      : 1;
        // nonpointer为1时,此数据即为相关信息
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        // 类的初始化标识
        uintptr_t magic             : 6;
        // 有弱引用标识
        uintptr_t weakly_referenced : 1;
        // 正在释放标识
        uintptr_t deallocating      : 1;
        // 存在外部SideTable中的引用计数标识(extra_rc > 255后,存储在外部)
        uintptr_t has_sidetable_rc  : 1;
        // 引用计数(< 256时使用),比真实计数少1
        uintptr_t extra_rc          : 8;
        // 引用计数的最低位为1
#       define RC_ONE   (1ULL<<56)
        // extra_rc引用计数最大值的一半 128
#       define RC_HALF  (1ULL<<7)
    };

故可以看出,在64位环境下,对象占用的内存是8字节

1.2 类

类Class是objc_class的结构体指针。其继承了objc_object。主要结构如下:

struct objc_class : objc_object {
    Class superClass;
    cache_t cache;
    class_data_bits_t bits;
    
    class_rw_t *data() {
        return bits.data();
    }
    
    ...
}

除了isa_t数据成员,还包含了父类指针,缓存成员及class_data_bits_t数据。
其中,在运行时,类中的相关信息(成员、方法列表、协议列表等)都保存在bits中,也就是class_rw_t指向的数据中。

在编译期,data方法获取到的是class_ro_t的数据,其内部包含的都是在编译期决议的东西。在运行期(runtime系统加载时),其他信息才会加载到类中(如category实现的相关对象、方法等),data返回的才是完整的class_rw_t指针信息。

2. 内存管理容器

2.1 SideTable

SideTable作为内存管理信息保存的容器,在系统中存储多个。系统通过实例对象的hash值与SideTable进行绑定。
一个实例对象对应一个SideTable,而一个SideTable可以供多个实例对象共用。

struct SideTable {
    /** 自旋锁 */
    spinlock_t slock;
    /** 引用计数表(散列表) */
    RefcountMap refcnts;
    /** weak表(内部使用数组或二级表实现) */
    weak_table_t weak_table;
}

...

// 相关初始化

// 返回内存对齐后的SideTable指针
alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];

// 初始化SideTable
static void SideTableInit() {
    new (SideTableBuf) StripedMap<SideTable>();
}


// 获取SideTable数组
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

其中说明,由于libc在C++初始化对象之前就调用了SideTable,且没有使用全局指针。故使用了这种方式初始化。

根据源码可以看到,SideTables是模板类StripedMap使用SideTable结构体初始化的类实例的指针。StripedMap是一个使用分离锁实现的类,内部成员是一个数组,使用hash算法进行索引存储。其算法如下:

enum { StripeCount = 8 };

static unsigned int indexForPointer(const void *p) {
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

故在生成SideTable时,系统使用实例对象的地址,将其进行hash计算后,得到index索引,最终从数组中取出SideTable对象:

// StripedMap重载了“[]”运算符

public:
T& operator[] (const void *p) { 
    return array[indexForPointer(p)].value; 
}
const T& operator[] (const void *p) const { 
    return const_cast<StripedMap<T>>(this)[p]; 
}

SideTable的获取:

NSObject obj;
SideTable *table = &SideTables()[obj];
2.2 RefcountMap

RefcountMap是引用计数表存储的数据结构。

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

其中存储的key是objc_object的指针。

根据继承关系可以发现,DenseMap继承的是DenseMapBase类,其内部存储的元素BucketT是c++键值对,而其主要功能,就是返回c++的迭代器iterator。

// DenseMapBase类
typedef std::pair<KeyT, ValueT> BucketT; // 键值对定义为BucketT


// public API示例

inline iterator begin() {
// When the map is empty, avoid the overhead of AdvancePastEmptyBuckets().
return empty() ? end() : iterator(getBuckets(), getBucketsEnd());
}
inline iterator end() {
return iterator(getBucketsEnd(), getBucketsEnd(), true);
}

// DenseMap类
BucketT *Buckets;
unsigned NumEntries;
unsigned NumTombstones;
unsigned NumBuckets;

因此,从代码可以知道,RefcountMap是一个存储键值对(实例对象指针作为key,size_t作为value),使用迭代器进行索引的散列表

3. 内存管理相关

3.1 alloc & init
+ (id)alloc {
    return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

根据调用流程,NSObject的实例创建中,实际上调用的是 id obj = class_createInstance(cls, 0); 。在内部实现中,可以发现zone已经被忽略。

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

而最终,经过多步跳转,最后执行的是 obj = (id)calloc(1, size); ,也就是给对象分配内存
然后通过 obj->initInstanceIsa(cls, hasCxxDtor); 给对象初始化isa_t中的相关信息。

注:
在创建实例对象的过程中,我们发现,系统在类的结构中会尽量使用nonpointer的方式创建对象,以便直接使用自身指针所占空间进行信息保存,提高效率。

最后的isa_t信息赋值过程如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;

        isa = newisa;
    }
}

在这里,我们也并未发现系统对新对象的引用计数进行过操作

而对于init方法,系统默认只是简单地返回了实例对象自身:

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

总结:

二段式初始化实例对象,只是分配内存空间,并给isa成员赋值,然后通过init方法返回实例对象。且 <u>并没有</u> 将引用计数设置为1。

3.2 retainCount

由于初始化时系统没有处理引用计数,故我们可以从retainCount中下手,查看如何实现。实例对象的引用计数实现如下:

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

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

    sidetable_lock();
    
    // 获取bits数据
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    
    
    if (bits.nonpointer) {
        // 优化过的nonpointer
        uintptr_t rc = 1 + bits.extra_rc; // 使用extra_rc位记录8位,即255以内的计数
        if (bits.has_sidetable_rc) {
            // 超过上限,加上SideTable的额外计数
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

在实现中可以看出,新对象的引用计数的1是由retainCount质检设置的。而较小数量的计数时,则直接从Class中的extra_rc数据域中进行读取;而较大计数(超过255以上的)则从SideTable中的refcountMap中进行读取累加后返回。

注意:读取SideTable中的引用计数时需要保证线程安全,进行加锁操作(SideTable内部的是自旋锁,性能好)。

在SideTable中读取引用计数的方式如下:

#define SIDE_TABLE_RC_SHIFT 2

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    // 找到对象对应的引用计数
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

这里,table.reccnts.find(this) 就是通过开始提到的DenseMapBase中的查找方法得到引用计数表的迭代器对象。

3.3 retain

说到了读取retainCount值,保留操作又是如何实现的呢?

- (id)retain {
    return ((id)self)->rootRetain();
}

由于在ARC环境下,系统在编译期添加的retain实现为底层的c函数(跳过消息发送流程,直接执行函数,效率高),故我们从c的retain方法看起:

__attribute__((aligned(16)))
id 
objc_retain(id obj)
{
    if (!obj) return obj; // nil直接返回
    if (obj->isTaggedPointer()) return obj; // taggedPointer指针对象,直接返回该指针
    return obj->retain(); // 正常retain
}

由于我们创建的对象一般不是taggedPointer,故执行的是标准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 rootRetain();
    }

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

由于默认没有自定义引用计数相关方法,走的是rootRetain函数。

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


ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false; // 标识是否加锁
    bool transcribeToSideTable = false; // 标识是否使用SideTable

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 获取isa数据
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 由于一般创建的对象都是nonpointer的,故这里表示为非nonpointer
            // 即不使用isa数据的内部引用计数,直接使用SideTable的情况
            
            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;
        }
        
        // 使用isa的情况,直接给内部的extra_rc加1,carry作为溢出标识(extra_rc为8位,最大为255)
        // RC_ONE从第57位开始,也就是说8位中是从左到右是从低到高排列
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // carry不是0了,则extra_rc超过最大值,溢出
            if (!handleOverflow) {
                // 不处理溢出情况
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            
            // 处理溢出
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true; // 准备从SideTable进行处理
            newisa.extra_rc = RC_HALF; // 只留下一半128
            newisa.has_sidetable_rc = true; // 标识为使用SideTable保存引用计数
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // transcribeToSideTable为true,使用ideTable进行处理(引用计数加上之前减少的128)
        sidetable_addExtraRC_nolock(RC_HALF);
    }

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

这里可以看到,在isa_t中的数据位extra_rc没有超限(小于255)时,直接操作其值+1,完成retain;如果超限,则将extra_rc中的一半(128)拷贝到SideTable中的refcnts中进行存储。其中,在SideTable中进行retain操作如下:

// 从isa位域中copy部分引用计数值到SideTable中。
// 如果对象的引用计数达到上限,返回true
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];

    // 得到SideTable中自身对象对应的引用计数值
    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);

    // 若已经达到最大值,就不加了,直接返回(最高位为1证明计数达到上限)
    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    // 溢出标识
    uintptr_t carry;
    
    // 进行 + 1操作(第三低位为引用计数最低位)
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        // 溢出了,证明到达或超过最大值了(间接证明了共有64 - 2 - 1 = 61位用来存储引用计数)
        // 记录最高位为1,其余位不变
        // 011 & oldRefcnt代表排除最后两位的影响
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        // 没有溢出,直接使用结果,retain成功
        refcntStorage = newRefcnt;
        return false;
    }
}

这里可以看出,SideTable的refcnts中存储的对象的引用计数值(size_t类型,即8字节)中,最高位为上限标识,最低位为weak标识,次低位为deallocating标识,故共有61位用于存储引用计数**。

若对象不是nonpointer的,则是使用SideTable直接进行引用计数的,实现就简单多了:

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)) {
        // 没有到达上限(最高位为1),引用计数 + 1 (100,第三低位开始是引用计数最低位)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
3.4 release

由于ARC环境,我们也从objc_release函数说起。

__attribute__((aligned(16)))
void 
objc_release(id obj)
{
    if (!obj) return; // nil无效
    if (obj->isTaggedPointer()) return; // tagPointer的,直接返回(直接存到指针里了)
    return obj->release(); // 正常释放
}

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

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

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

其中,最终执行的是rootRelease函数:

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

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 {
        // 获取isa指针
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        
        
        if (slowpath(!newisa.nonpointer)) {
            // 对象不是nonpointer的优化指针,直接通过SideTable进行释放操作
            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;
        // extra_rc - 1(减去的是引用计数的最低位,从左到右是从低到高,在x86_64下即减去1)
        // 这里是因为若extra_rc是0,减去1则会发生下溢出,也就是说,
        // 此时已经没有引用计数了,需要dealloc
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            // 非0,即下溢出发生(得到了负值,此时证明引用计数已经为0,需要dealloc了)
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    // 没有释放锁,释放
    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 // 下溢出情况(引用计数已经为0,需要dealloc)
 underflow:
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        // 检测是使用了SideTable进行引用计数存储
        
        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;
        }

        // 从SideTable的refcnts中减去128,作为借的值(结果只能是128或0)
        // 相当于把以前从extra_rc中移过去的128恢复回来
        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) {
            // 存储isa_t内容中的extra_rc位的值为127,并存储回去(减去的1是alloc默认的1)
            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 {
            // 没有借到值,即SideTable的refcnts存储的引用计数就是0。直接dealloc就可以了
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        // isa_t中已经标记为需要dealloc了
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        // 报错(正常release过程的引用计数必定不为0)
        return overrelease_error();
        // does not actually return
    }
    
    // isa_t对应数据位标记为需要dealloc,并保存
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    // 没有释放锁,释放
    if (slowpath(sideTableLocked)) sidetable_unlock();

    // 同步执行默认的dealloc方法
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

注:

extra_rc的8位(x86_64架构下)从左往右是低位到高位的存储,故1是0b10000000。
RC_ONE
1ULL << 57 对应的正好是extra_rc的最低位,也就是最左边的位**。
所以,release时,isa.bits - RC_ONE时,如果引用计数为0,则会出现负值,也就是下溢出。

总结一下,其实release干了这几件事:

  1. 如果本身不是nonpointer,则直接从SideTable对应的refcnts中进行计数减1操作。
  2. 是nonpointer的情况:直接对extra_rc引用计数减1。根据结果进行区分:
    1. 如果原来extra_rc是0,减1后发生下溢出。但是此时需要看一下是否SideTable中还存储着额外的引用计数:如果存在,则“借位”,将原来移过去的128挪回来,减去alloc的1后,将127存储到extra_rc中,结束;SideTable中的引用计数时0,则代表彻底没有引用计数了,需要dealloc。
    2. 原来extra_rc不是0,减1后,结束。

其中,在SideTable的refcnts计数表中对计数进行减1操作,则很简单:

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    // 根据对象获取SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    // 在SideTable的refconts中查找自身对象的引用计数信息
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        // 未找到,证明没有计数记录,需要dealloc对象
        do_dealloc = true;
        // 在引用计数表中标记自身的数据为“正在释放”(10)
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // 存在记录数据,且数据 < 10(即01 或 00),也许设置了包含弱引用(01),不要修改
        // 证明引用计数已经为0(低两位为保留位,第三低位才是最低位)
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING; // 将第二低位设置为1(正在释放)
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        // 引用计数未达到上限(最高位不是1)
        it->second -= SIDE_TABLE_RC_ONE; // 引用计数 - 1(第三低位为引用计数的最低位)
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        // 需要dealloc对象,且计数已为0,调用释放方法
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

最终,同步执行的SEL_dealloc就是我们NSObject释放对象时的dealloc实例方法**。

3.5 dealloc

不废话,直接上流程:

- (void)dealloc {
    _objc_rootDealloc(self);
}

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

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    // taggedPointer,不处理(只是指针)
    if (isTaggedPointer()) return;

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        // 类是nonpointer优化对象,且没有存储相关附带信息
    
        // 不能在SideTable中的weak_table和refcnts中存储信息
        assert(!sidetable_present());
    
        // 直接释放内存
        free(this);
    } 
    else {
        // 存在其他配置信息,正常释放
        object_dispose((id)this);
    }
}

这里可以看到,当对象的isa指针数据中没有相关信息的标志位,也就是说,没有其他额外信息(优化指针,且没有weak指向、没有相关对象、没有实现自定义的c++释放函数且没有使用SideTable进行外部引用计数存储),则直接释放内存(因为相关信息都保存在了isa的位域中了,不存在外部关联内存,故可以直接释放)。因此,如果类没有其他相关设置,释放对象操作会特别高效

其他情况下,则会调用object_dispose函数进行正常释放:

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

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

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

object_dispose中,需要额外进行的,就是在objc_destructInstance函数中,依照顺序将相关联的其他信息移除。顺序如下:

**1. 执行自定义的c++释放方法(如过存在)

  1. 将相关对象从全局相关信息表中移除(如果存在)

  2. 移除SideTable中的weak信息及引用计数信息

  3. 最终,返回object_dispose中,释放指针内存,结束。**

  4. 检查并执行c++的释放函数,依照“子类 -> 父类” 的继承体系依次查找执行。没有实现则直接返回。

static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Call cls's dtor first, then superclasses's dtors.

    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return; 
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}

注:

这个c++的析构函数(SEL_cxx_destruct,对应的是.cxx_destruct),实际上系统将对象的ivar变量的释放操作插入了这里进行。因此实现了在ARC环境下,对象的成员变量自动释放的功能
实现了自动在最后执行[super dealloc]的功能(ARC下dealloc过程及.cxx_destruct的探究,后面llvm实现看不懂,mark一下)。

  1. 移除相关对象,则是从全局hash表AssociationsHashMap中根据对象地址获取到存储该相关对象的子表,然后移除子表,并将子表中所有指向的对象依次释放。
void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    
    {
        // 获取全局相关对象的整体哈希表
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
    
        // 本身没有存储任何信息,直接返回
        if (associations.size() == 0) return;
    
        // 将object指针转换为查询的指针
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 试图查询是否存在object的相关对象容器信息
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            // 已查到,获取入口地址
            ObjectAssociationMap *refs = i->second;
            
            // 依次遍历子哈希表(ObjectAssociationMap对象的键值对信息)
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                // 头插法插入到向量对象中
                elements.push_back(j->second);
            }
            // remove the secondary table.
            // 删除入口地址信息
            delete refs;
            // 清除子表(object容器中存储的所有信息,即ObjectAssociationMap对象)
            associations.erase(i);
            // 清除之后,object便没有任何相关对象保存
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    // 对向量中存储的所有相关对象信息,依次释放内存
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

释放每个相关对象的操作如下:

struct ReleaseValue {
    void operator() (ObjcAssociation &association) {
        releaseValue(association.value(), association.policy());
    }
};

static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        // 通过按位与运算,确认是retain的对象,才进行释放操作
        return objc_release(value);
    }
}
  1. 最后,清除链接到的其他信息。如外部SideTable中的引用计数表和weak表。
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());
}

通过查看对象是否为nonpointer优化指针,根据情况进行SideTable中信息的移除。

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

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) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

可以看到,二者实现的功能相同,都是在自身对应的SideTable中将refcnts和weak_table对应的信息移除。唯一区别是,nonpointer的优化对象指针需要在执行前判断对应的isa位域的值。

但是重要的是,在对象清除自身引用计数表之前,将weak_table中自身的所有weak指针清除,置为nil

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    
    // 获取全局weak_table_t中referent对应的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()) {
        // 使用的外部动态数组(weak指针超过4个),配置信息
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        // 未超过4个,使用的是自身内部的静态数组,配置信息
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    // 依次将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_table_t表中,移除entry(清除容器)
    weak_entry_remove(weak_table, entry);
}

因此,我们可以看到,在dealloc执行的最后一步(free指针之前),运行时系统将所有指向释放对象的weak指针置为了nil,完成了weak在ARC下的功能。然后清除引用计数表

3.6 autorelease
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}


// Base autorelease implementation, ignoring overrides.
inline id 
objc_object::rootAutorelease()
{
    // taggedPointer对象,不处理
    if (isTaggedPointer()) return (id)this;
    
    // 返回值优化(若调用方为strong对象,则使用objc_autoreleaseReturnValue替代)
    // 直接返回,不执行autorelease(ReturnAtPlus1即true)
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    // 标准autorelease
    return rootAutorelease2();
}

由于autorelease优化涉及到汇编知识,自己不懂。只明白大体意思:在通过查看调用方的入口地址,通过偏移量算出自身函数返回的地址,得到返回值的接收方的内存管理方式。如果接受对象为strong对象,则使用objc_autoreleaseReturnValue替代autorelease函数(将对象地址保存到当前线程绑定的地址空间中----TLS,Thread Local Storage,线程局部存储)。

这里我们先看标准的autorelease----rootAutorelease2

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

这里实际上使用的是AutoReleasePoolPage类进行实现。主要功能如下:

3.6.1 AutoReleasePoolPage的基本结构
class AutoreleasePoolPage 
{
    // 为了节省内存,在没有任何对象进行autorelease操作之前,在,在所在线程中的绑定内存中保存一个指定信息(1)作为占位pool。
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

    // 占位pool存储首个对象前的分隔符
#   define POOL_BOUNDARY nil

    // 在所在线程的绑定地址中保存hotPage对象的key
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    
    // 虚拟内存页尺寸,4096bytes
    static size_t const SIZE = PAGE_MAX_SIZE;
    
    // 4096 / 8 = 512个对象
    static size_t const COUNT = SIZE / sizeof(id);
    
    magic_t const magic; // 完整性数据
    id *next; // 指向新位置(相当于栈顶指针)
    pthread_t const thread; // 所在线程(一一对应)
    AutoreleasePoolPage * const parent; // 父page
    AutoreleasePoolPage *child; // 子page
    uint32_t const depth; // 深度(根page为0,子为1,以此类推)
    uint32_t hiwat;
    
    ...
    
    // 基本功能函数
    
    // 获取AutoReleasePoolPage实例对象的存储区域开头
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
    
    // 获取AutoReleasePoolPage实例对象的存储区域结尾
    id * end() {
        return (id *) ((uint8_t *)this+SIZE); // 自身地址偏移4096字节,为end
    }

    // 判定AutoReleasePoolPage实例对象的存储空间是否为空
    bool empty() {
        return next == begin();
    }
    
    // 判定AutoReleasePoolPage实例对象的存储空间是否已满
    bool full() { 
        return next == end();
    }
    
    // 向当前AutoReleasePoolPage实例对象中插入obj,返回插入地址
    id *add(id obj)
    {
        assert(!full());
        unprotect(); // 设置对象为可读可写
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj; // 将next指向obj。next后移
        protect(); // 设置对象为只可读
        return ret;
    }

    // 释放AutoReleasePoolPage实例对象中的所有对象
    void releaseAll() 
    {
        releaseUntil(begin());
    }
    
    ...
}

根据AutoReleasePoolPage类的基本结构可以看出:

  1. 其在内存中的存储结构为:地址由低到高,首先保存的是类中的成员变量,然后是大小为4096字节的存储空间,用于保存设置为autorelease的对象实例
  2. 两个指针beginend分别指向存储空间开始和末尾,使用next指针指向空间中最后一个对象的下一位置**,相当于栈结构中的栈顶指针。
  3. 整体的自动释放池是由一个个AutoReleasePoolPage对象通过parentchild指针连接而成的<u>双向链表</u>。其中,EMPTY_POOL_PLACEHOLDER占位pool并不相当于头指针或头节点,而是只是一个标记位,在尚未使用AutoReleasePool之前节省内存。

现在,我们回到刚才的问题。

3.6.2 objc_object::autorelease的实现
static inline id autorelease(id obj)
{
    assert(obj);
    assert(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

可以看出autorelease的本质是通过autoreleaseFast将obj插入到AutoReleasePoolPage实例对象中。

static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        // page未满,插入新对象(next指向obj,然后返回next指针地址)
        return page->add(obj);
    } else if (page) {
        // page已满,创建新page并插入新对象
        return autoreleaseFullPage(obj, page);
    } else {
        // 没有存在page,创建第一个page并插入新对象
        return autoreleaseNoPage(obj);
    }
}

这里,根据当前使用的AutoreleasePoolPage对象的存储状态,分情况进行处理:

  1. 当前page没有存满,则直接插入;
  2. 当前page已经存满,创建新page然后插入;
  3. 不存在page(只有一个EMPTY_POOL_PLACEHOLDER占位用),创建新page然后插入。

相关实现如下(直接插入的不用说了,将next指针的数据域存储上obj地址,然后next后移即可):

  • 向已存满的AutoReleasePoolPage对象插入新对象:
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);

    // 如果当前page已满(next == end),检查并创建新page对象
    do {
        // 子page存在,向子page中尝试插入
        if (page->child) page = page->child;
        // 没有,则创建新page对象
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    // 设置为hotPage(绑定到当前线程)
    setHotPage(page);

    // 插入对象
    return page->add(obj);
}
  • 只有一个初始状态下的占位pool,没有真正用于存储的AutoReleasePoolPage对象,创建新的page对象,然后插入。
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    // 没有page,可能意味着是根本没有pool对象,或者是一个占位pool存在但是没有内容(后者是正确的)
    assert(!hotPage());

    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        // 只有占位pool对象(正常情况1)
    
        // 我们正在空的占位pool后面插入一个新pool,
        // 或者在占位空pool中插入第一个对象。
        // 在开始之前,插入一个pool分隔符(nil)来代表一个空标志位
        
        // 存在占位空pool(当前只有占位空pool,没有任何其他内容),需要插入分隔符
        pushExtraBoundary = true;
        
        // 题外话:
        // 由于分隔符是nil,那么如何区分插入到AutoReleasePool中的nil对象呢?
        // 在最上层,对nil发送消息实际上在最开始就被忽略掉了,所以这里不会有其他nil因素干扰。
        // 所以,只要是nil,就可以将其认定为这次的AutoReleasePool的开端(drain时会释放到此nil位置)。
    }
    else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
        // obj存在(对象或新pool),但是没有占位pool对象【错误情况】
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     pthread_self(), (void*)obj, object_getClassName(obj));
        // 添加breakpoint崩溃处理
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        // 不存在占位空pool,且obj是nil,即需要创建并返回一个占位空pool(正确情况2,不用于存储对象,只是用于创建占位pool)
        return setEmptyPoolPlaceholder();
    }
    
    // 插入实例对象 或者  插入非占位的正常AutoReleasePoolPage对象(新pool)过程:


    // 创建根pool
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    // 设置为hot(当前pool)
    setHotPage(page);
    
    if (pushExtraBoundary) {
        // 插入分割位(nil)
        page->add(POOL_BOUNDARY);
    }
    
    // 插入obj(对象或是新子pool)
    return page->add(obj);
}

这里有两个点要注意:

  1. 获取和创建占位pool:
/** 判定是否存在占位pool */
static inline bool haveEmptyPoolPlaceholder()
{
    id *tls = (id *)tls_get_direct(key);
    return (tls == EMPTY_POOL_PLACEHOLDER);
}

/** 设置保存占位pool */
static inline id* setEmptyPoolPlaceholder()
{
    assert(tls_get_direct(key) == nil);
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    return EMPTY_POOL_PLACEHOLDER;
}

这里使用的tls_get_directtls_set_direct函数的实现如下:

static inline void *tls_get_direct(tls_key_t k) 
{ 
    assert(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        // 返回当前线程中绑定的指定位的值
        return _pthread_getspecific_direct(k);
    } else {
        return pthread_getspecific(k);
    }
}

static inline void tls_set_direct(tls_key_t k, void *value) 
{ 
    assert(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        _pthread_setspecific_direct(k, value);
    } else {
        pthread_setspecific(k, value);
    }
}

它们实际上就是使用一个固定的内存空间来供指定线程来进行数据存取(线程与内存空间一对一绑定)。这种方式叫做TLS----Thread Local Storage,线程局部存储。

这里就是使用此方式,将1(id指针)存储到当前线程的绑定存储区域中,使用key进行绑定(key - value方式存取)。

  1. hotPage的读取与设置。
static inline AutoreleasePoolPage *hotPage()
{
    // key是AUTORELEASE_POOL_KEY,即返回线程绑定的AutoreleasePoolPage对象
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key); // 返回当前线程中key绑定的值
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 不存在poolPage,返回
    if (result) result->fastcheck(); // 检查AutoreleasePoolPage对象信息是否完整
    // 不完整,fastCheck函数中直接崩溃
    return result;
}

static inline void setHotPage(AutoreleasePoolPage *page) 
{
    if (page) page->fastcheck();
    tls_set_direct(key, (void *)page); // 绑定到当前线程
}

hotPage,即代表当前正在使用的AutoReleasePoolPage对象(一般意味着自动释放池链表的最后一个pool对象)。也是使用了TLS的方式将当前使用的pool对象的地址与key绑定存储到指定区域中。

3.6.3 建立新的@autoreleasepool{}

注意
一定要明确,在上层创建@autoreleasepool{}代码块,与底层实现中的AutoReleasePoolPage对象的创建没有任何关系!
底层的整体释放池存储使用的是链表结构,一个AutoReleasePoolPage对象存满之后才会创建新的对象,而创建@autoreleasepool{}代码块,只是在当前的hotPage对应的AutoReleasePoolPage对象中插入一个标志位,用于在RunLoop执行drain时,标记此代码块在hotPage对象中开始的位置。

  • 代码块开始对应着objc_autoreleasePoolPush函数:
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

在非debug模式下,实际上就是autoreleaseFast(nil) 。即在hotPage对应的AutoReleasePoolPage对象中插入一个标志位。
而在debug模式下,执行的是autoreleaseNewPage(nil) 。也就是每次创建一个新AutoReleasePoolPage对象,然后再插入标志位。只有此时与@autoreleasepool{}代码块是一一对应的。

  • 代码块结束对应着objc_autoreleasePoolPop函数:
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}


static inline void pop(void *token) 
{
    // token实际上是内容为nil的指针(调用push时,插入分隔符nil后返回的地址)

    AutoreleasePoolPage *page;
    id *stop;
    
    // 处理token为占位pool的情况
    ...

    // 根据token(实际上是内容为nil的指针)找到所属的AutoReleasePoolPage对象
    page = pageForPointer(token);
    stop = (id *)token;
    if (*stop != POOL_BOUNDARY) {
        // 若stop的数据域中不是nil(分隔符)
        
        if (stop == page->begin()  &&  !page->parent) {
            // stop指向的就是根page对象的begin位置,即无需做额外工作(用于以后复用)
        } else {
            // 错误,只有nil分隔符才是正确的释放停止点
            return badPop(token);
        }
    }

    // 依次释放直到指定的标志位(分隔符nil)为止
    page->releaseUntil(stop);

    // 删除空的子page对象,释放内存
    ...
}

这里的实现经过简化。主要目的就是将push时得到的token地址作为标记,将token及其后面插入的autorelease对象依次释放

其中,主要的实现有两点:

  1. 根据token确定所处的AutoReleasePoolPage对象。
  2. 在此page对象中,倒序删除所有保存的数据,直到token地址为止(包括token位置的nil数据)。

依次来看实现。

  1. 根据插入的对象地址,获取所在的AutoReleasePoolPage对象。
static AutoreleasePoolPage *pageForPointer(const void *p) 
{
    // void * 转换为了 uintptr_t 类型,可以进行按位操作
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
{
    AutoreleasePoolPage *result;

    // 通过取余运算,得到地址在一个page内存中的偏移量
    uintptr_t offset = p % SIZE;

    assert(offset >= sizeof(AutoreleasePoolPage));

    // p - offset,即得到最近一个page对象的起始地址
    result = (AutoreleasePoolPage *)(p - offset);

    // 验证page的有效性
    result->fastcheck();

    return result;
}

我们知道,AutoReleasePoolPage对象可以看作为一个类似数组的容器,将p地址减去在一个page容器中的偏移量,即可得到此page对象的起始地址。

  1. 在AutoReleasePoolPage对象中,倒序移除保存的数据指针。
void releaseUntil(id *stop) 
{
    // 直到释放到stop所在位置才可
    while (this->next != stop) {        
        // 获取当前使用的page对象(防止此时再插入更多的autorelease对象【没有加锁,也不是同步操作】)
        AutoreleasePoolPage *page = hotPage();

        while (page->empty()) {
            // 当前page已空(且还没有到stop),向上继续查找
            page = page->parent;
            // 设置为hotPage(下次从这开始查找)
            setHotPage(page);
        }

        page->unprotect(); // 解锁读写权限
        // 减1后,获取指向的数据
        id obj = *--page->next;
        // 将next的数据域设置为0xa3(由于指针是4字节,因此写入完成后是0xa3a3a3a3)
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect(); // 锁定page

        if (obj != POOL_BOUNDARY) {
            // 取出的数据不是分隔符,则是正常对象,执行release操作
            objc_release(obj);
        }
        // obj是nil分隔符,则继续向上(因为次分隔符地址不是指定的那个)
    }

    // 重新将当前page设置为hotPage
    setHotPage(this);

#if DEBUG
    // 确保所有的子page都是空的
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}

实际的过程可以看做是在栈中将对象的地址进行出栈操作,然后对每个出栈的对象调用release方法,直到nil标志位出栈后停止。

在后面检查移除page对象的空的子page中,kill函数也可以看一下:

void kill() 
{
    AutoreleasePoolPage *page = this;

    // 获取最后的子page对象
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        // 获取父page对象
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil; // 将子page置为nil
            page->protect();
        }
        delete deathptr; // 删除子page指针
    } while (deathptr != this);
    // 直到删除到自身page
}

由于AutoReleasePoolPage组成的是双向链表,故

  1. 首先根据child指针域依次找到最末尾的page对象。
  2. 然后从后往前,根据parent指针找到父page对象,将自身置为nil,断开连接。直到调用方最初的page对象为止。

4. 参考资料

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

推荐阅读更多精彩内容