I have been developing software in Objective-C for quite some time now, but I continue to discover new ways to shoot myself in the foot, particularly in the way of memory management. A while back, I wrote a post about finding iOS memory leaks using Xcode’s Instruments. This is a great tool for discovering memory leaks and tracking them down. But ideally, we would not have to deal with Objective-C memory leaks in the first place! The best way to avoid these types of problems is to stop them before they start.
In thinking back about most of the Objective-C memory leaks I’ve dealt with, they can be boiled down to a few main causes. To prevent them, I’d suggest the following practices.
Use the Correct Object Ownership
Memory leaks often occur as the result of a retain cycle. When two objects have strong references to each other, they will never be deallocated. The first step in breaking these cycles is to use correct object ownership.
A property declaration usually contains a keyword like strong or weak. If you’re not sure which one to use in a particular instance, think about whether the property is an ownership relationship or merely a reference relationship.
Typically, a parent class would like all of the objects that it owns to remain in memory as long as the parent itself is in memory. So the parent would have a strong reference to its child. However, a child may also have a reference to its parent (for example, a UIView has a reference to its superview). The child does not own the parent, so it must have a weak reference to its parent.
Another good example of this is the delegate pattern. An object always has a weak reference to its delegate since it does not own the delegate (typically, the delegate is the owner).
Be Careful When Using Blocks
It’s very easy to create retain cycles when using blocks if you’re not careful (and sometimes even if you are careful!). Most often this occurs when the block captures a reference to self, and then that block is assigned to a property on self. If you’ve done any work with blocks at all, you’ve probably seen the advice to use a weakened version of self inside the block, like so:
__weak typeof(self) weakSelf = self;
self.updateText = ^(NSString *text) {
__strong typeof(self) strongSelf = weakSelf;
strongSelf.label.text = text;
};
Ugh. Fortunately, there is a library that contains some useful macros for this type of repetitive work. So instead of all that ugliness (and having to remember to use strongSelf instead of self inside the block), you can just use @weakfiy and @strongify, like this:
@weakify(self)
self.updateText = ^(NSString *text) {
@strongify(self)
self.label.text = text;
};
My current project utilizes ReactiveCocoa, which makes extensive use of blocks. So I’ve gotten into the habit of always using @weakify/@strongify whenever there is a block. The compiler will emit a warning if self isn’t used inside the block, which I think is actually a little unfortunate. It means that I have to remove the @strongify(self) in order to avoid the warning, but make sure to put it back in later if I add some reference to self.
There may be references to objects other than self that you need to @weakify/@strongify, as well. References to self are the most common case, since typically the block will be assigned to a property on self. But it all depends what object is ultimately going to hold a strong reference to the block.
Beware of Hidden Retain Cycles
Even if you’ve done your due diligence in using the correct object ownership, retain cycles may still lie hidden behind macros. I recently discovered that the NSAssert macro contains a reference to self. It’s helpful for logging, but it’s not obvious at all that calling NSAssert
inside of a block can cause a retain cycle.
To protect against this, just be sure you’ve done your @weakify/@strongify before the call to the macro. Alternatively, you can use NSCAssert, which does not have a reference to self at all.
Conclusion
Memory management is a pain, but hopefully, establishing a few good habits can help you avoid the headache of Objective-C memory leaks.