内存布局

内存管理方案
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的对象。