30.@autoreleasepool的使用(面试点:在for循环中创建大量局部变量,会导致大量内存暴增,这个时候用autoreleasepool可以减轻内存增长)

自动释放池block,苹果官方文档:Using Autorelease Pool Blocks

面试经常会有这样的问题:

1.什么是@autoreleasepool?

NSAutoreleasePool实际上是个对象引用计数自动处理器,在官方文档中被称为是一个类。Objective-C的对象(全部继承自NSObject),就是使用引用计数的方法来管理对象的存活,众所周知,当引用计数为0时,对象就被销毁了。操作非常简单,当对象被创建时,引用计数被设成1。可以给对象发送retain消息,让对象对自己的引用计数加1。而当对象接受到release消息时,对象就会对自己的引用计数进行减1,当引用计数到了0,对象就会调用自己的dealloc处理。当对象被加入到NSAutoreleasePool中,会对其对象retain一次,当NSAutoreleasePool结束时,会对其所有对象发送一次release消息。NSAutoreleasePool可以同时有多个,它的组织是个栈,总是存在一个栈顶pool,也就是当前pool,每创建一个pool,就往栈里压一个,改变当前pool为新建的pool,然后,每次给pool发送drain消息,就弹出栈顶的pool,改当前pool为栈里的下一个pool。

2.里面对象的内存什么时候释放?

3.什么时候要用@autoreleasepool?

回答:

1.@autoreleasepool是自动释放池,让我们更自由的管理内存

2.当我们手动创建了一个@autoreleasepool,里面创建了很多临时变量,当@autoreleasepool结束时,里面的内存就会回收

3.ARC时代,系统自动管理自己的autoreleasepool,runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration。

第一个监听的是activities 是 NSRunLoopEntry状态,说明当runloop进入entry状态的时候,会调用_wrapRunLoopWithAutoreleasePoolHandler
,其内部会调用_objc_autoreleasePoolPush()创建自动释放池。
第二个监听的activities是 NSRunLoopBeforeWaiting 和NSRunLoopExit,BeforeWaiting 其回调方法_wrapRunLoopWithAutoreleasePoolHandler内部会调用先调用pop操作,然后再push 创建一个新的自动释放池。Exit会调用pop操作。

autoreleasepool的执行顺序就是Entry-->push ---> BeforeWaiting--->pop-->push -->Exit-->pop,按照这样的顺便,保证了,每一次push都对应一个pop。autoreleasepool释放操作在每一次runloop 的BeforeWaiting和exit的时候执行的

AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入

方便是方便了,但是有些情况下,我们还是需要手动创建自动释放池,那么,什么时候呢?

1,如果你正在编写不基于UI 框架的程序,比如命令行工具。

2,如果你编写的循环创建了很多临时对象。

3,你可以在循环中使用自动释放池block,在下次迭代前处理这些对象。在循环中使用自动释放池block,有助于减少应用程序的内存占用。

4,你生成了一个辅助线程。

一旦线程开始执行你必须自己创建自动释放池。否则,应用将泄漏对象

这是苹果文档中的翻译,按我的理解,最重要的使用场景,应该是有大量中间临时变量产生时,避免内存使用峰值过高,及时释放内存的场景。

举个例子

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding
                                         error:&error];
    }
}

这个for循环里如果不使用@autoreleasepool,那临时变量内存可能是爆发式的,但是使用了@autoreleasepool,在每个@autoreleasepool结束时,里面的临时变量都会回收,内存使用更加合理。

今天在学习大佬博客的时候看到一个问题,下面代码会有什么问题?

// largeNumber是一个很大的数
for (int i = 0; i < largeNumber; i++) {
    NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
    str = [str stringByAppendingString:@" - world"];
    NSLog(@"%@", str);
}

刚开始没看出什么问题,就是普通的循环,每次循环创建一个局部变量NSString。于是写了个Demo验证了下,在观察内存的时候发现了端倪,在循环过程中,内存不断飙升。

image.png

顿时明白了,原来问题的关键就是这个largeNumber,当循环此时很大时,就会创建大量的局部变量,而且得不到释放,于是内存就爆了。这时候就该@autoreleasepool上场了,优化后代码:

for (int i = 0; i < largeNumber; i++) {
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
        str = [str stringByAppendingString:@" - world"];
        NSLog(@"%@", str);
    }
}

查看运行内存,可以看到非常平稳。

image.png

你可能感兴趣的:(30.@autoreleasepool的使用(面试点:在for循环中创建大量局部变量,会导致大量内存暴增,这个时候用autoreleasepool可以减轻内存增长))