搞懂Objective-C中的autorelease

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搞懂Objective-C中的autorelease相关的知识,希望对你有一定的参考价值。

参考技术A

本文是上一篇: 搞懂Objective-C中的ARC 的延伸和补充

操作系统实现了一个线程工厂称为线程池,还有一个配合工作的管理器称为调度中心,ios系统并没有接口去直接操作线程池,线程的创建、调度、销毁全权交给线程池,当我们试图去创建一个线程的时候,调度中心会 检查线程池的运行状态、运行线程数、运行策略,决定接下来的执行流程,是直接申请线程执行还是放入缓冲队列中,待到执行的时候,线程池决定是开辟新的线程还是复用现有空闲线程,长时间空闲的线程会被回收 (一般10s左右)

iOS操作系统为 线程运行 配备了一套数据结构, 一个栈、一个autoreleasePool、一个runloop(懒加载) 以及一些用于控制状态的标志位变量

好的~我们回到问题本身

先说结论:

再看个demo,这里只讨论非主线程:

如上创建了一个串行队列,写了个死循环,一直在创建堆区字符串,但是内存不会涨一丝一毫,原因上一篇文章 搞懂Objective-C中的ARC 讲过,这个对象直接会被release,不会加入autoreleasePool

修改一下:

会发现非主线程创建autorelease对象会导致内存无休止的在涨,直到OOM

再次修改:

结果是内存也不会涨一丝一毫,数据刚好能对应上面的结论,对于有知其所以然需求的可以往下看

前辈 这篇 讲得很好了,本文会基于最新源码做个浅析(可能不是最新源码,作者开始看源码时候的版本objc4-781.2),顺便讲述下如何查阅源码, 源码链接

会发现AutoreleasePoolPage是一个c++的类,继承AutoreleasePoolPageData,具体实现很长,这里省去部分函数实现

还是很长是不是,但是对于理解原理都重要,看看苹果自己的注释

/***********************************************************************
Autorelease pool implementation

A thread\'s autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/

/***********************************************************************
Autorelease pool 的实现
线程的Autorelease pool是一系列声明在栈上的指针。
每个指针要么是一个需要释放的对象,要么是 POOL_BOUNDARY,它是
Autorelease pool边界。
token是指向该池的 POOL_BOUNDARY 的指针。 什么时候
池被弹出,每个比哨兵更热的对象都被释放。
栈被分成一个双向链接的AutoreleasePoolPage列表。 Pages根据需要,添加
和删除。
线程本地存储指向最新被存储到自动释池所在的热点页面

**********************************************************************/

看完这个注释,我觉得都不用往下讲了😂,一个字,清晰透彻

next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置,当有新的obj加入autoreleasePool的时候取到next指针的地址存储obj,next指针向栈顶方向移动8个字节

每个autoreleasePool初始化的时候都会绑定当前线程,如下是AutoreleasePoolPage的初始化函数

第一个参数是AutoreleasePoolPage的标识,第二个参数是一个函数指针,线程退出之前会执行这个函数,就是tls_dealloc,调用栈如下:

线程休眠之前,runloop会调用objc_autoreleasePoolPop,上面流程除了第一步,后面一样

单向的可以吗?可以呀,但是查询速度会以指数级别降低,删除栈顶page后想删除的倒数第二个page,怎么办,从头开始遍历呗,遍历n-1次,然后遍历n-2次.......
所以为什么设计成双向链表,效率高,如此而已

一文搞懂│php 中的 DI 依赖注入

以上是关于搞懂Objective-C中的autorelease的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发 runtime实现原理以及实际开发中的应用

一文搞懂│php 中的 DI 依赖注入

一文搞懂数据结构中的线性表

轻松搞懂Java中的自旋锁

彻底搞懂 SQLAlchemy中的 backref

彻底搞懂Python 中的 import 与 from import