OC内存管理

209 阅读4分钟

内存布局

内存管理方案

TaggedPointer 专门用来存储小的对象,例如NSNumber和NSDate。Tagged Pointer的引入,给 64 位系统带来了内存的节省和运行效率的提高。Tagged Pointer通过在其最后一个 bit 位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其 isa 变量。如果 8 字节承载不了时,则又用以前的方式来生成普通的指针。

NONPOINTER_ISA在32位为指针类型,保存着类对象的地址。在64位上部分保存着指针地址,其余的为标记位。例如:

     //64位arm
      uintptr_t nonpointer        : 1; //表示是否对isa开启指针优化 。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。
      uintptr_t has_assoc         : 1; //关联对象标志位
      uintptr_t has_cxx_dtor      : 1; //该对象是否有C++相关内容
      uintptr_t shiftcls          : 33;//对象地址
      uintptr_t magic             : 6;//判断当前对象是真的对象还是一段没有初始化的空间
      uintptr_t weakly_referenced : 1;//是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快
      uintptr_t deallocating      : 1;//是否正在释放
      uintptr_t has_sidetable_rc  : 1;//是否使用sidetable
      uintptr_t extra_rc          : 19//存储一部分引用计数,当空间不够,则has_sidetable_rc进位,表示使用sidetable进行记录

散列表

散列表数据结构

散列表结构:

自旋锁spinlock_t:盲等锁,适合轻度访问。

引用记数表RefcountMap:Hash表,通过对象地址和hash函数查找对象的引用计数

弱引用表weak_table_t:Hash表,对象指针为key通过hash函数,找到weak_entry_t结构体数组,weak_entry_t中存储weak修饰的对象指针。

ARC&&MRC

MRC是手动引用计数,关键字alloc retain retainCount release autorelease dealloc. ARC是自动引用计数器,LLVM和Runtime共同协作为我们管理引用计数。不能手动使用retain retainCount release autorelease。新增weak strong。

引用记数管理

alloc:本质就是调用C语言的calloc,给对象分配内存

retain:根据对象指针经过两次hash查找找到对应的引用计数值。

    SideTable & table = SideTables()[this]
    size_t &refcntStorage = table.refcnts[this]
    refcntStorage += SIDE_TABLE_RC_ONE

release:原理同retain,也是两次hash查找找到对应引用计数,然后-1

reainCount:对象alloc后retainCount为什么引用计数为1 ?

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

dealloc:方法中主要是判断5个条件是否满足释放要求,如果不满足就分别清理对应的条件。分别为:nonpointer_isa(当前对象是否为非指针类型)、has_assoc(是否有关联对象内容)、has_cxx_dtor(是否有C++相关内容)、weakly_referenced(是否有弱引用)、has_sidetable_rc(是否通过引用记数表维护)

弱引用管理

弱引用计数是当对象被__weak修饰后会调用objc_initWeak()->storeWeak()->weak_register_no_lock()方法在弱引用记数表中记录该对象,然后对象释放后会在dealloc方法中调用weak_clear_no_lock()方法,将指针指向nil,以避免野指针。

自动释放池

在当次runloop将要结束的时候调用AutoreleasePoolPage:pop(). 多层嵌套就是多次插入哨兵对象。 使用场景比如在for循环中下载大量图片的时候可以使用。 当我们调用@autoreleasepool{}时,编译器为我们生成如下的代码:

void *ctx = objc_autoreleasePoolPush()
{}中的代码
objc_autoreleasePoolPop(ctx)

其中objc_autoreleasePoolPush()内部调用AutoreleasePoolPage::push(void)方法。objc_autoreleasePoolPop(ctx)内部调用AutoreleasePoolPage::pop(void *ctx)方法。所以我们需要研究下AutoreleasePoolPage的结构体:

id *next;
AutoreleasePoolPage *parents;
AutoreleasePoolPage *child;
pthread_t const thread;

从以上的结构体可以看出AuroreleasePoolPage是一个以栈为节点的双向链表,并且和线程是一一对应的关系。 那么AutoreleasePoolPage在内存中的布局是:

当调用AutoreleasePoolPage:push(void)会在栈中插入以个哨兵对象,并且next向栈顶移动

当调用AutoreleasePoolPage::pop(void*ctx)的时候next会向栈底移动,直到找到最近的哨兵,并且向中间的对象发送release消息。

循环引用

循环引用大致分为:自循环、互相循环和多个对象循环引用。常见循环引用问题如:在轮播图中NSTimer的循环引用,runloop强持有timer,timer强持有视图对象,造成更对象无法释放。解决方法是,添加一个中间对象来,弱持有timer,和使用timer的对象。