内存管理(3) - Autorelease Pool(大部分摘抄自black)

前言

NSAutoreleasePool(仅MRC有)

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//作用范围从释放池创建到释放池release
[pool release];

@autoreleasepool(MRC、ARC都有)

@autoreleasepool    //比NSAutoreleasePool高效
{
//作用范围在此
}

这里主要讨论Autorelease Pool


1、简介

Autorelase Pool 提供了一种可以允许你向一个对象延迟发送release消息的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立即被释放掉(例如在一个方法中返回一个对象时),Autorelease Pool 的作用就显现出来了。
所谓的延迟发送release消息指的是,当我们把一个对象标记为autorelease
时:

NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];

这个对象的 retainCount 会+1,但是并不会发生 release。当这段语句所处的 autoreleasepool 进行 drain 操作时,所有标记了 autorelease 的对象的 retainCount 会被 -1。即 release 消息的发送被延迟到 pool 释放的时候了。
autorelease 的对象会加入到最近创建的自动释放池中。


2、runloop和@autoreleasepool

我们的程序在main()调用的时候会自动调用一个autorelease,然后在每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的 Autorelease pool(main()里的autorelease)会被销毁,这样这个pool里的每个Object会被release。


3、@autoreleasepool的好处

在ARC 下,我们并不需要手动调用 autorelease 有关的方法,甚至可以完全不知道 autorelease 的存在,就可以正确管理好内存。因为 Cocoa Touch 的 Runloop 中,每个 runloop circle 中系统都自动加入了 Autorelease Pool 的创建和释放。

当我们需要创建和销毁大量的对象时,使用手动创建的 @autoreleasepool 可以有效的避免内存峰值的出现。因为如果不手动创建的话,外层系统创建的 pool 会在整个 runloop circle 结束之后才进行 drain,手动创建的话,会在 block 结束之后就进行 drain 操作。详情请参考苹果官方文档。一个普遍被使用的例子如下:

for (int i = 0; i < 100000000; i++)
{
    @autoreleasepool
    {
        NSString* string = @"ab c";
        NSArray* array = [string    componentsSeparatedByString:string];
    }
}

如果不使用@ autoreleasepool ,需要在循环结束之后释放 100000000 个字符串,如果 使用的话,则会在每次循环结束的时候都进行 release 操作。


4、main.m 中 Autorelease Pool 的解释

int main(int argc, char * argv[]) {
    @autoreleasepool {
    return UIApplicationMain(argc, argv, nil,   NSStringFromClass([AppDelegate class]));
    }
}

(1)根据苹果官方文档, UIApplicationMain 函数是整个 app 的入口,用来创建 application 对象(单例)和 application delegate。尽管这个函数有返回值,但是实际上却永远不会返回,当按下 Home 键时,app 只是被切换到了后台状态。

(2)同时参考苹果关于 Lifecycle 的官方文档,UIApplication 自己会创建一个 main run loop,我们大致可以得到下面的结论:

main.m 中的 UIApplicationMain 永远不会返回,只有在系统 kill 掉整个 app 时,系统会把应用占用的内存全部释放出来。

因为(1), UIApplicationMain 永远不会返回,这里的 autorelease pool 也就永远不会进入到释放那个阶段

在 (2) 的基础上,假设有些变量真的进入了 main.m 里面这个 pool(没有被更内层的 pool 捕获),那么这些变量实际上就是被泄露的。这个 autorelease pool 等于是把这种泄露情况给隐藏起来了。

UIApplication 自己会创建 main run loop,在 Cocoa 的 runloop 中实际上也是自动包含 autorelease pool 的,因此 main.m 当中的 pool 可以认为是没有必要的。
在基于 AppKit 框架的 Mac OS 开发中, main.m 当中就是不存在 autorelease pool 的,也进一步验证了我们得到的结论。不过因为我们看不到更底层的代码,加上苹果的文档中不建议修改 main.m ,所以我们也没有理由就直接把它删掉


