28

Revisit ARC, Autorelease pool, and NSString

Revisit ARC, Autorelease pool, and NSString - a OOM incident during live streaming in Kuaishou's main app

This post was originally published in Chinese at Kuaishou HQ. It was then republished and at 快手大前端技术, which has sensitive code and images removed.

The conclusion:

NSString is not as simple as we thought. It is a class cluster. There are actually many concrete classes inside NSString, such as various String objects, NSTaggedStringContainer, NSTaggedPointerString, etc.

When [NSString stringWithCString:encoding:] is called, it actually calls CFStringCreateWithBytes(). This is critical because CF(CoreFoundation) objects do not implement the autorelease mechanism.

[NSString stringWithFormat:] is much more complex, and if we delve deeper, it ultimately calls a method of NSAttributedString.

The reason why we see inconsistent behavior is because the implementation of [NSString stringWithCString:encoding:] happens to be different. The "leak" we see in [NSString stringWithFormat:] is actually caused by a certain part of the internal implementation of [NSString stringWithFormat:], not by the method itself.

As outsiders, devs shouldn't care too much about these details.

The different results we see in the class methods of NSString in an environment where autorelease is not released in a timely manner are normal, because the concrete class implementation of NSString is different, leading to different results

Key take away:

  1. Not using Autorelease pool properly We should create an autoreleasepool in any Cocoa object. We can rely on several autoreleasepools provided by Cocoa, such as using @autoreleasepool directly in the main() function, and run loops will also execute pool push/pop operations before and after each event. However, in contexts where we are unsure, we should not overly rely on autoreleasepools created by the system.

  2. Autorelease pool is not that smart The implementation of autoreleasepool is not as complex or intelligent as we imagine; it simply provides a mechanism for delaying the release of objects.

  3. Pool is lightweight, create pools on demand, and don't worry creating too many. Many discussions have been made about autoreleasepool causing performance costs, but this is inaccurate. In fact, we do not need to worry too much about creating too many autoreleasepools, as autoreleasepool itself is a very lightweight implementation, and normal use does not cause performance burden. In fact, the performance cost of using ARC is greater than that of using autorelease.

We should not rely on autoreleasepools that are "automatically created" by CF to correctly release Cocoa objects. In an environment without autoreleasepool, calling auto_release() is a "programmer error" which would have led to a crash in the past. It is only after many years of iteration that the runtime now automatically creates an autoreleasepool, but this does not necessarily solve the memory management problem, as the bound thread may never end.