为什么要手动创建autoreleasepool

autorelase的本质就是延迟调用release方法

NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool,GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool

用@autoreleasepool是有用的。
正常情况下,你创建的变量会在超出其作用域的时候被释放掉。
而如果你的函数写的很长,在你函数运行过程中出现很多中间变量,占据了大量的内存,怎么办?
用@autoreleasepool。
在@autoreleasepool中创建的变量,会在@autoreleasepool结束的时候执行一次release,进行释放。其实@autoreleasepool就相当于一层作用域。

1.循环。用autoreleasepool包装循环体,相当于将循环体单独封装成一个函数,每次循环结束就会释放中间变量;
2.图片处理,过于复杂的图片处理,会产生大量中间数据。如:原图->灰度图->裁剪图->blendmode图,如果这些操作写在一个方法里,那么中间产生的图片就要及时释放(当然,大部分情况我们每一步都封装成一个方法,很少用到autoreleasepool)。

三种场合下,你可能要创建自己的自动释放池。

  1. 如果你创建了一个不是基于UI框架的程序,例如命令行工具。
  2. 如果你写了一个创建了很多临时变量的循环。你可能在下次循环之前使用一个自动释放池来释放那些变量。在循环中使用自动释放池来减少应用程序的峰值内存占用。
    3.如果你创建了一个二级线程。

你必须创建你的自动释放池在线程一开始执行的时候,否则,你的应用将会内存泄漏。

使用自动释放池来减少峰值内存占用。

许多程序自动释放的临时对象。这些对象添加到程序的内存直到这个block结束。在大多数情况下,允许临时对象累计直到这个当前循环结束并不会引起内存占用大大占用。然而,在一些情况下,你可能会创建大量的临时对象,从而增加内存空间(这里的累计更多的是来不及释放)。你想要快速释放它们。在后边这种情况下,你可以创建自己的自动释放池。在自动释放池的最后,临时对象被release,这通常导致它们释放减少了内存占用。

下边的例子显示在for循环中,你怎样利用自动释放池。

NSArray *urls = <# An array of file URLs #>;

for (NSURL *url in urls) {
	@autoreleasepool {
		NSError *error;
		NSString *fileContents = [NSString stringWithContentsOfURL:url
			encoding:NSUTF8StringEncoding error:&error];
	/* Process the string, creating and autoreleasing more objects. */
	}
}

这个for循环移除处理一个文件夹。在block最后,自动释放池中的所有对象都会发送一条autorelease消息。

注意:用类方法创建的对象,一般会把这个对象放入最近的自动释放池中。这就导致for循环中大量的对象放入了自动释放池中。导致局部出现了内存释放。对于这种情况最好不要等到让系统的自动释放池一次释放,需要自己创建一个自动释放池,这样的话我们就可以把局部变量提前释放了。

经过一个自动释放池之后,你应该把autoreleased的所有对象都给释放掉。不要发送消息给那个对象或者返回它给你的方法的调用者。如果你必须用一个超出自动释放池的临时变量,你可以发送一条retain消息给这个对象,然后在自动释放池之后给他发送一条autorelease消息,像下边例子一样

自动释放池三种使用情形

面试题程序中有几个runloop?

主线程中有一个mainrunloop,子线程如果获取了runloop就会有runloop,如果没有就没有runloop

再次说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)