5、在子线程添加Autorelease Pool

主线程中开启runloop,有自动释放池,而使用GCD和NSOperation也会添加自动释放池。

而自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池,或开启runloop。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。

但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。


6、Autorelease Pool 与函数返回值

如果一个函数的返回值是指向一个对象的指针,那么这个对象肯定不能在函数返回之前进行 release,这样调用者在调用这个函数时得到的就是野指针了,在函数返回之后也不能立刻就 release,因为我们不知道调用者是不是 retain 了这个对象,如果我们直接 release 了,可能导致后面在使用这个对象时它已经成为 nil 了。

为了解决这个纠结的问题, Objective-C 中对对象指针的返回值进行了区分,一种叫做 retained return value,另一种叫做 unretained return value。前者表示调用者拥有这个返回值,后者表示调用者不拥有这个返回值,按照“谁拥有谁释放”的原则,对于前者调用者是要负责释放的,对于后者就不需要了。

按照苹果的命名 convention,以 alloc, copy, init, mutableCopy 和 new 这些方法打头的方法,返回的都是 retained return value,例如 [[NSString alloc] initWithFormat:],而其他的则是 unretained return value,例如 [NSString stringWithFormat:]。我们在编写代码时也应该遵守这个 convention。

MRC中两张函数返回值的处理

对于 retained return value,需要负责释放

假设我们有一个 property 定义

@property (nonatomic, retain) NSObject *property;

赋值的时候,我们应该使用:

self.property = [[[NSObject alloc] init] autorelease];

然后在 dealloc 方法中加入:

[_property release];
_property = nil;

对于retained return value,无需理会。

+ (MyCustomClass *) myCustomClass   //类方法
{
    return [[[MyCustomClass alloc] init] autorelease]; // 需要autorelease
}

- (MyCustomClass *) initWithName:(NSString *) name   //实例方法
{
    return [[MyCustomClass alloc] init]; // 不需要 autorelease
}

不建议在IOS中使用autorelease和返回自动释放对象的释放方法

因为它是一种延迟释放。如果程序一直在运行,代码尚未出自动释放池之前,即使有很多对象不再需要了,但它们占用的内存并未完全释放。


ARC中对unretained return value的优化

对于unretained return value,ARC并不一定会使用autorelease

当方法全部基于 ARC 实现时,在方法 return 的时候,ARC 会调用 objc_autoreleaseReturnValue() 以替代 MRC 下的 autorelease。在 MRC 下需要 retain 的位置,ARC 会调用 objc_retainAutoreleasedReturnValue()。因此下面的 ARC 代码:

+ (instancetype)createSark {
    return [self new];
}
// caller
Sark *sark = [Sark createSark];

实际上会被改写成类似这样:

+ (instancetype)createSark {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
}
// caller
id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我们调用retain
Sark *sark = tmp;
objc_storeStrong(&sark, nil); // 相当于代替我们调用了release

objc_autoreleaseReturnValue

id objc_autoreleaseReturnValue(id object) 
{
    if ( //调用者将会执行retain )    //如果发现后面会retain
    {
         set_flag(object);        //设置flag
         return object;           //将这个对象直接返回
    } 
    else
    {
         return [object autorelease];    //flag被设置之后就不autorelease 
    }
}

objc_retainAutoreleasedReturnValue

id objc_retainAutoreleasedReturnValue(id object) 
{ 
    if (get_flag(object))      //检测到flag被设置
    {
        clear_flag(object);     //清楚flag标志
        return object;          //返回这个对象
    } 
    else 
    {
        return [object retain];    //否则对对象retain
    }
}

7、ARC的使用范围仅对可保留指针(ROP)有用

  • 代码块指针
  • oc对象指针
  • 通过_attribute_((NSObject))类型定义的指针

8、ARC的命名规则

  • 不能以new开头,如@property NSString *newString不允许

猜你喜欢

转载自blog.csdn.net/qq_28446287/article/details/80157566