欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

程序员文章站 2022-03-07 20:01:31
...

前言:上一篇内存管理里面, iOS内存管理篇(一)–alloc/reatain/release/dealloc方法实现 我们提到了如何引用计数的概念,那么今天我们来看看 NSAuoreleasePool是什么,如何工作的的,又是一个怎样的原理。

NSAutoreleasePool是什么

  • 官方释义:NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.

  • 个人理解:NSAutoreleasePool是一个对象池,它管理着在池内的对象的引用计数以及何时销毁问题。

  那么现在有朋友会说,NSAutoreleasePool离我们很远啊,从来没有使用过,是的,NSAutoreleasePool 是在 MRC时代使用的,那么 ARC是使用什么呢

@autoreleasepool {
    }

  PS:使用以上的代码的时候,系统自动为我们创建了一个 NSAutoreleasePool
那么来说一个离我们最近的@autoreleasepool吧,我们新建一个工程,然后可以看到如下图的 main.m 文件

iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

  打开 main.m 文件,我们可以看到如下代码

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

  原来在我们工程创建的时候,系统就为我们创建好了一个@autoreleasepool。
那么来讲一下这个@autoreleasepool吧。


一个项目里面可以有多个@autoreleasepool

  每一个 NSRunLoop会隐式创建一个autoreleasepool
新建一个@autoreleasepool会像堆栈一样压入@autoreleasepool组里面,新的@autoreleasepool会代替当前的@autoreleasepool成为新的当前@autoreleasepool。当每一个NSRunLoop结束的时候,会将当前的autoreleasepool进行销毁,如下的一个结构图

iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

  PS: 可以把autorelease pool理解成一个类似父类与子类的关系,main()创建了父类,每个Runloop自动生成的或者开发者自定义的autorelease pool都会成为该父类的子类。当父类被释放的时候,没有被释放的子类也会被释放,这样所有子类中的对象也会收到release消息。

我们来看看实际的一个例子

有如下的代码:

#import "MStestaaaViewController.h"

@interface MStestaaaViewController ()
@property (nonatomic ,copy) NSString *testStr;
@end

@implementation MStestaaaViewController
__weak id reference = nil;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *str = [NSString stringWithFormat:@"I am a test"];
    // str是一个autorelease对象,设置一个weak的引用来观察它
    reference = str;
    NSLog(@"viewDidLoad with testStr = %@",reference);
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear with testStr = %@",reference);
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"viewDidAppear with testStr = %@",reference);

打印结果如下

2017-07-13 20:36:15.541 hi7_client[4185:603299] viewDidLoad with testStr = I am a test
2017-07-13 20:36:15.544 hi7_client[4185:603299] viewWillAppear with testStr = I am a test
2017-07-13 20:36:37.466 hi7_client[4185:603299] viewDidDisappear with testStr = I am a test
2017-07-13 20:36:37.467 hi7_client[4185:603299] dealloc 

以上结果说明这三个方法都是在一个 autorelease实现的,我们也可以手动修改作用块

- (void)viewDidLoad {
    [super viewDidLoad];
    __autoreleasing NSString *str;
    @autoreleasepool {
        str = [NSString stringWithFormat:@"sunnyxx"];
    }
    NSLog(@"%@", str); // Console: (null)
}

关于__autoreleaseing 的解释是

__autoreleasing表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

  当我们创建一个 autorelease pool 的时候,系统是如何做的呢,系统会生成一个叫做“autorelease pool page”的东西,为我们开辟一页的虚拟内存空间,至于这个类是怎么实现的借助一下这篇文章的一个图片黑幕背后的Autorelease

iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

  我们知道内存地址的分配都是由低地址分配到高地址,最开始栈顶指针和栈底指针是一致的, 随着我们往当前的autoreleasepool里面增加元素栈顶地址也会增加,每释放一个元素,栈顶地址也会随之下降,如果是直接释放整个 autoreleasepool的话,里面的元素也会随之释放。
嵌套式的 autoleasepool 也是如此。

理解 autorelease

Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release

举个例子来说,有如下代码

-(void)viewDidLoad
{
    [super viewDidLoad];
    Person *p = [[Person alloc]init];
    [p release];
    p.name = @"I am Lili";
}

这个时候,带么执行到”p.name = @”I am Lili”;”这一句的时候就会报错,原因很简单,因为 p 已经被释放了,这个内存地址已经不存在了,而再次调用 p.name = @”I am Lili”;,就会产生野指针

在 ARC的模式下,我们不需要手动调用 release 方法,系统在编译阶段自动为我们加上了释放的代码

例如: 有如下代码

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

系统在编译阶段创建的代码是这样的

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

什么样的场景下用autoreleasepool?
苹果官方是这么说的

  • If you are writing a program that is not based on a UI framework, such as a command-line tool.
    你写的程序不是基于UI framework, 例如命令行项目

  • If you write a loop that creates many temporary objects.
    You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
    If you spawn a secondary thread.
    你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值

  • You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.
    你创建了一个新线程
    当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.

举个例子来说

+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{

// Create a graphics image context
UIGraphicsBeginImageContext(newSize);

// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();

// End the context
UIGraphicsEndImageContext();


// Return the new image.
return newImage;
}

如果循环几百次调用以上的代码,就会收到内存警告,如何优化,代码如下:

+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
//http://wiresareobsolete.com/2010/08/uiimagepickercontroller/

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// Create a graphics image context
UIGraphicsBeginImageContext(newSize);

// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();

// End the context
UIGraphicsEndImageContext();

[newImage retain];

[pool release];

// Return the new image.
return newImage;
}

添加上了 nsautoreleasepool 后就不会收到内存警告了 arc 模式下使用@autoreleasepool

平时使用 for 循环和 for in 循环,苹果官方还给我们提供了一种循环遍历的方法,叫做
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
在内存上也进行了优化,有兴趣的同学可以试一试。

在使用的时候需要注意什么

  • 在ARC项目中我们同样可以创建NSAutoreleasePool类对象去帮助我们更精确的管理内存问题。

  • NSAutoreleasePool的管理范围是在NSAutoreleasePool *pool =
    [[NSAutoreleasePool alloc]init];与[pool release];之间的对象

  • 既然ARC项目中设置了ARC,为什么还要使用@autoreleasepool?(注意a的案例解释)ARC 并不是舍弃了
    @autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease
    的代码调用。所以,跟你想象的不一样,ARC 之下依然是延时释放的,依然是依赖于 NSAutoreleasePool,跟非 ARC
    模式下手动调用那些函数本质上毫无差别,只是编译器来做会保证引用计数的正确性

  • NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; 当执行[pool
    autorelease]的时候,系统会进行一次内存释放,把autorelease的对象释放掉,如果没有NSAutoreleasePool
    , 那这些内存不会释放
    注意,对象并不是自动被加入到当前pool中,而是需要对对象发送autorelease消息,这样,对象就被加到当前pool的管理里了。当当前pool接受到drain消息时,它就简单的对它所管理的所有对象发送release消息。

  • 在ARC项目中.不能直接使用autorelease pools,而是使用@autoreleasepool{},
    @autoreleasepool{}比直接使用NSAutoreleasePool效率高。不使用ARC的时候也可以使用(autorelease嵌套)

好了,我们说了这么多,了解了 NSAutoreleasePool 和 autorelease 的概念,上一篇文章也学习了 alloc/reatain/release/dealloc的使用方法,其实都是内存管理的一些基础知识,系统是如何为我们分配内存的,如何管理对象引用计数的,如何在适当的时候给我们添加代码的,都有做详细的说明,大家有不同或者对本文有质疑的地方,欢迎提出哟