前言
内存管理一直是Objective-C
的重点,在MRC
环境下,通过调用[obj autorelease]
来延迟内存的释放,在现在ARC环境下,我们都知道编译器会在合适的地方插入release/autorelease
内存释放语句,我们甚至可以不需要知道Autorelease
就能很好的管理内存。虽然现在已经几乎用不到MRC
,但是了解 Objective-C
的内存管理机制仍然是十分必要的,看看编译器帮助我们怎么来管理内存。本文仅仅是记录自己的学习笔记。
AutoreleasePool简介
1.什么是AutoreleasePool
AutoreleasePool
:自动释放池是 Objective-C
开发中的一种自动内存回收管理的机制,为了替代开发人员手动管理内存,实质上是使用编译器在适当的位置插入release
、autorelease
等内存释放操作。当对象调用 autorelease
方法后会被放到自动释放池中延迟释放时机,当缓存池需要清除dealloc
时,会向这些 Autoreleased
对象做 release
释放操作。
2.对象什么时候释放(ARC规则)
一般的说法是对象会在当前作用域大括号结束时释放,
有这样一个ARC
环境下简单的例子🌰:首先创建一个ZHPerson
类:
//// ZHPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZHPerson : NSObject
+(instancetype)object;
@end
////ZHPerson.m
#import "ZHPerson.h"
@implementation ZHPerson
-(void)dealloc
{
NSLog(@"ZHPerson dealloc");
}
+(instancetype)object
{
return [[ZHPerson alloc] init];
}
@end
然后在ViewController.m
导入头文件ZHPerson.h
,然后在写一段这样的代码:
__weak id temp = nil;
{
ZHPerson *person = [[ZHPerson alloc] init];
temp = person;
}
NSLog(@"temp = %@",temp);
解释一下这个代码:先声明了一个 __weak
变量temp
,因为 __weak
变量有一个特性就是它不会影响所指向对象的生命周期,然后让变量temp
指向创建的person
对象,输出如下:
这里超出了
person
的作用域,它就被释放了,看来是正常的。
把上面的创建对象的方法,变一变写法:
__weak id temp = nil;
{
ZHPerson *person = [ZHPerson object];
temp = person;
}
NSLog(@"temp = %@",temp);
输出如下:
这里
person
对象超出了其作用域还是存在的,被延迟释放了,也就是说其内部调用了autorelease
方法。
小总结:
查询得知:以 alloc
, copy
, ,mutableCopy
和new
这些方法会被默认标记为 __attribute((ns_returns_retained))
,以这些方法创建的对象,编译器在会在调用方法外围要加上内存管理代码retain/release
,所以其在作用域结束的时候就会释放,而不以这些关键字开头的方法,会被默认标记为__attribute((ns_returns_not_retained))
,编译器会在方法内部自动加上autorelease
方法,这时创建的对象就会被注册到自动释放池中,同时其释放会延迟,等到自动释放池销毁的时候才释放。
3.AutoreleasePool的显示创建
1.MRC下的创建
//1.生成一个NSAutoreleasePool对象
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//2.创建对象
id object = [[NSObject alloc] init];
//3.对象调用autorelease方法
[object autorelease];
//4.废弃NSAutoreleasePool对象,会对释放池中的object发送release消息
[pool drain];
2.ARC下的创建
@autoreleasepool {
//LLVM会在内部插入autorelease方法
id object = [[NSObject alloc] init];
}
AutoreleasePool
的作用前面有提到过,每当一个对象调用 autorelease
方法时,实际上是将该对象放入当前 AutoreleasePool
中,当前 AutoreleasePool
释放时,会对添加进该 AutoreleasePool
中的对象逐一调用 release
方法。在ARC
环境下,并不需要特别的去关注Autoreleasepool
的使用,因为系统已经做了处理。
AutoreleasePool探索学习
为了看一下AutoreleasePool
到底做了什么,先来创建一个main.m
文件(Xcode
-> File
-> New Project
-> macOS
-> Command Line Tool
-> main.m
);
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
然后,使用编译器clang
编译main.m
转化成main.cpp
文件(在终端使用命令:clang -rewrite-objc main.m
),滑到main.cpp
文件的最后,有这样一段代码:
这个代码是把
@autoreleasePool
转换成一个__AtAutoreleasePool
类型的局部私有变量__AtAutoreleasePool __autoreleasepool
;
接着在 main.cpp
文件中查询__AtAutoreleasePool
,来看一下它具体的实现:
可以看到
__AtAutoreleasePool
是结构体类型,并且实现了两个函数:构造函数__AtAutoreleasePool()
和析构函数~__AtAutoreleasePool()
。
也就是说在声明 __autoreleasepool
变量时,构造函数 __AtAutoreleasePool()
被调用,即执行 atautoreleasepoolobj = objc_autoreleasePoolPush();
;当出了当前作用域时,析构函数 ~__AtAutoreleasePool()
被调用,即执行 objc_autoreleasePoolPop(atautoreleasepoolobj);
那么上面的main.m
中的代码可以用这种形式代替:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
// @autoreleasepool
{
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
// insert code here...
NSLog(@"Hello, World!");
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
接下来看一下析构函数和构造函数分别实现了什么内容?这里需要一份objc_runtime
的源码(源码地址),这里使用的是objc4-756.2.tar.gz
:
这里两个函数本质上就是分别调用了
AutoreleasePoolPage
的push
方法和pop
方法(这里::是C++
调用方法的形式,类似于点语法)。
1.AutoreleasePoolPage
AutoreleasePoolPage
是一个C++
实现的类,查看它的具体实现代码是:
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
# define POOL_BOUNDARY nil //哨兵对象(可以看做是一个边界)
...部分代码省略
可以发现,AutoreleasePoolPage
是继承自AutoreleasePoolPageData
的,紧接着查看AutoreleasePoolPageData
的定义:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; //用来校验 `AutoreleasePoolPage`的结构是否完整;16
id *next; //指向最新添加的 `autoreleased` 对象的下一个位置,初始化时指向 `begin()` ;8
pthread_t const thread; //指向当前线程;8
AutoreleasePoolPage * const parent; //指向父结点,第一个结点的 `parent` 值为 `nil` ;8
AutoreleasePoolPage *child; //指向子结点,最后一个结点的 `child` 值为 `nil` ;8
uint32_t const depth; //代表深度,从 `0` 开始,往后递增 `1`;4
uint32_t hiwat; //代表 `high water mark` ; 4
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)
{
}
};
通过AutoreleasePoolPageData
定义发现,其结构体内部有AutoreleasePoolPage
,所以存在这样一种关系AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage
,
通过源码可以知道这是一个典型的双向列表结构,所以
AutoreleasePool
是由若干个AutoreleasePoolPage
以双向链表的形式组合而成。
通过AutoreleasePoolPageData
源码可知,AutoreleasePoolPageData
结构体的大小为56字节,
AutoreleasePoolPage
每个对象会开辟4096字节内存(虚拟内存一页的大小),除了上面的实例变量(结构体的大小)所占空间,剩下的空间全部用来储存autorelease
对象的地址,(注意,AutoreleasePoolPage
的第一页会包含哨兵对象,哨兵对象占位8字节,现每个加入的对象为8字节,那么第一页最多可以存储504个对象,从第二页开始最多可以存储505个对象。)AutoreleasepoolPage
通过压栈的方式来存储每个autorelease
的对象(从低地址到高地址)。其中next
指针作为游标指向栈顶最新add
进来的autorelease
对象的下一个位置,当 next
指针指向begin
时,表示 AutoreleasePoolPage
为空;当 next
指针指向end
时,表示 AutoreleasePoolPage
已满,此时会新建一个AutoreleasePoolPage
对象,连接链表,后来的autorelease
对象在新的AutoreleasePoolPage
插入,同样的新AutoreleasePoolPage
的next
指针被初始化在栈底(指向begin
的位置)。
2.AutoreleasePoolPage::push()
既然已经知道了autorelease
的对象会通过压栈的方式插入到AutoreleasePoolPage
当中,那么显然AutoreleasePoolPage
的push
方法就承包了AutoreleasePoolPage
的创建和插入。
接着看下push
方法的源码:
static inline void *push()
{
id *dest;
//判断是否已经初始化AutoreleasePoolPage
if (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;
}
这里的POOL_BOUNDARY
可以理解为哨兵对象,或者理解为一种边界标识,而且这个POOL_BOUNDARY
值为0,是个nil
。
在这里理解为,判断是否有poolpage,如果没有则调用autoreleaseNewPage
方法进行创建,如果存在,则直接压入哨兵对象。
进入autoreleaseNewPage
方法:
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
//获取当前hotpage
AutoreleasePoolPage *page = hotPage();
//判断当前页是否存在,如果存在,则压栈对象
if (page) return autoreleaseFullPage(obj, page);
//如果不存在,则创建page
else return autoreleaseNoPage(obj);
}
接下来再看autoreleaseFullPage
和autoreleaseNoPage
两个方法的实现:
static __attribute__((noinline))
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);
//遍历循环查找page是否已满
do {
//如果子页面存在,则将页面替换为子页面
if (page->child) page = page->child;
//如果子页面不存在,则创建建页面
else page = new AutoreleasePoolPage(page);
} while (page->full());
//设置为当前hotPage
setHotPage(page);
//对象压栈
return page->add(obj);
}
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
//判断是否是空占位符,如果是,则压栈哨兵标识符置为YES
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
//如果对象不是哨兵对象,且没有Pool,则报错
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
//如果对象是哨兵对象,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();//设置空的占位符
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
//初始化第一页
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//设置page为当前hotpage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
//压栈哨兵的标识符为YES,则压栈哨兵对象
if (pushExtraBoundary) {
//压栈哨兵
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
//压栈对象
return page->add(obj);
}
接下来,回到push
方法的源码,再来看一下autoreleaseFast
这个方法,
static inline id *autoreleaseFast(id obj)
{
//获取到当前page,这个hotPage是从当前线程的局部私有空间取出来的
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
我们知道链表是有空间的,所以上面👆的源码可以理解为:
(1). 当前page
存在且没有满时,直接将对象添加到当前page
中,即next
指向的位置;
(2). 当前page
存在并且已满时,创建一个新的page
,并将对象添加到新创建的page
中,然后将这两个链表节点进行链接。
(3). 当前page
不存在时,创建第一个page
,并将对象添加到新创建的page
中。
这里重点看一下page->add(obj)
这个方法,
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
可以看到这里返回的ret
其实next
指针指向的地址,由上面的push
方法的源码可知,这里page->add(obj)
传入的obj
其实就是POOL_BOUNDARY
,也就是说调用push
方法,会插入一个POOL_BOUNDARY
。
3.autorelease
通过上面的分析已经知道了构造方法objc_autoreleasePoolPush
会创建AutoreleasePoolPage
,并插入哨兵对象POOL_BOUNDARY
,那么对象通过调用autorelease
是怎么插入到AutoreleasePoolPage
的呢?下面来看下autorelease
的源码实现:
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
//如果不是对象,直接返回
if (!obj) return obj;
//如果是小对象,直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
再进入到obj->autorelease()方法来看,
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
然后进入rootAutorelease()方法:
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
然后再进入rootAutorelease2()方法:
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
此时会调用AutoreleasePoolPage
的autorelease()
方法,此处传入的this
就是要压栈的对象。那么再进入到AutoreleasePoolPage
的autorelease()
方法:
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;
}
会发现来到了autoreleaseFast(obj);
方法,这里的重点还是autoreleaseFast(obj)
;由于这里插入对象的方法和AutoreleasePoolPage
调用push
方法实现哨兵对象的插入是一样的,只不过push
操作插入的是一个 POOL_BOUNDARY
,而autorelease
操作插入的是一个具体的autoreleased
对象,在此处就不做多余分析。
通过上面👆的这些分析,已经大概知道AutoreleasePool
是怎样的一个构造,以及内部是如何实现压栈哨兵对象以及压栈对象的。
4.AutoreleasePoolPage::pop(ctxt)
通过上面对构造函数objc_autoreleasePoolPush
的学习,已经知道objc_autoreleasePoolPush
返回的是哨兵对象的地址,那么在调用析构函数objc_autoreleasePoolPop
的时候传入的也就是这个哨兵对象的地址。随着方法的一步步调用,紧接着来看下AutoreleasePoolPage
的pop
方法的实现:
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
if (hotPage()) {
pop(coldPage()->begin());
} else {
setHotPage(nil);
}
return;
}
page = pageForPointer(token); //根据传入的哨兵对象的地址,获取到page中的哨兵对象之后的地址空间
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
} else {
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop); //对当前链表当中的对象进行release操作
if (DebugPoolAllocation && page->empty()) {
//释放 `Autoreleased` 对象后,销毁多余的 page
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
page->kill();
setHotPage(nil);
}
else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
这里重点看一下page->releaseUntil(stop)
方法:
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
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);
}
这里的stop
同样是POOL_BOUNDARY
的地址,这里分析一下这个方法:
(1). 外部循环挨个遍历autoreleased
对象,直到遍历到哨兵对象POOL_BOUNDARY
。
(2). 如果当前page
没有 POOL_BOUNDARY
,并且为空,则将hotPage
设置为当前page
的父节点。
(3). 给当前autoreleased
对象发送release
消息。
(4). 最后再次配置hotPage
。
5.AutoreleasePool的嵌套
对于嵌套的AutoreleasePool
也是同样的原理,在pop
的时候总会释放对象到上次push
的位置为止,也就是哨兵位置,多层的pool
就是插入多个哨兵对象而已,然后根据哨兵对象来进行释放,就像剥洋葱一样一层一层的,互不影响。
那么这里有个疑问,如果在AutoreleasePool
多层嵌套中是同一个对象呢,那么会怎么释放?下面通过一个小例子🌰来看一下:
@autoreleasepool {
ZHPerson *person = [ZHPerson object];
NSLog(@"current count %d",_objc_rootRetainCount(person));
@autoreleasepool {
ZHPerson *person1 = person;
NSLog(@"current count %d",_objc_rootRetainCount(person));
@autoreleasepool {
ZHPerson *person2 = person;
NSLog(@"current count %d",_objc_rootRetainCount(person));
}
}
}
打印结果如下:
这里
dealloc
方法只调用了一次,由上面的代码可知:当前person1
和person2
是对person
的引用,如果系统会为每一次引用都自动插入一个autorelease
,那么对象在执行第一个autorelease
的时候,会调用objc_release(obj)
来释放当前的对象,那么当调用rootRelease()
的时候就会报错,因为当前对象已经被释放了,那么也就是说对于引用的对象只会被释放一次。(同一个对象不能够反复的autorelease
)
NSthread、NSRunLoop、AutoReleasePool
1.NSthread和AutoReleasePool
先来看个简单的例子:
在
temp
的位置设置一个断点,然后在控制台输入watchpoint set variable temp
,
等到这个线程执行结束之后,来看一下左侧边栏的内容:
当执行到
NSLog(@"thread end");
这句代码,表示线程执行结束,这里,其实线程会先调用[NSthread exit]
,然后执行_pthread_tsd_cleanup
,清除当前线程的有关资源,然后调用tls_dealloc
,也就是把当前线程关联的AutoReleasePool
释放掉,最后调用weak_clear_no_lock
清除指针。
那么这一系列过程就说明了:在NSThread
退出了之后,与NSThread
对应的AutoReleasePool
也会被自动清空,所以当一个线程结束的时候,就会回收♻️AutoReleasePool
中自动释放的对象。
结论:
每一个线程都会维护自己的
AutoReleasePool
,而每一个AutoReleasePool
都会对应唯一一个线程,但是线程可以对应多个AutoReleasePool
。
2.NSRunLoop和AutoReleasePool
对于NSThread
只是一个简单的线程,如果把它换成一个常驻线程呢?
这里创建一个
NSTimer
,并将其常驻。利用同样的方式,watchpoint set variable temp
,:
可以看到这里
NStimer
是被加入到子线程当中的,但是在子线程中,我们并没有去写关于AutoReleasePool
的内容,我们只知道test
做了autorelease
操作。下面回到源码中来看:
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;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
id *autoreleaseNoPage(id obj)
{
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
}
//这里省略了部分代码
所以从上面的源码我们可以得出结论:子线程在使用autorelease
对象的时候,会懒加载出来一个AutoreleasePoolPage
,然后将对象插入进去。
那么问题又来了,autorelease
对象在什么时候释放的呢?也就说AutoreleasePoolPage
在什么时候调用了pop
方法?
其实在上面创建一个NSThread
的时候,在调用[NSthread exit]
的时候,会释放当前资源,也就是把当前线程关联的autoReleasePool
释放掉,而在这里当RunLoop
执行完成退出的时候,也会执行pop
方法,这就说明了为什么在子线程当中,我们没有显示的调用pop
,它也能释放当前AutoreleasePool
的资源的原因。
3.主线程的NSRunLoop和AutoReleasePool
那么在主线程的RunLoop
到底什么时候把对象进行释放回收的呢?
简单粗暴点,直接在控制台通过po [NSRunloop currentRunloop]
打印主线程的RunLoop
:
这里,系统在主线程的RunLoop
里注册了两个Observer
,回调都是_wrapRunLoopWithAutoreleasePoolHandler
,第一个Observer
的状态是activities = 0x1
,第二个Observer
的状态是activities = 0xa0
,这两种状态代表什么意思呢?
先在这里插入一点RunLoop
的内容(RunLoop
的状态枚举):
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), // 64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
0x1
代表的是kCFRunLoopEntry
,也就是说第一个 Observer
监视的事件是Entry
(即将进入Loop
时),其回调内会调用_objc_autoreleasePoolPush()
创建一个自动释放池。其order
优先级是-2147483647,优先级最高,保证创建自动释放池发生在其他所有回调之前。
0xa0
对应的是kCFRunLoopBeforeWaiting
和kCFRunLoopExit
,也就是说第二个Observer
监视了两个事件:kCFRunLoopBeforeWaiting
准备进入休眠,kCFRunLoopExit
即将退出RunLoop
。在kCFRunLoopBeforeWaiting
事件时调用 _objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的自动释放池并创建新的自动释放池;在kCFRunLoopExit
事件时调用_objc_autoreleasePoolPop()
来释放自动释放池,同时这个Observer
的order
优先级是 2147483647,优先级最低,保证其释放自动释放池的操作发生在其他所有回调之后。
所以在没有手动增加AutoreleasePool
的情况下,Autorelease
对象都是在当前的runloop
迭代结束时释放的,而它能够释放的原因是系统在每个runloop
迭代中都加入了自动释放池push
和pop
操作。
总结:
对于不同线程,应当创建自己的
AutoReleasePool
。如果应用长期存在,应该定期drain
和创建新的AutoReleasePool
,AutoReleasePool
与RunLoop
与线程是一一对应的关系,AutoReleasePool
在RunLoop
在开始迭代时做push
操作,在RunLoop
休眠或者迭代结束时做pop
操作。
AutoreleasePool的应用场景
通常情况下我们是不需要手动创建AutoreleasePool
,但是也有一些特殊的:
-
编写的程序不基于
UI
框架,如命令行程序。 -
在循环中创建大量临时对象时用以降低内存占用峰值。
-
在主线程之外创建新的线程,在新线程开始执行处,创建自己的
AutoreleasePool
,否则将导致内存泄漏。
下面就来简单看下第二种情况,直接来个for
循环:
for (int i = 0; i < 100000000; i ++) {
NSString * str = [NSString stringWithFormat:@"noAutoReleasePool"];
NSString *tempstr = str;
}
}
来看一下Memory
的使用情况:
相反的,如果加上AutoreleasePool
,来看一下:
for (int i = 0; i < 100000000; i ++) {
@autoreleasepool {
NSString * str = [NSString stringWithFormat:@"AutoReleasePool"];
NSString *tempstr = str;
}
}
来看一下这种情况下的Memory
的使用情况:
这个对比伤害就很明显了。
这个做个备注:在主函数main.m
文件中的@autoreleasepool
,如果在这里做个测试,使用for
循环创建大量的临时对象,是否加上这个@autoreleasepool
对Memory
的使用情况没有特别大的影响。
总结
写到这里,对于AutoReleasePool
学习内容就暂告一段了,正常情况下,我们不需要去关心AutoReleasePool
的创建和释放,但是学习理解了AutoReleasePool
能够使我们更加理解ARC
模式下系统是怎样来管理内存的。
文中内容如有不当之处,还请指出,谢谢您!