[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。

RunLoop和线程是息息相关的,我们知道线程的作用是用来执行特定的一个或多个任务,但是在默认情况下,线程执行完之后就会退出,就不能再执行任务了。这时我们就需要采用一种方式来让线程能够处理任务,并不退出。所以,我们就有了RunLoop。

RunLoop


    for (int i = 0; i < 10; i++) {
        for (int j = 1; j < 1001; j++) {
            NSInteger resoule = 0;
            date = [NSDate date];
            NSTimeInterval useTime = [[NSDate date]timeIntervalSinceDate:date];
            totalTime += useTime;
            maxTime = maxTime > useTime?  maxTime:useTime;
            minTime = minTime < useTime?  minTime:useTime;
            sucessCase++;
            totalCase++;
            NSLog(@"--test:%ld %ld",(long)resoule, totalCase);
        }
    }

这个代码细看并没有什么不对劲的地方,也很难找到会发生内存泄露的地方,但是也是写代码时最容易出现的code。在ARC模式下,所有的变量在每次的for循环之后都应该是释放了的呀,到底是哪里出现了内存上涨么呢?ARC是自动释放内存,但只是在恰当的时候释放掉内存!那难道是我们觉得应该释放的时候,此时并不是这个"恰当"的时机,内存并没有释放么?使用自动释放池autoreleasepool,内存不再上涨了:


    for (int i = 0; i < 10; i++) {
        for (int j = 1; j < 1001; j++) {
        @autoreleasepool{
            NSInteger resoule = 0;
            date = [NSDate date];
            NSTimeInterval useTime = [[NSDate date]timeIntervalSinceDate:date];
            totalTime += useTime;
            maxTime = maxTime > useTime?  maxTime:useTime;
            minTime = minTime < useTime?  minTime:useTime;
            sucessCase++;
            totalCase++;
            NSLog(@"--test:%ld %ld",(long)resoule, totalCase);
            }
        }
    }

内存老实了很多嘛,后来在网上查到的是这个:该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,在循环中创建自己的autoReleasePool,能够及时释放占用内存大的临时变量,减少内存占用峰值

  • main函数中的autoreleasepool的作用?
  • 系统的autoreleasepool我们自己创建的autoreleasepool释放时机差别在哪?
  • 在ARC的环境中, 什么情况下需要使用autoreleasepool? 不使用autoreleasepool变量什么时候会被释放?

对于autoreleasepool释放时机

for (int i = 0; i < 10e5 * 2; i++) { 
	@autoreleasepool {
		 NSString *str = [NSString stringWithFormat:@"hi + %d", i]; 
	} 
}
 NSLog(@"finished!");

同样是上面一段函数, 在for循环中加入autoreleasepool:

为临时变量分配的内存已经得到平稳的释放, 所以结论就是最上面我们看到的认知? 其实本身每个Runloop已经默认会创建一个autoreleasepool了, 所以我们这里添加相当于嵌套(便于理解)了一个, 并没有弄清楚autoreleasepool自身的释放时机. 下面做另外一个小测试:
这一次在代码中新增对Runloop的Observer, 及时获取Runloop的状态变化确认释放时机, 代码如下:
// 添加一个监听者

- (void)addRunLoopObserver {

    // 1. 创建监听者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"进入RunLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理Timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"退出RunLoop");
                break;
            default:
                break;
        }
    });

    // 2. 添加监听者
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

另外上面的方法运行连续运行两次, 不手动添加autoreleasepool, 大概是这样:

- (void)test1 {

    NSLog(@"test1 begin!");
    for (int i = 0; i < 10e5 * 2; i++) {
        //@autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hi + %d", i];
        //}
    }
    NSLog(@"test1 finished!");
}

- (void)test2 {

    NSLog(@"test2 begin!");
    for (int i = 0; i < 10e5 * 2; i++) {
        //@autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hi + %d", i];
        //}
    }
    NSLog(@"test2 finished!");
}

很清楚的看到Runloop没有完成一次循环之前所有内存都未释放, 即使局部变量出了作用域也必须等待Runloop循环完成.

下面同样, 手动添加autoreleasepool观察释放时机.
结果是意外也合理的. 即使Runloop未完成循环, 内存也即使释放了

@autoreleasepool{}
等价于
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

每次出了{}时objc_autoreleasePoolPop()就被调用, 所以直接释放掉了. 当然, 系统自动创建的autoreleasepool也是一样, 只是调用的时机不同: 线程与Runloop是一一对应, Runloop与系统创建的autoreleasepool也是一一对应, 所以不论是Runloop完成了一次循环还是线程被关闭时, autoreleasepool都会释放, 当然手动添加的也会被管理, 上面为了方便理解, 说的是嵌套, 本质上是没有嵌套这个说法的, 对@autoreleasepool{}本质的一些个人总结:
主要就是一个类:AutoreleasePoolPage
两个函数: objc_autoreleasePoolPush()、objc_autoreleasePoolPop()
运作方式: autoreleasepool由若干个autoreleasePoolPage类以双向链表的形式组合而成, 当程序运行到@autoreleasepool{时, objc_autoreleasePoolPush()将被调用, runtime会向当前的AutoreleasePoolPage中添加一个nil对象作为哨兵,
在{}中创建的对象会被依次记录到AutoreleasePoolPage的栈顶指针,
当运行完@autoreleasepool{}时, objc_autoreleasePoolPop(哨兵)将被调用, runtime就会向AutoreleasePoolPage中记录的对象发送release消息直到哨兵的位置, 即完成了一次完整的运作

回到最初的问题, main函数中的autoreleasepool的作用, 我翻阅了大量资料, 在StackOverflow上赞的比较高的回答是没卵用… 暂且只能先这样认为了… 希望有了解的同学可以讲解一下~
在实际中的使用场景其实很明确了, 在程序中中有大量临时变量的时候最好手动创建.
最常出现大量变量的时候显然是循环/遍历, 我们常用的for循环, 以及enumerate其实跟autoreleasepool也有关, for循环是不自动创建autoreleasepool的, 而enumerate中已经自动创建了autoreleasepool, 值得注意的是高并发enumerate常常会出一些意外的问题, 例如对象被提前释放, 所以建议高并发情况下使用for循环(性能高于enumerate), 再手动添加autoreleasepool

在ARC环境中autoreleasepool(runloop)的研究

使用自动释放池的示例:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

一个非常人为的例子,当然,如果你没有在外部for循环中使用@autoreleasepool,那么你将在以后释放100000000个对象,而不是每次绕过外部for循环10000

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值