@autoreleasepool uses in 2019 Swift

return label; [label release]; // Oopsie, nope!}If release() is called before return, the NSString will dealloc before it can be used which will crash the app, and calling after return means it will never be executed, causing a memory leak.

The solution for this edge case is to use a neat method called autorelease():-(NSString *)getCoolLabel { NSString *label = [[NSString alloc] initWithString:@"SwiftRocks"]; return [label autorelease];}Instead of instantly reducing the retain count of an object, autorelease() adds the object to a pool of objects that need to be released sometime in the future, but not now.

By default, the pool will release these objects at the end of the run loop of the thread being executed, which is more than enough time to cover all usages of getCoolLabel() without causing memory leaks.

Great, right?Well, kinda.

This will indeed solve your problem in 99% of the times, but consider this:-(void)emojifyAllFiles { int numberOfFiles = 1000000; for(i=0;i<numberOfFiles;i++) { NSString *contents = [self getFileContents:files[i]]; NSString *emojified = [contents emojified]; [self writeContents:contents toFile:files[i]]; }}Assuming that getFileContents and emojified return autoreleased instances, the app will be holding two million instances of NSStrings at once even though the individual properties could be safely released after their respective loops!Because autorelease defers the release of these objects, they will only be released after the run loop ends — which is way after the execution of emojifyAllFiles.

If the contents of these files are large, this would cause serious issues if not crash the app entirely.

The solution to prevent this is the @autoreleasepool block; when used, every autoreleased property defined inside of it will be released exactly at the end of block:-(void)emojifyAllFiles { int numberOfFiles = 1000000; for(i=0;i<numberOfFiles;i++) { @autoreleasepool { NSString *contents = [self getFileContents:files[i]]; NSString *emojified = [contents emojified]; [self writeContents:contents toFile:files[i]]; } }}Instead of waiting until the end of the thread’s run loop, the two NSStrings now receive a releasemessage right after writeContents is called, keeping the memory usage stable.

In fact, “releasing after the thread’s run loop” isn’t compiler magic, this is simply because the threads themselves are surrounded by @autoreleasepools!.You can see this partially in the main.

m of any Obj-C project.

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}What about Swift though?Is @autoreleasepool needed in ARC-era Swift?The answer is it depends.

In theory, yes, as the problem shown still exists since autorelease is still a thing, but these problems are harder to come by.

The ARC optimizations for Swift evolved a lot in the past few years, and as far as I tested it seems that ARC for Swift is now smart enough to simply never call autorelease, editing your code so that objects call release multiple times instead.

The language itself doesn't even appear to have a definition for autorelease outside of the Obj-C bridging — the one we can use in Swift is in fact the one from Obj-C.

This means that for pure Swift objects, @autoreleasepool appears to be useless as nothing is never going to be autoreleased.

The following code stays at a stable memory level even though it's looping millions of times.

for _ in 0.

9999999 { let obj = getGiantSwiftClass()}Swift code calling Foundation / Legacy Objective-C codeHowever, it’s a different story if your code is dealing with legacy Obj-C code, specially old Foundation classes in iOS.

Consider the following code that loads a big image tons of times: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) }}Even though we’re in Swift, this will result in the same absurd memory spike shown in the Obj-C example!.This is because the Data init is a bridge to the original Obj-C [NSDatadataWithContentsOfURL] — which unfortunately still calls autorelease somewhere inside of it.

Just like in Obj-C, you can solve this with the Swift version of @autoreleasepool; autoreleasepool without the @:autoreleasepool { let url = URL(fileURLWithPath: file) let imageData = try!.Data(contentsOf: url)}The memory usage will now again be at a low, stable level.

How to know if a legacy UIKit/Foundation init/method returned an autoreleased instance?Because these frameworks are closed source at Apple, there’s no way to look their source code to see where autorelease is used.

I tried many ways to automate this inside Swift with no success.

My attempts included expanding NSData to contain an associated object to a dummy Swift class, but ARC was still smart enough to deinit these objects right after their loops even though the Data object is technically still alive.

I also tried to create a weak reference to the Data instance hoping that it would be still alive after its loop, but it was too set to nil.

This makes me think that it's possible that the Data object is indeed deiniting after the loop and what's autoreleasing in that init is something else entirely, but there's way to confirm this without looking the source code; which we unfortunately can't.

I believe that there must be an internal property in NSAutoreleasePool or NSThread that you can inspect autoreleased properties, but for now the only way I found really is to manually look the Allocations instrument.

ConclusionTo put it short, autoreleasepool is still useful in iOS/Swift development as there are still legacy Obj-C classes in UIKit and Foundation that call autorelease, but you likely don't need to worry about it when dealing with Swift classes due to ARC's optimizations.

Follow me on my Twitter (@rockthebruno), and let me know of any suggestions and corrections you want to share.

.

. More details

Leave a Reply