- iOS内存管理(上)简单的说了下
retain、release和dealloc。不过关于内存管理还有个比较重要的东西autoreleasepool,也是兄弟们常说的自动释放池
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