(一)Autoreleasepool简介
AutoreleasePool
:自动释放池是oc提供的一种自动回收的机制,具有延迟释放的特性,即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop
结束或者作用域超出{}或者超出[pool release]
之后再被释放。
(二)alloc /init/new/copy/mutableCopy 返回的对象都不是autorelease对象
void createString(void) {
NSString *taggedPointerStr = [[NSString alloc] initWithFormat:@"Hello"];//创建TaggedPointer对象
NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];//创建常规对象
NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];//创建Autorelease对象
weak_TaggedPointerStr = taggedPointerStr;
weak_allocString = string;
weak_StringAutorelease = stringAutorelease;
NSLog(@"------in the createString()------");
NSLog(@"%@", weak_TaggedPointerStr);
NSLog(@"%@", weak_allocString);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.navigationItem.title = @"AotoReasePool";
self.view.backgroundColor = [UIColor whiteColor];
@autoreleasepool {
createString();
NSLog(@"------in the autoreleasepool------");
NSLog(@"%@", weak_TaggedPointerStr);
NSLog(@"%@", weak_allocString);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
NSLog(@"------in the main()------");
NSLog(@"%@", weak_TaggedPointerStr);
NSLog(@"%@", weak_allocString);
NSLog(@"%@", weak_StringAutorelease);
}
输出:
------in the createString()------
Hello
Hello, World!
Hello, World! Autorelease
------in the autoreleasepool------
Hello
(null)
Hello, World! Autorelease
------in the main()------
Hello
(null)
(null)
由上面的输出可以看出由alloc
的Hello
一直存在,而由alloc
的Hello, World!
创建的对象出了当前作用域以后就释放了。而有stringWithFormat
创建Autorelease
对象会延迟释放。这是为什么呢?
1.init/alloc/new/copy/mutableCopy创建的对象不是autorelease对象, 只作用在当前作用域。
2.当字符小于等于8个字符的时候,系统会对其进行优化为Tagged Pointer对象。Tagged Pointer通过在其最后一个bit位设置一个特殊标记,用于将数据直接保存在指针本身中。Tagged Pointer并不是真正的对象,使用时需要注意不要直接访问其isa变量。
(三) AutoreleasePoolPage的结构:
在了解如何实现之前,我们先了解一下什么是AutoreleasePoolPage
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
AutoreleasePoolPage
它是一个结构体
magic
:用来校验 AutoreleasePoolPage
的结构是否完整
next
:指向栈顶,也就是最新入栈的autorelease
对象的下一个位置
thread
:指向当前线程
parent
:指向父节点
child
:指向子节点
depth
:表示链表的深度,也就是链表节点的个数
hiwat
:表示high water mark(最高水位标记),记录了入栈对象最多时候对象的个数。
一个线程的
autoreleasepool
就是一个指针栈,我们每次入栈之前都会添加一个POOL_BOUNDARY
做为标记,然后添加对象。当autoreleasepool
进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release
。 这个栈是由一个以page
为节点双向链表组成,page
根据需求进行增减。autoreleasepool
对应的线程存储了指向最新page
。 画张图理解一下:
(四) AutoreleasePool的底层实现:
-
如何添加对象
/* @autoreleasepool */ __AtAutoreleasePool __autoreleasepool;
@autoreleasepool
在底层会声明一个__AtAutoreleasePool
类型的局部变量__autoreleasepool
,那么我们来看看__AtAutoreleasePool
的定义呢?
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
这是一个语法糖,构造函数在局部变量的开始调用,析构函数在作用域结束的时候调用。
那我们来看一下objc
源码中这两个函数
void *
_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
//每一个释放池开始的时候都会创建一个新的page
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//快速的添加哨兵对象nil
dest = autoreleaseFast(POOL_BOUNDARY);//哨兵对象
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
我们来看一下,是如何添加对象的autoreleaseFast(POOL_BOUNDARY)
,POOL_BOUNDARY
。我们称之为哨兵对象,查看它的宏,是一个nil
。
# define POOL_BOUNDARY nil
static inline id *autoreleaseFast(id obj)
{
//链表是有空间的
AutoreleasePoolPage *page = hotPage();//回去最新的page
if (page && !page->full()) {//1.存在page,并且没有存满,直接添加obj
return page->add(obj);
} else if (page) {//有page,但是满了,创建新的page,添加obj,child指针指向新创建page
return autoreleaseFullPage(obj, page);
} else {//没有page,创建一个page,并添加obj
return autoreleaseNoPage(obj);
}
}
如何向链表中添加objc
,调用add(obj)
这个函数
id *add(id obj)//像当前链表插入objc
{
ASSERT(!full());
unprotect();//解除保护
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;// 将obj入栈到栈顶并重新定位栈顶
protect();
return ret;
}
首先获取当前
page
没有就创建一个,然后添加一个哨兵对象POOL_BOUNDARY
,之后在添加对象,将next
指针指向最后一个位置。
-
对象是如何释放的
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
static inline void
pop(void *token)//token指针指向栈顶的地址
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
} else {
page = pageForPointer(token);// 通过栈顶的地址找到对应的page
}
//部分代码省略
return popPage<false>(token, page, stop);
}
拿到当前的page
进行pop
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();// 记录最高水位标记
page->releaseUntil(stop);// 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象
// memory: delete empty children
// 删除空掉的节点
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
page->releaseUntil(stop)
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
}
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);//
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
autorelease
函数和push
函数一样,关键代码都是调用autoreleaseFast
函数向自动释放池的链表栈中添加一个对象,不过push
函数的入栈的是一个哨兵对象,而autorelease
函数入栈的是需要加入autoreleasepool
的对象。
(五)使用场景
1、对象作为函数返回值
+ (instancetype)object{
return [[MMPooLPerson alloc] init];
}
当一个对象要作为函数返回值的时候,因为要遵循谁申请谁释放的思想,所以应该在返回之前释放,但要是返回之前释放了,就会造成野指针错误,但是要是不释放,那么就违背了谁申请谁释放的原则,所以就可以使用autorelease延迟释放的特性,将其在返回之前做一次autorelease
,加入到自动释放池中,保证可以被返回,一次runloop
之后系统会帮我们释放。
2、临时生成大量对象
一定要将自动释放池放在for循环里面,要释放在外面,就会因为大量对象得不到及时释放,而造成内存紧张,最后程序意外退出
for (int i = 0; i<10000; i++) {
@autoreleasepool {
UIImageView *imegeV = [[UIImageView alloc]init];
imegeV.image = [UIImage imageNamed:@"efef"];
[self.view addSubview:imegeV];
}
}
3、创建新的线程
4、长时间在后台运行的任务
总结
@autoreleasepool
系统就是通过@autoreleasepool {}这种方式来为我们创建自动释放池的,一个线程对应一个runloop,系统会为每一个runloop隐式的创建一个自动释放池,所有的autoreleasePool构成一个栈式结构,在每个runloop结束时,当前栈顶的autoreleasePool会被销毁,而且会对其中的每一个对象做一次release(严格来说,是你对这个对象做了几次autorelease就会做几次release,不一定是一次)。
系统方法那个会帮我们自动写入自动释放池
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
自动释放池多层嵌套
嵌套的autoreleasepool:autorelease对象被添加进离他最近的自动释放池,多层的pool会有多个哨兵对象。
@autoreleasepool {//p1被放在该自动释放池里面
Person *p1 = [[Person alloc]init];
@autoreleasepool {//p2被放在该自动释放池里面
Person *p2 = [[Person alloc]init];
}
}
NSThread、NSRunLoop 和 NSAutoreleasePool的关系
1.每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。
2.每一个autoreleasepool
对应一个线程,一个线程会有多个autoreleasepool
3.每一个线程会维护自己的autoreleasepool
,NSRunLoop
对象的每个 event loop
开始前,系统会自动创建一个 autoreleasepool
,并在 event loop
结束时 drain