“This is the 20th day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

Foundation’s NSAutoreleasePool type is a long-standing class in iOS development. Later abstracted to @autoreleasepool. In the case of MRC, this type of use is very important to prevent memory explosions. With the advent of ARC and later Swift, we rarely managed memory manually, never making it seem like it was rare.

@ autoreleasepool (OC)

In the MRC era, we often used retain and release to add and subtract reference counts based on objects to indicate how many times an object was referenced, and to safely release it when the reference count reached zero.

- (NSString *)getFileName{
    NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
    return  fileName;
}
Copy the code

The NSString alloc will automatically call retain underneath so that fileName can be reference count +1 (count 1), and then return fileName to any other class that wants to reference it, which will reference count +1 (count 2) again. But there is a big problem here, when getFileName calls release to signal to release it, the reference count will not be zero, but one. FileName cannot be released. So we want the count +1 that NSString created in this method to release itself, can we do that with release? Let’s try… (Reference counting has one rule: retain and release)

-(NSString *)getFileName {
      NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
     [fileName release]; // Oopsie, nope!
     return fileName;
     [fileName release]; // Oopsie, nope!
 }
Copy the code

If release is called before return is called, fileName is released before use, which causes the program to crash, while calling after return means it will never be executed, resulting in a memory leak. So it looks like we can’t handle this situation with release. So now we’re using autoRelease.

-(NSString *)getFileName {
    NSString *fileName = [[NSString alloc]initWithString:@"fileName"];
     return [fileName autorelease];
}
Copy the code

Instead of immediately reducing the reference count of the object fileName, autoRelease adds the object to the release pool at some future time for release (-1). By default, the release pool releases the objects that need to be released after the executing thread runs the loop. This will free all objects using fileName correctly without causing a memory leak.

However, when we encounter operations that consume a lot of memory, here is an example:

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. */

}

Copy the code

Assuming that urls are 1 million times, the application will hold 1 million fileContents instantiations, because the default is to defer releasing these objects with autoRelease, and they will only be released after the runtime loop ends. If the contents of these files are large, the operation memory usage peaks too high without crashing, so this is also a serious problem. This can be resolved or prevented using @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]; }}Copy the code

So instead of waiting until the running thread’s loop ends to release, a million fileContents now send a release as soon as it is called, keeping memory usage stable. If @AutoReleasepool is not used in this for loop, the temporary variable memory might be explosive, but with @Autoreleasepool, fileContents are reclaimed at the end of each @Autoreleasepool, and memory usage is more reasonable. @autoreleasepool in the oc main.m file we can see that the project itself created an autoreleasepool from the start:

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

@ autoreleasepool (Swift)

We know that some of the classes that we use in Swift are actually derived from the OC definition. Here’s an example:

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) } }Copy the code

After testing, it is found that in Swift, also produces as high memory as in OC! This is because Data init Bridges the OC’s [NSData Data with Contentsofurl], and it still uses autoRelease internally. So Swift also needs to use autoreleasepool as OC does to solve this problem to maintain memory stability.

autoreleasepool {
     let url = URL(fileURLWithPath: file)
     let imageData = try! Data(contentsOf: url)
 }
Copy the code

In short, AutoReleasepool is still useful in OC/Swift development because there are still a lot of classes in UIKit and Foundation that call autoRelease internally.