前言
在iOS中由于有ARC的存在,经常有博客会讲到循环时的内存堆积问题,主动加入autoreleasepool可以防止内存延时释放,降低内存峰值,这也是面试中优化方面常会被考察的点,于是项目中基本上凡是有有较多数据循环的地方都会被手动加入autoreleasepool,如果你没加,在code review的时候都会被认为为“没有一定内存方面的思考”(菜鸡😂)
本文不会去讲autoreleasepool的原理数据结构等知识(很多优秀的博客都讲的很好了),只会针对autoreleasepool用法这一点进行探讨
那么问题来了,你加的autoreleasepool真的有用么??
下面截取一段某大厂对外提供的demo代码片段(已做简化)
刚好因为需求看到了这段代码实在是太典型了,也是自身项目中众多循环代码的缩影,于是促成了想写这篇帖子的想法😂
槽点:创建的item会被加到数组中,本身就不会释放,为什么要加autoreleasepool呢
那去掉add操作后代码如下
看起来就比较像很多示例代码了,实际测试下来也并没有发生内存堆积导致的峰值升高
那把autoreleasepool去掉呢?代码如下
测试下发现也没有发生内存堆积导致的峰值升高😂
原因:很多人错误的理解了ARC导致认为创建的对象实际都是归autoreleasepool去管理的
实际上直接通过alloc 、new等创建的对象都是靠ARC自动插入release释放的,跟autoreleasepool没啥关系,只有被autorelease的对象才会被加入到自动释放池中受autoreleasepool的管理,代码如下
验证下确实会因为autorelease的释放机制(RunLoop)导致因内存堆积导致的峰值升高,此时手动加入autoreleasepool进行管理,就不会有内存堆积导致的峰值升高的问题了,代码如下
总结如下
但是实际开发中绝大多数对象都是直接通过alloc 、new等创建出来的,对象都是靠ARC自动插入release释放的,手动autoreleasepool+__autoreleasing就显得有点多此一举了
那么问题又来了,__autoreleasing到底应该啥时候用??
目前大厂面试都有一定的数据结构与算法方面的考核,现在来看下这一段代码,构造一条5W数据的链表
代码很简单看起来也没什么问题
运行一下会发现最终crash了
原因:头结点析构后会释放自身持有的属性(cxx_destruct,这里不去讲dealloc的流程 网上有很多优秀的帖子)导致next指向的node析构...不停的析构导致stack overflow引发的EXC_BAD_ACCESS
我猜这可能也是基于引用计数机制的iOS没有提供链表(NSNode)这种数据结构的一个原因,通过链表实现的栈、队列也没有直接提供(NSMutableArray底层的环型数组机制也能保障头尾操作是O(1)的,可以作为栈、队列的平替)
那么如何解决呢??
可以在node对象的dealloc方法中手动制空next指针让next指向的node对象析构,其原理是将释放节点提前了,不会导致stack overflow(具体参考dealloc流程),但也只是治标不治本,将数组量级增大还是会导致stack overflow
最终解决方案是将next加入到自动释放池中,由自动释放池管理其释放,就不是造成因链式析构导致的stack overflow问题了(具体参考autoreleasepool释放流程)
RAC代码库里有更精细的实现(超过某一阈值后加入到autoreleasepool中),代码如下
感想
1.希望这篇文章能对你有所帮助,能更精准的使用autoreleasepool并对ARC autorelease等机制有更深入的理解
2.从业多年一直有想写点技术博客的冲动,但受限于没想到写什么(iOS相关的帖子实在是太多了,基本你想写什么都有很优秀的帖子存在了)和繁忙的工作(懒),这次因疫情原因终于鼓起勇气尝试了一下,希望能为以后打好基础
愿疫情早日结束、世界和平🙏🙏🙏
update
为SDWebImage提了pr#3388,已被合并