- iOS内存管理(上)简单的说了下retain、release和dealloc。不过关于内存管理还有个比较重要的东西autoreleasepool,也是兄弟们常说的自动释放池
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
1. 自动释放池autoreleasepool
1.1. autoreleasepool 结构分析
- 兄弟们在main.m常常会看到过
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
-
我们首先通过clang看下他的结构是啥样子的
xcrun -sdk iphonesimulator clang -rewrite-objc main.m -
得到main.cpp文件,定位位置
-
我们看下他的结构
他就是个结构体,有自己的构造和析构
1.2. autoreleasepool 压栈追踪
- 我们看下构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
- 然后找到他的压栈处理
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
这个时候,我们遇到了个AutoreleasePoolPage,到这了,那我们就看看这个的结构
1.2.1. 拓展 AutoreleasePoolPage
- 我追寻的过程
- 有个比较官方的名词解释,咱们先看一下
* magic 用来校验 AutoreleasePoolPage 的结构是否完整;
* next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() ;
* thread 指向当前线程;
* parent 指向父结点,第一个结点的 parent 值为 nil ;
* child 指向子结点,最后一个结点的 child 值为 nil ;
* depth 代表深度,从 0 开始,往后递增 1;
* hiwat 代表 high water mark 最大入栈数量标记
好吧,到了这一步,大家可能和我第一次过来一样,比较懵逼。。这个是干啥用呢,咋还有父节点和子节点
- 这个时候看下自动释放池实现的官方文档
/***********************************************************************
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.
**********************************************************************/
- 我来翻译一波了,😆(展示下我的大白话)
一个线程的自动释放池 是一个栈 --> 由好多指针组成(说白了就是栈结构,所以有后面的出栈和压栈。)
一个池子里的标记指向POOL_BOUNDARY(边界/哨兵),当这个池子被释放的时候,比POOL_BOUNDARY 还hot的进行 release
这个栈 划分为 双向链接的页,页 在必要的时候进行增加或者删除
本地线程 存储的是 新进来的,并把它设成 hot page(聚焦页面)
这样的话,这个页结构就好懂了些。由于是双向链接的页,所有有了parent和child
1.3. autoreleasepool压栈实现
- 那我们看下压栈的处理
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
每一个自动释放池 会在一个新页面上启动
- 我们追踪下
- 如果刚开始加
//上面还有些乱起把遭的。。太长了,我就不沾了
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
简单来说就是。如果没有页面,就给他个。并设置成hotpage,第一个页面的话会给个 POOL_BOUNDARY,然后add
- 看下add代码
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
这个就是他核心的push代码。(就是一步一步压,然后平移next指针)
- 如果页面满了
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
由于它是双向链接结构的页。所以页面满了,会找下一个页。通过child,然后把最新也设置成hotpage,在add
- 补充
上面其实就是他的压栈。
补充知识:听说面试有人问到过:就是一个自动释放池只有一个哨兵/边界,一页最大容量是505,第一页是哨兵加上504(当然这个大家也可以通过源码知道)
1.4. autoreleasepool 出栈
- autoreleasepool的出栈其实就是压栈的反向操作。然后通过parent结点release
- 首先看下我们刚才的的出来的析构
//构造
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
//析构 参数是构造得出的对象
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
- 兄弟们看下我的探究过程
我只是直接把找的核心代码,这些代码兄弟们可以去看看。会更加清晰。(我感觉有注释,人家写的代码又好,就不做过多的解释了,😆)
- 我们看下releaseUntil
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
这就是objc源码的好处,注释让人很舒服。 总结下就是:这个就是通过parent寻找,然后next进行- -操作,
不是哨兵的话objc_release
好了,希望对大家有帮助吧。我又要加班了~~😿o(╥﹏╥)o
原文作者:小谷先森
原文地址:https://juejin.cn/post/6899718676585545742