内存布局
① 栈区
stack
:方法调用会在栈区展开;
② 堆区heap
:通过alloc分配的对象,copy后的block,都是在堆区;
③ bss:为初始化的全局变量
④ data:已经初始化的全局变量
⑤ text:程序的代码段加载到内存中时,都是在text段中的
内存管理方案
系统针对不同场景提供不同的内存管理方案
① TaggedPointer:对于一些小对象如NSNumber等采用TaggedPointer管理方案;(深入理解Tagged Pointer)
② Nonpointer_isa:64位架构(arm64)下应用采用NONPOINTER_ISDA内存管理方案。在64位架构下,ISA指针占64bit位,实际上有30-40位就够用了,剩下的位数就浪费了,Apple针对这种情况,为了提高内存的利用率,在ISA当中剩余的位中存放了一些内存管理相关的内容,所以这个叫做非指针型ISA(Nonpointer_isa);
③ 散列表:是一个复杂的数据结构,其中包含了弱引用表和引用计数表
内存数据结构
- 散列表(side Tables()结构)
- side Tables()
实际是一张hash表,能通过hash算法快速查找某一个Side Table
为什么不是一个SideTable而是一个SideTables?
>为了提高访问效率采用分离锁的方式,将对象分配到不同的Side Table中,这样对多个Side Table中的对象访问可以同步访问,如果只有一个Side Table,只能进行串行访问,影响效率- Side Table:
spinlock_t涉及的内容是一些多线程和资源竞争
① 自旋锁spinlock_t
:是"忙等"的锁,适用于轻量访问;
② 引用计数表RefcountMap
:是一个hash表,将指针通过hash算法得到一个unsign long size_t
,size_t
的第一个表示是否有弱引用指针
,第二位表示是否在执行dealloc
操作,所以size_t
需要
右移两位才能得到正确的引用计数值
③ 弱引用表weak_table_t
:是一个hash表
ARC&MRC
- MRC手动引用计数:
retain、release、retainCount、autorelease
- ARC 自动引用计数:
① ARC 是LLVM编译器(自动添加retain、release等代码)与Runtime(weak表是通过运行时维护的,如weak对象被释放时自动设置为nil)协作的结果;
在 runtime 源码的 objc-internal.h 文件中声明了一些内存管理的函数,代码经由编译器编译会添加这些函数,从而实现引用计数的管理,包括 objc_alloc(Class cls)、objc_retain(id obj)、void objc_release(id obj) 等等,所以可以说 ARC 由编译器和 runtime 协作完成
② ARC 中是禁止调用MRC中的独有方法;
③ ARC 中新增weak、strong关键字
引用计数
① alloc:经过一系列函数封装和调用,最终调用了c函数calloc,此时并没有设置引用计数为1;
② return:我们在return操作时,系统时如何查找对象的引用计数SideTable& table = SideTables()[this];//根据对象通过hash查找到对应的SideTable size_t& refcntStorage = table.refcnts[this];//然后通过hash在refcnts引用计数表中查找引用计数 refcbtStorage += SIDE_TABLE_RC_ONE;
③ release:
SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); it->second -= SIDE_TABLE_RC_ONE;
④ retainCount:只通过alloc创建的对象,调用retainCount获取的值为1;
SideTable& table = SideTables()[this]; size_t refcnt_result = 1; RefcountMap::iterator it = table.refcnts.find(this); refcont_result += it->second >> SIDE_TABLE_RC_ONE;
⑤ dealloc:
非指针型ISANonpointer_isa
,弱引用weakly_referenced
(如果有需要对弱引用对象进行清理),关联对象assoc
,C++、ARC(cxx_dtor),引用计数表sidetable_rc
(是否使用了引用计数表管理引用计数)
rootDealloc实现相关inline void objc_object::rootDealloc() { assert(!UseGC); if (isTaggedPointer()) return; if (isa.indexed && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); } }
object_dispose实现相关
id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); #if SUPPORT_GC if (UseGC) { auto_zone_retain(gc_zone, obj); // gc free expects rc==1 } #endif free(obj); return nil; }
** objc_destructInstance**实现相关
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = !UseGC && obj->hasAssociatedObjects(); bool dealloc = !UseGC; // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj->clearDeallocating(); } return obj; }
弱引用
id __weak obj1 = obj; //经过编译后 id obj1; objc_initWeak(&obj1, obj);
weak变量被废弃后为什么会被置为nil:当一个对象被dealloc后,dealloc会调用当前对象的弱引用清除相关函数
weak_clear_no_lock()
,在weak_clear_no_lock()
内部实现中会根据当前函数指针查找弱引用表,把当前对象中的弱引用都取出来,然后分别置为nil
自动释放池
//编译器会将@autoreleasepool{}改写为 void *ctx = objc_autoreleasePoolPush(); {}中的代码 objc_autoreleasePoolPop(ctx);
自动释放池的结构:
① 是以栈为节点通过双向链表的形式组合而成的数据结构。
② 是和线程一一对应的。class AutoreleasePoolPage{ ... id *next;//指向栈当中下一个可填充的位置 AutoreleasePoolPage * const parent;//父节点 AutoreleasePoolPage *child;//孩子节点 pthread_t const thread;//线程相关 ... }
在当前runloop将要结束的时候,调用AutoreleasePoolPage::pop()。
autoreleasePool的实现原理:以栈为节点通过双向链表的形式组合而成的一个数据结构。
autoreleasePool为什么可以多层嵌套使用:多层嵌套就是在栈中多次插入AutoreleasePoolPage(哨兵)对象。
在什么样的场景需要手动创建autoreleasePool:在进行内存消耗较大的操作时,如在for循环中alloc图片数据等需要手动插入autoreleasePool
循环引用
分类
@interface ClassA : NSObject @property (nonatomic, strong) id *obj; @end @interface ClassB : NSObject @property (nonatomic, strong) id *obj; @end
自循环引用:自身持有自身
ClassA *a = [ClassA alloc] init]; a.obj = a;
互相循环引用
ClassA *a = [ClassA alloc] init]; ClassB *b = [ClassB alloc] init]; a.obj = b.obj; b.obj = a.obj;
多循环引用
即大环引用出现循环引用场景:代理、block、NSTimer、大环引用
如何破除循环引用:避免产生循环引用(weak,strong)、在合适的时机手动断环
__weak, __block,
__block的使用问题
MRC 环境下,block 截获外部用 __block 修饰的变量,不会增加对象的引用计数。
ARC 环境下,block 截获外部用 __block 修饰的变量,会增加对象的引用计数,无法避免循环引用,需要手动解环。
所以,在 MRC 环境下,可以通过 __block 来打破循环引用,在 ARC 环境下,则需要用 __weak 来打破循环引用。
NSTimer的循环引用问题
通过中间件(iOS中解决NSTimer循环引用问题)
iOS10中,定时器的API新增了block方法,可以使用新方法避免循环引用问题+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
总结
什么是ARC
为什么weak指针指向的对象在废弃之后会被自动置为nil
苹果是如何实现autoreleasePool的(原理)
什么是循环引用,你遇到过哪些循环引用,是怎么解决的