QiShare is a team of QiShare and qliuq, the QiShare team of QiShare

Introduction: These articles are part of an effort to delve into Effective Objective-C 2.0. They include opinions from the author and from the editor, as well as some demos. Hope to help you quickly understand the essence of the original author with concise words. Here, the QiShare team pays tribute to original author Matt Galloway.

The table of contents is as follows: IOS write high-quality Objective-C code (1) iOS write high-quality Objective-C code (2) iOS write high-quality Objective-C code (3) iOS write high-quality Objective-C code (4) iOS Write high-quality Objective-C code for iOS (6) Write high-quality Objective-C code for iOS (7)


This article is about “memory management mechanisms” in iOS.

When it comes to iOS memory management, there are two mechanisms available on iOS: MRC & ARC. Manual Reference counting (MRC) : allows developers to manage memory. ARC (Automatic Reference counting) : supports automatic reference counting starting from iOS 5. The memory is managed by the compiler.

Why Did Apple introduce ARC?

Prior to iOS 4, all iOS developers had to manually manage memory, that is, the allocation and release of memory for objects. First, the constant insertion of memory management statements, such as retain and release, significantly increased the amount of work and code. Second, in the face of some multi-threaded concurrent operations, manual memory management is not easy for developers, and may lead to a lot of unexpected problems. So, Starting with iOS 5, Apple introduced ARC, which lets the compiler manage memory for you. At compile time, the compiler automatically adds memory management statements. In this way, developers can focus more on business logic.

Writing quality Objective-C code (part 5)

Understand reference counting

  • How reference counting works:

Objective-c Advanced Programming iOS and OSX Multithreading and Memory Management

1. Turn on the light: create an object. 2. Turn off the lights: extended to: “destroy objects”.

Explanation: 1. Someone clocks in: Turn on the light. — (create object, count 1) 2. Someone else: Keep the light on. — (Hold object, count 2) 3. Someone else: Keep light on. 4. Someone clocked out: Keep the light on. 5. Someone else is off duty: Keep the light on. 6. All employees are off duty: Turn off the lights. — (Destroy object, count 0)


scenario Action corresponding to OC Method corresponding to OC
To turn on the light To generate the object Alloc/new/copy/mutableCopy, etc
Need lighting Hold the object retain
No lighting required Remove hold release
Turn off the lights from work Destruction of objects dealloc

If you find the examples in this book a bit abstract, please take a look at the illustration below: Hint: Solid arrows are strong references, and virtual arrows are weak references.

  • Memory management in property access methods:

Here is an example of the set method:

