autorelease简介:
autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作,
使用autorelease有什么好处呢:不用再关心对象释放的时间,不用再关心什么时候调用release
autorelesspool写法:
1. NSAutoreleasePool*autoreleasePool = [[NSAutoreleasePoolalloc] init];
Person *p = [[[Person alloc] init] autorelease];
[autoreleasePool drain]; //池子销毁会给池子中所有对象发送一条release消息,NSAutoreleasePool对象不能retain, 不能autorelease
2. @autoreleasepool{// 创建一个自动释放池
Person *p = [[Personnew] autorelease];// 将代码写到这里就放入了自动释放池
}// 销毁自动释放池(会给池子中所有对象发送一条release消息),在arc下自动管理。
MRC下需要对象调用autorelease才会入池, ARC下可以通过__autoreleasing修饰符, 否则的话看方法名, 非alloc/new/copy/mutableCopy开头的初始化方法编译器都会自动帮我们调用autorelease方法.
autorelease pool堆栈结构
每一个autorelease pool创建都会将池子入栈,给池子发送drain或者release消息,该池子和其后创建的池子都将销毁
例子:
NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];
TestOne*one = [[[TestOnealloc]init]autorelease];
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc]init];
TestTwo *two = [[[TestTwo alloc]init]autorelease];
[pool1 drain];
sleep(5); //阻碍pool的释放,因为函数执行完池子就会被销毁,即使不发送release或drain消息
输出:
TestTwo dealloc
TestOne dealloc
即使只使用了 [pool1 drain];one对象和two对象都能释放
autorelease pool 与 线程
每一个线程(包括主线程)都有一个NSAutoreleasePool栈. 当一个新的池子被创建的时候, push进栈. 当池子被释放内存时, pop出栈. 对象调用autorelease方法进入栈顶的池子中. 当线程结束的时候, 它会自动地销毁掉所有跟它有关联的池子.
autorelease pool 与 RunLoop
程序运行 -> 开启事件循环 -> 发生触摸事件 -> 创建自动释放池 -> 处理触摸事件 -> 事件对象加入自动释放池 -> 一次事件循环结束, 销毁自动释放池.
在开始每一个事件循环之前系统会在主线程创建一个自动释放池, 并且在事件循环结束的时候把前面创建的释放池释放, 回收内存
苹果在主线程 RunLoop 里注册了两个 Observer:
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
NSAutoreleasePool何时释放?
分两种情况:
1.手动加NSAutoreleasePool是在大括号结束释放
2.没手动加时在当前的runloop迭代时结束时候释放
小实验:
@property(nonatomic, weak)NSString *string_weak;
- (void)viewDidLoad {
[super viewDidLoad];
// 场景 1
NSString *string = [NSString stringWithFormat:@"1234567890"];
self.string_weak= string;
NSLog(@"string: %@",self.string_weak);
//场景 2
// @autoreleasepool {
// NSString *string = [NSString stringWithFormat:@"1234567890"];
// _string_weak = string;
// }
// NSLog(@"string: %@",_string_weak);
// 场景 3
// NSString *string = nil;
// @autoreleasepool {
// string = [NSString stringWithFormat:@”1234567890”];
// _string_weak = string;
// }
// NSLog(@"string: %@",self.string_weak);
}
- (void)viewWillAppear:(BOOL)animated {
[superviewWillAppear:animated];
NSLog(@"string: %@", self.string_weak);
}
- (void)viewDidAppear:(BOOL)animated {
[superviewDidAppear:animated];
NSLog(@"string: %@", self.string_weak);
}
输出:
场景一:
string: 1234567890
string: 1234567890
string: (null)
分析:使用stringWithFormat,string对象加入当前runloop的自动释放池,此时string对象的引用为strong和autoreleasepool;当作用域结束后strong引用用消失但autoreleasepool的引用还在,所以在viewWillAppear方法里面string: 1234567890;viewDidAppear方法时autoreleasepool销毁,autoreleasepool的引用消失,此时string销毁为null
场景二:
string: (null)
string: (null)
string: (null)
分析:string在autoreleasepool的作用域结束后释放
场景三:
string: 1234567890
string: (null)
string: (null)
分析:结合上面两个场景可以分析出
autoreleasepool原理:
//将oc代码翻译成c++代码
int main(int argc,char* argv[]) {
@autoreleasepool{
}
}
翻译为:
int main(int argc,char* argv[]){
{
__AtAutoreleasePool __autoreleasepool;
}
} //也就是生成了一个__AtAutoreleasePool类型的__autoreleasepool实例
__AtAutoreleasePool是一个结构体:
struct __AtAutoreleasePool {
__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void* atautoreleasepoolobj;
}; //__autoreleasepool中含有一个atautoreleasepoolobj指针,并且初始化和销毁时分别调用了objc_autoreleasePoolPush()和Pop方法
已经有人在苹果的官方源码里找到了关于AutoreleasePool的底层实现NSObject.mm里,源码地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html
void*objc_autoreleasePoolPush(void){
if(UseGC) returnNULL;//如果使用垃圾回收机制
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void*ctxt){
if(UseGC)return;// fixme rdar://9167170
if(!ctxt)return;
AutoreleasePoolPage::pop(ctxt);
}
//也就是池子的创建和销毁根本上是调用了AutoreleasePoolPage的push和pop方法
AutoreleasePoolPage中存放的是加入池子的oc对象
magic 用来校验 AutoreleasePoolPage 的结构是否完整;
next 栈顶,初始时在哨兵对象的下一个位置,每插入一个元素next都会是这个元素的下一个位置
begain 代表AutoreleasePoolPage的开始位置
end 代表AutoreleasePoolPage的结束位置,每个AutoreleasePoolPage会开辟4096字节内存
thread 指向当前线程;
parent 指向父结点,第一个结点的 parent 值为 nil ;
child 指向子结点,最后一个结点的 child 值为 nil ;
depth 代表深度,从 0 开始,往后递增 1;
hiwat 代表 high water mark 。
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,通过parent和child指针连接成链表,后来的autorelease对象在新的page加入。
上图堆栈由三个 AutoreleasePoolPage 结点组成,第一个 AutoreleasePoolPage 结点为 coldPage() ,最后一个 AutoreleasePoolPage 结点为 hotPage(),总共有两个 POOL_SENTINEL (哨兵)。
首先我来介绍几个概念
1.POOL_SENTINEL(哨兵对象):
在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);
上面的 atautoreleasepoolobj 就是一个 POOL_SENTINEL
2.push 操作
void *objc_autoreleasePoolPush(void){
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
static inline void *push(){
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
一个 push 操作其实就是创建一个新的 autoreleasepool ,对应 AutoreleasePoolPage 的具体实现就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的内存地址,在执行 pop 操作的时候作为函数的入参。
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);
}
}
autoreleaseFast 函数(将对象插入到池子)在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:
.当前 page 存在且没有满时,直接将对象添加到当前 page 中,即 next 指向的位置;
.当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;
.当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。
3.[obj autorelease] 操作
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
id objc_object::rootAutorelease2(){
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
AutoreleasePoolPage 的 autorelease 函数的实现对我们来说就比较容量理解了,它跟pool push 操作的实现非常相似。只不过pool push 操作插入的是一个 POOL_SENTINEL ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。下面是AutoreleasePoolPage::autorelease代码:
static inline id autorelease(id obj){
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || *dest == obj);
return obj;
}
4.pop操作
同理,前面提到的 objc_autoreleasePoolPop(void *) 函数本质上也是调用的 AutoreleasePoolPage 的 pop 函数
void objc_autoreleasePoolPop(void *ctxt){
if (UseGC) return;
// fixme rdar://9167170
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
pop 函数的入参就是 push 函数的返回值,也就是 POOL_SENTINEL 的内存地址,就是pool token 。当执行 pop 操作时,内存地址在 pool token 之后的所有 autoreleased 对象都会被 release 。直到 pool token 所在 page 的 next 指向 pool token 为止。
总结:每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,当需要release时,objc_autoreleasePoolPop(哨兵对象)作为入参,根据传入的哨兵对象地址找到哨兵对象所处的page。在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
代码实战:
对象的autorelease方法会将当前对象加入栈顶的NSAutoreleasePool;NSAutoreleasePool是栈式管理,先入栈的后出栈,对NSAutoreleasePool对象发送release或者drain会销毁栈顶的NSAutoreleasePool对象一直到销毁当前的NSAutoreleasePool对象
子线程的autoreleasepool?
每个线程创建的时候就会创建一个autorelease pool,并且在线程退出的时候,清空autorelease pool。所以子线程的autorelease对象,要么在子线程中设置runloop清除,要么子线程结束时清除
TestObject:
+ (instancetype) instanceWithNumber:(NSInteger)number{
//不加__autoreleasing 会因为arc返回值优化而不是一个autorelease对象,可以去掉试试
__autoreleasing TestObject*object = [[TestObject alloc]init];
return object;
}
-(void)dealloc{
NSLog(@"Test Object dealloc");
}
ViewController:
__weak id _testObject;
NSThread*_testThread;
- (void)viewDidLoad {
[super viewDidLoad];
_testThread = [[NSThread alloc] initWithTarget:self
selector:@selector(testBuild)
object:nil];
[_testThread start];
[self performSelector:@selector(testAlert) onThread:_testThread withObject:nil waitUntilDone:NO];
}
- (void)testBuild{
TestObject*testObject = [TestObjectinstanceWithNumber:10];
_testObject= testObject;
}
- (void)testAlert{
NSLog(@"test alert");
NSLog(@"test alert");
NSLog(@"test alert");
}
如何详细的查看堆栈信息?
在 Debug Navigator 下面有个搜框, 旁边有三个按钮, 把第一个按钮点击一下, 取消选中就可以了.