「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。
在 iOS 开发中Foundation的NSAutoreleasePool类型是一个很久的类。后来抽象为@autoreleasepool。在MRC的时候,这种类型的使用对于防止内存爆炸起到非常重要的作用。随着ARC的出现以及后面的Swift发展,我们已经很少去手动管理内存,从来使得看起来它变得少见了。
@autoreleasepool(OC)
在MRC的时候,我们经常使用retain和release来进行基于对象的引用计数进行加减,可以来指示对象被引用的次数,当引用计数达到0时就可以安全地释放它。
- (NSString *)getFileName{
NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
return fileName;
}
NSString的alloc会在底层自动调用retain使fileName能够是引用计数+1(计数 1),然后将fileName返回给想要引用它的其他类,再次将会再次引用计数+1(计数 2)。但是这里有一个很大的问题,当调用getFileName调用release以发出释放它的信号之后,引用计数不会是0,而是1。这样会导致fileName无法被释放。那我们我们希望方法里面NSString创建的计数+1自己释放,那么用release能做到嘛?让我们试试。。。。(引用计数有一个原则:谁retain 谁release)
-(NSString *)getFileName {
NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
[fileName release]; // Oopsie, nope!
return fileName;
[fileName release]; // Oopsie, nope!
}
如果release在调用return之前调用,fileName会在使用之前被释放,这会导致程序崩溃,而在调用return之后调用意味着它永远不会被执行,从而导致内存泄漏。这样看来使用release我们无法处理这样的情况。这个时候我们使用到autorelease了。
-(NSString *)getFileName {
NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
return [fileName autorelease];
}
autorelease不会立即马上减少对象fileName的引用计数,而是将对象添加到未来某个时间的释放池中进行释放(-1)。默认情况下,释放池会在正在执行的线程运行循环结束后释放需要释放的对象。这样会所有使用fileName的对象能正确释放,而不会导致内存泄漏。
但是当我们遇到(文件操作)这样消耗大量内存的操作的时候,如下面的例子:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
假设urls是100万次的时候,应用程序将举行100万个fileContents的实例化,因为系统默认的是用autorelease推迟释放这些对象,它们只会在运行循环结束后才会被释放。 如果这些文件的内容很大,如果不崩溃的话也会操作内存使用峰值过高,所以这个也是一个严重的问题。而解决或者防止这样的情况我们可以使用@autoreleasepool来解决。
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
这样100万个fileContents现在不是等到正在运行的线程的循环结束去release,而是在被调用后立即发送release,从而保持内存使用稳定。这个for循环里如果不使用@autoreleasepool,那临时变量内存可能是爆发式的,但是使用了@autoreleasepool,在每个@autoreleasepool结束时,里面的fileContents都会回收,内存使用更加合理。
而@autoreleasepool 其实在oc的main.m文件我们就可以看到项目自己一开始就自己创建一个自动释放池:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@autoreleasepool(Swift)
我们知道在Swift使用的一些类实际上它还是来自OC的定义。如下面的例子:
func run()
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
经过测试发现在 Swift 中,也会产生像OC中那样高的内存!这是因为Data init 是桥接了OC的[NSData dataWithContentsOfURL],它内部使用的还是autorelease。所以在Swift也需要像OC那样使用autoreleasepool来解决这个问题来保持内存稳定性。
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
简而言之,autoreleasepool在 OC/Swift 开发中仍然很有用,因为 UIKit 和 Foundation中仍有有很多的类内部调用的是autorelease。