- (void)setObject:(id)object { [object retain]; // Added by ARC [_object release]; // Added by ARC _object = object; }Copy the code

Explanation: The set method will keep the new value, release the old value, and then update the instance variable. The order of these three statements is important. If you release and then retain. The object may have already been reclaimed, at which point the retain operation is invalid because the object has been freed. The instance variable becomes a dangling pointer. (Dangling pointer: A pointer is a pointer to nil.)

  • Auto-release pool: If you’re careful, you’ll notice that when we write iOS apps,mainThere’s one in the functionautoreleasepoolAutomatic release pool.
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

Autorelease can extend the life of an object for a while after it has crossed the “method call boundary”.

  • Circular reference:

Retain cycles are also called retention cycles. Circular references are formed because objects strongly point to each other (or hold each other). In development, we don’t want circular references because of memory leaks. Solution: One party uses weak references to unloop references so that multiple objects can be freed. PS: For information on how to check for memory leaks in a project: see this blog post.

Simplify reference counting with ARC

In the ARC environment, 🚫 is prohibited from calling the retain, release, Autorelease, dealloc methods.

  • If the method name begins with alloc, new, copy, or mutableCopy, the returned object belongs to the caller.

  • Memory management semantics for variables:

Compare the code differences between MRC and ARC

MRC environment:

- (void)setObject:(id)object {

    [_object release];
    _object = [object retain];
}
Copy the code

This creates a boundary case where if the new value and the old value are the same object, they are released first, and object becomes a dangling pointer.

ARC environment:

- (void)setObject:(id)object {

    _object = object;
}
Copy the code

ARC resolves the boundary problem in a more secure way: keep the new value, release the old value, and finally update the instance variable.

Meanwhile, ARC can change the semantics of local and instance variables with modifiers:

The modifier The semantic
__strong Default, strong hold, keep this value.
__weak Do not retain this value. After the object is freed, the pointer is set to nil.
__unsafe_unretained It is unsafe not to retain this value. After the object is freed, the pointer still points to the original address (that is, without nil).
__autoreleasing This value is automatically released when the method returns.
  • How ARC cleans up instance variables:

In MRC, developers need to insert the necessary cleanup code into dealloc. ARC does this by borrowing a feature of objective-c ++ that calls C++ ‘s destructor: the.cxx_destruct method. When the OC object is released, ARC adds the required cleanup code to the.cxx_destruct underlying method (which calls the dealloc method at some point). However, if there are non-OC objects, the dealloc method will be overridden. For example, objects in CoreFoundation or memory allocated by malloc() in the heap still need to be cleaned up. Call CFRetain/CFRelease when appropriate.

- (void)dealloc {

   CFRelease(_coreFoundationObject);
   free(_heapAllocatedMemoryBlob);
}
Copy the code

The dealloc method releases only references and unlistens

When the dealloc method is called, the object is already in the reclaimed state. Other methods cannot be called at this point, especially if they are called back to perform some task asynchronously. If the object has been destroyed by the time the callback is executed asynchronously, the object will crash.

The dealloc method does something related to releasing, such as:

  • Releases references to other objects.
  • Unsubscribe from KVO.
  • Cancels NSNotificationCenter notification.

Here’s an example:

  • KVO:
- (void)viewDidLoad { //.... [webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil]; [webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil]; [webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil]; [webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ self.backItem.enabled = self.webView.canGoBack; self.forwardItem.enabled = self.webView.canGoForward; self.title = self.webView.title; self.progressView.progress = self.webView.estimatedProgress; self.progressView.hidden = self.webView.estimatedProgress>=1; } - (void)dealloc { [self.webView removeObserver:self forKeyPath:@"canGoBack"]; //< remove KVO [self.webView removeObserver:self forKeyPath:@"canGoForward"]; [self.webView removeObserver:self forKeyPath:@"title"]; [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; }Copy the code
  • NSNotificationCenter:
- (void)viewDidLoad { //...... // Add response notification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarBtnRepeatClick) name:BQTabBarButtonDidRepeatClickNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(titleBtnRepeatClick) name:BQTitleButtonDidRepeatClickNotification object:nil]; } // Remove notification - (void)dealloc {// [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTabBarButtonDidRepeatClickNotification object:nil]; // [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTitleButtonDidRepeatClickNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self]; }Copy the code

Pay attention to memory management problems when writing “exception safe code”

Exceptions should only be thrown after a serious error has occurred. Bad use can cause a memory leak: In a try block, if an object is retained and then an exception is thrown before it is released, the object’s memory will leak unless the catch block fixes the problem.

Reason: C++ destructors are run by Objective-C exception-handling routines. Because throwing an exception shortens the lifetime, an exception must be destructed to avoid a memory leak, which can occur if system resources such as file handles are not cleaned up properly.

  • When catching an exception, make sure thattryObjects created in the block are cleaned up.
  • By default, the compiler does not generate the cleanup code needed to safely handle exceptions under ARC. To enable, please manually open:-fobjc-arc-exceptionsMark. But it affects performance. So it’s best not to use it. But here’s a situation you can use:Objective-C++Mode.

PS: C++ and Objective-C exceptions are compatible with each other at runtime. This means that exceptions thrown by either language can be caught by “exception handlers” written in the other language. When writing objective-c ++ code, the code used by C++ to handle exceptions is similar to the additional code implemented by ARC. The -fobjc-arc-exceptions flag is automatically turned on by the compiler with little performance penalty.

Finally, I suggest:

  1. Exceptions are only used to handle fatal errors.
  2. For non-fatal errors, there are two solutions:
    • Let the object returnnilor0(for example: invalid initialization argument, method returns nil or 0)
    • useNSError

5, weak references to avoid circular references (avoid memory leaks)

Retain Cycle with Weak References

  • To avoid memory leaks due to circular references. At this point, some references need to be set to weak (weak).
  • Using weak ReferencesweakIn ARC, the pointer is set when the object is releasednil.

6. Reduce memory peak with “auto release pool block”

  • By default, the auto-release pool waits for the thread to execute the next event loop before emptying. Typically, the for loop creates new objects to be added to the auto-release pool until the end of the loop. As a result, it can take up a lot of memory.
  • Manually add automatic Release pool block (@autoreleasepool) : Each for loop frees memory directly, reducing the memory peak.

In particular, automatic release pooling can be used to reduce memory spikes when traversing large arrays or dictionaries, for example:

NSArray *qiShare = /* a large array */ NSMutableArray *qiShareMembersArray = [NSMutableArray new]; for (NSStirng *name in qiShare) { @autoreleasepool { QiShareMember *member = [QiShareMember alloc] initWithName:name]; [qiShareMembersArray addObject:member]; }}Copy the code

PS: The principle of autorelease pool: arranged in the “stack”, the system puts the object into the top pool after executing the autorelease message (pushed), while clearing the autorelease pool is to destroy the object (pushed). The time to call off the stack is when the current thread executes the next event loop.

Debug memory management problems with “zombie object”

As shown above, check here to enable zombie object Settings. After this function is enabled, the system does not recycle the object. Instead, the isa pointer points to a zombie class and turns it into a zombie object. The zombie class can respond to all selectors by printing a message containing the message content and its receiver, and then terminating the application.

Zombie object simplicity: Implementation code is built into the objective-C runtime library, Foundation framework, and CoreFoundation framework. When the system is about to reclaim an object, it is identified as a zombie object by an environment variable NSZombieEnabled — not completely reclaimed, isa pointer points to the zombie class and responds to all selectors.

Don’t use retainCount

RetainCount has been deprecated since Apple introduced ARC, and there is no way to call the retainCount method to check the reference count at any time, because the value is actually not accurate anymore (and cannot be called in an ARC environment). But it can be used normally under MRC.

Finally, special thanks: Effective Objective-C 2.0 chapter 5.

QiShare(Simple book) QiShare(digging gold) QiShare(Zhihu) QiShare(GitHub) QiShare(CocoaChina) QiShare(StackOverflow) QiShare(wechat public account)

If 360 introduces pepper water, will ladies buy it? From dog food you can learn about the WoT connection scene