Memory management

iPhone and iPad devices are resource-constrained on memory. An app may be terminated by the operating system if it crosses the established per-process limit.1 As such, successfully managing memory plays a central role in implementing an iOS app.

At WWDC 2011, Apple revealed that about 90% of device crashes happened due to issues pertaining to memory management. And of these, the biggest causes are either bad memory access or memory leaks due to retain cycles.

Unlike the Java runtime (which uses garbage collection), iOS runtimes for Objective-C and Swift use reference counting. The downsides of using reference counting include possible overrelease of memory and cyclic references if the developer is not careful.

As such, it is important to understand how memory is managed in iOS.

In this lesson, we study the following:

  • Memory consumption (i.e., how an app consumes memory)

  • The memory management model (i.e., how the iOS runtime manages memory)

  • Language constructs—we’ll take a look at Objective-C constructs and the available features you can use

  • Best practices for minimizing memory usage without degrading the user experience

Memory Consumption

Memory consumption refers to the RAM that an app consumes.

The iOS virtual memory model does not include swap memory, which means that, unlike with desktop apps, the disk cannot be used to page memory. The end result is that the apps are restricted to available RAM, which is used not only by the app in the foreground but also by the operating system services and potentially also by background tasks being run by other apps.

There are two parts to memory consumption in an app: stack size and heap size. The following subsections take a closer look at each.

Stack Size

Each new thread in an app receives its own stack space consisting of both reserved and initially committed memory. The stack is freed when the thread exits. The maximum stack size for a thread is small, and among other things, it limits the following:

Maximum number of methods that can be called recursively

Each method has its own stack frame and contributes to the overall stack space consumed. For instance, as shown in Example 1-1, if you call main, which in turn calls method1 (which subsequently calls method2), there are three stack frames contributing a few bytes each. Figure 1-1 shows how a thread stack looks like over time.

Example 1-1. Call tree
main() {
    method1();
}

method1() {
    method2();
}
Maximum number of variables that you can use within a method

All variables are loaded on the method stack frame, and hence contribute to the stack space consumed.

Maximum number of views that you can nest in the view hierarchy

Rendering a composite will invoke layoutSubViews and drawRect recursively across the complete hierarchy tree. If the hierarchy is deep, it may result in a stack overflow.

hpia 0201
Figure 1-1. Stack with stack frame of each method

Heap Size

All threads of one process share the same heap. The total heap size available for an app is generally much lower than the device RAM. For example, an iPhone 5S may have 1 GB of RAM, but the maximum heap size allocated to an app may be 512 MB or even less. The app cannot control the heap allocated for it. It is managed by the operating system.2

Processes such as NSString, loading images, creating or consuming JSON/XML data, and using views will consume a lot of heap memory. If your app is an image-heavy one (something along the lines of the Flickr and Instagram apps), you will need to take special care to minimize average and peak memory usage.

Figure 1-2 shows a typical heap that may exist at some time in an app.

In Figure 1-2, the main thread started by the main method creates UIApplication. We assume that at some point in time the window comprises a UITableView that uses a UITableViewDataSource whose method tableView:cellForRowAtIndex: is called when a row must be rendered.

The data source has a reference to all the photos to be shown in a property named photos of type NSArray. If not implemented properly, this array can be huge, resulting in high peak memory usage. One solution is to always store a fixed number of images in the array and swap in and out as the user scrolls the view. This fixed number will determine your app’s average memory usage.

hpia 0202
Figure 1-2. Heap demonstrating use of a model HPPhoto in a UITableViewDataSource

Each item in the array is of type HPPhoto, which represents a photo. HPPhoto stores data associated with the object—for example, image size, date of creation, owner, tags, web URL associated with the photo (not shown in the image), reference to local cache (not shown in the image), and so on.

All data related to objects created from classes is stored on the heap.

The class may have properties or instance variables (iVars) of value types such as int, char, or struct, but because the objects are created on the heap, they will consume only heap memory.

When objects are created and values assigned, they may be copied from stack to heap. Similarly, when values are used within a method, they may be copied from heap to stack. This may be an expensive operation. Example 1-2 highlights when the copy from stack to heap and vice versa happens.

Example 1-2. Heap versus stack
@interface AClass 1

@property (nonatomic, assign) NSInteger anInteger; 2
@property (nonatomic, copy) NSString *aString; 3

@end

//some other class
-(AClass *) createAClassWithInteger:(NSInteger)i
    string:(NSString *)s { 4

    AClass *result = [AClass new];
    result.anInteger = i; 5
    result.aString = s; 6
}

-(void) someMethod:(NSArray *)items { 7
    NSInteger total = 0;
    NSMutableString *finalValue = [NSMutableString string];

    for(AClass *obj in items) {
        total += obj.anInteger; 8
        [finalValue appendString:obj.aString]; 9
    }
}
1

The class AClass has two properties.

2

anInteger is of type NSInteger, which is passed by value.

3

aString is of type NSString *, which is passed by reference.

4

The createAClassWithInteger:string: method (in some class that is not of relevance here) instantiates AClass. This method is provided with the values required to create the object.

5

The value for i is on the stack. However, when assigned to the property, it must be copied to the heap because that is where result is stored.

6

Although NSString * is passed by reference, the property is marked copy, which means that the value must be duplicated or cloned, depending on how the method [-NSCopying copyWithZone:] is implemented.

7

someMethod: processes an array of AClass objects.

8

When anInteger is used, its value must be copied to the stack before it can be processed. In this example, the value is added to total.

9

When aString is used, it is passed by reference. In this example, appendString: uses the reference to the aString object.

Tip

It is a good idea to keep your memory requirements to not more than a percentage of available RAM. Though there is no hard rule to it, it is recommended to not use more than 80%–85%, leaving the remainder for core OS services.

Do not ignore didReceiveMemoryWarning signals.

Memory Management Model

In this section, we study how the iOS runtime manages memory and the effect it has on the code.

The memory management model is based on the concept of ownership. As long as an object is owned, the memory it uses cannot be reclaimed.

Whenever an object is created in a method, the method is said to own the object. If this object is returned from the method, then the caller is said to claim the ownership. The value can be assigned3 to another variable, and the corresponding variable is likewise said to have claimed the ownership.

Once the task with the object is completed, you relinquish ownership. This process does not transfer ownership, but increases or decreases the number of owners, respectively. When the number of owners goes down to zero, the object is deallocated and the memory is released.

This ownership count is more formally referred to as the reference count. When you manage it yourself, it is called manual reference counting (MRC). Although it is rarely used today, MRC is useful to understand. Modern-day apps use automatic reference counting (ARC), which we discuss in “Automatic Reference Counting”.

Example 1-3 demonstrates the basic structure of manual memory management using reference counting.

Example 1-3. Reference counting with manual memory management
NSString *message = @"Objective-C is a verbose yet awesome language"; 1
NSString *messageRetained = [message retain]; 2
[messageRetained release]; 3
[message release]; 4
NSLog(@"Value of message: %@", message); 5
1

Object created, ownership claimed by message, reference count of 1.

2

Ownership claimed by messageRetained, reference count increases to 2.

3

Ownership relinquished by messageRetained, reference count decreases to 1.

4

Ownership relinquished by message, reference count decreases to 0.

5

The value of message, strictly speaking, is undetermined. You may still get the same value as before because the memory may not have been reused or reset.

Example 1-4 demonstrates how methods affect the reference count.

Example 1-4. Reference count in methods
//part of a class Person
-(NSString *) address {
    NSString *result = [[NSString alloc]
            initWithFormat:@"%@
%@
%@, %@",
            self.line1, self.line2, self.city, self.state]; 1
    return result;
}

-(void) showPerson:(Person *) p {
    NSString *paddress = [p address]; 2

    NSLog(@"Person's Address: %@", paddress);

    [paddress release]; 3
}
1

Object first created; reference count of memory pointed to by result is 1.

2

Reference count of memory referenced via paddress (referring to result) is still 1. The method showPerson: is the owner of the object it creates using the address button. It should not retain.

3

Renounce the ownership; reference count goes down to 0.

If you look at Example 1-4, showPerson: does not know if address creates a new object or reuses one. However, it does know that the object would have been returned to it after incrementing the reference count by 1. As such, it does not retain the address. Once the job is completed, it releases it. If the object had a reference count of 1, it will become 0 and object will be dealloced.

Official Apple and LLVM documentation prefers the term ownership. The terms ownership and reference count are used interchangeably in the lesson.

Autoreleasing Objects

Autoreleasing objects allows you to relinquish the ownership of an object but defer its destruction. It is useful in scenarios in which you create an object in a method and want to return it. It helps in the management of an object’s life in MRC.

In the strict sense of naming conventions of Objective-C, in Example 1-4, there is nothing to denote that the address method owns the returned string. The caller, showPerson:, therefore has no reason to release the returned string, resulting in a potential memory leak. [paddress release] is a piece of code that has been added for illustrative purposes.

So, what is the correct code for the method address?

There are two possibilities:

  • Do not use alloc or associated methods.

  • Return an object with a deferred release message.

The first fix is easy to implement when working with NSString. The updated code is shown in Example 1-5.

Example 1-5. Fixed code for reference count in methods
-(NSString *) address {
    NSString *result = [NSString
            stringWithFormat:@"%@
%@
%@, %@",
            self.line1, self.line2, self.city, self.state]; 1
    return result;
}

-(void) showPerson:(Person *) p {
    NSString *paddress = [p address];

    NSLog(@"Person's Address: %@", paddress);
    2
}
1

Do not use the alloc method.

2

Do not use the release method within the showPerson: method that does not create the entity.

However, this fix is not easy to apply when not working with NSString, as it is generally difficult to find the appropriate method that will serve the need. For example, when working with a third-party library or with a class that has multiple methods to create an object, it may not always be clear which method retains the ownership.

And this is where deferred destruction comes into play.

The NSObject protocol defines the message autorelease that can be used for deferred release. Use it when returning an object from a method.

The updated code using autorelease is given in Example 1-6.

Example 1-6. Reference counting using autorelease
-(NSString *) address
{
    NSString *result = [[[NSString alloc]
            initWithFormat:@"%@
%@
%@, %@",
            self.line1, self.line2, self.city, self.state]
        autorelease];
    return result;
}

The code can be analyzed as follows:

  1. You own the object (NSString, in this case) returned by the alloc method.

  2. To ensure no memory leak, you must relinquish the ownership before losing the reference.

  3. However, if you use release, the object will be dealloced before return and the method, as a result, will return an invalid reference.

  4. autorelease signifies that you want to relinquish ownership but at the same time allow the caller of the method to use the returned object before it is dealloced.

Tip

Use autorelease when creating an object and returning from a non-alloc method. It ensures that the object will be released and, if applicable, memory reclaimed once the caller method is done working with it.

Autorelease Pool Blocks

The autorelease pool block is a tool that allows you to relinquish ownership of an object but avoid it being dealloced immediately. This is a very useful feature when returning objects from a method.

It also ensures that the objects created within the block are dealloced as may be needed once the block is complete. This is useful when you need to create several objects. Local blocks can be created to dealloc the objects as early as possible and keep the memory footprint low.

An autorelease pool block is marked using @autoreleasepool.

If you open the main.m file in the sample project, you will notice the code shown in Example 1-7.

Example 1-7. @autoreleasepool block in main.m
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([HPAppDelegate class]));
	}
}

All objects that were sent an autorelease message within the block will be sent a release message at the end of the autoreleasepool block. More importantly, a release message will be sent for each autorelease call. This means that if an object was sent an autorelease message more than once, the release message will be sent more than once. This is good, as it will keep the reference count of the object down to the same as it was before the autoreleasepool block. If the count is 0, the object will be dealloced, keeping a low memory footprint.

If you look at the code in the main method, you’ll see that the entire app is within the autoreleasepool block. This means that any autorelease object will be dealloced at the end, resulting in no memory leak.

Like other code blocks, autoreleasepool blocks can be nested, as shown in Example 1-8.

Example 1-8. Nested autoreleasepool blocks
@autoreleasepool {
 // some code
 @autoreleasepool {
  // some more code
	}
}

Because control passes from one method to another, it is uncommon to use nested autoreleasepool blocks in the same method. However, the called method may have its own autoreleasepool block for early object deallocations.

There are some occasions where you will likely want to create autoreleasepool blocks of your own. For example:

When you have a loop that creates lot of temporary objects

Use an autoreleasepool block within the loop to deallocate the memory for each iteration. Although the eventual memory use before and after the iteration may still be the same, the maximum memory requirement for your app may be reduced by a large factor.

Example 1-9 provides examples of bad as well as good code to write when using autoreleasepool blocks.

When you create a thread

Each thread will have its own autoreleasepool block stack. The main thread starts with its own autoreleasepool because it comes from the generated code. However, for any custom thread, you have to create your own autoreleasepool.

See Example 1-10 for sample code.

Example 1-9. Autorelease pool block in a loop
//Bad code 1
{
    @autoreleasepool {
        NSUInteger *userCount = userDatabase.userCount;

        for(NSUInteger *i = 0; i < userCount; i++) {
            Person *p = [userDatabase userAtIndex:i];

            NSString *fname = p.fname;
            if(fname == nil) {
                fname = [self askUserForFirstName];
            }

            NSString *lname = p.lname;
            if(lname == nil) {
                lname = [self askUserForLastName];
            }
            //...
            [userDatabase updateUser:p];
        }
    }
}

//Good code 2
{
    @autoreleasepool {
        NSUInteger *userCount = userDatabase.userCount;

        for(NSUInteger *i = 0; i < userCount; i++) {

            @autoreleasepool {
                Person *p = [userDatabase userAtIndex:i];

                NSString *fname = p.fname;
                if(fname == nil) {
                    fname = [self askUserForFirstName];
                }

                NSString *lname = p.lname;
                if(lname == nil) {
                    lname = [self askUserForLastName];
                }
                //...
                [userDatabase updateUser:p];
            }
        }
    }
}
1

This code is bad because there is only one autoreleasepool and the memory cleanup happens after all the iterations of the loop are complete.

2

In this case, there are two autoreleasepools. The inner autoreleasepool ensures that the memory cleanup happens after each iteration. This results in less memory requirements.

Example 1-10. Autorelease pool block in custom thread
-(void)myThreadStart:(id)obj {
    @autoreleasepool {
        //New thread's code
    }
}

//Somewhere else
{
    NSThread *myThread = [[NSThread alloc] initWithTarget:self
        selector:@selector(myThreadStart:)
        object:nil];

    [myThread start];
}

Automatic Reference Counting

Keeping track of retain, release, and autorelease is not easy. What is even more puzzling is determining where, when, and to whom to send these messages.

Apple introduced Automatic Reference Counting (ARC) at WWDC 2011 as a solution to this problem. Swift, the new language for iOS apps, also uses ARC. Unlike Objective-C, Swift does not support MRC.

ARC is a compiler feature.4 It evaluates the lifetime requirements of the objects in the code and automatically injects appropriate memory management calls at compile time. The compiler also generates appropriate dealloc methods. This means that most of the difficulties related to keeping track of memory usage (e.g., ensuring that it is deallocated when not required) are eliminated.

Figure 1-3 demonstrates the relative development time with MRC versus ARC. Development with ARC is faster because of reduced code.

hpia 0203
Figure 1-3. ARC reduces development time and prevents headaches

You’ll need to ensure that ARC is enabled in the Xcode project settings, which is the default starting with Xcode 5 (see Figure 1-4).

hpia 0204
Figure 1-4. Project settings for ARC in Xcode

Rules of ARC

ARC enforces a few rules that you must follow when writing your code. The intention of these rules is to provide a reliable memory management model. In some cases, they just enforce best practice, while in others they simplify the code or are direct corollaries of you not having to work directly with memory management.5 These rules are enforced by the compiler, resulting in a compile-time error rather than a runtime crash. These are the compiler rules when working with ARC:

  • You cannot implement or invoke retain, release, autorelease, or retainCount methods. This restriction is not only limited to working with objects but also with selectors. So, [obj release] or @selector(retain) are compile-time errors.

  • You can implement dealloc methods but cannot invoke them. This restriction extends not only to other objects but also to the superclass when implementing one. [super dealloc] is a compile-time error.

    You can still use CFRetain, CFRelease, and related methods with Core Foundation–syle objects.

  • You cannot use NSAllocateObject or NSDeallocateObject. Use alloc for creating objects. The runtime takes care of deallocation.

  • You cannot use object pointers in C structs.

  • There is no casual casting between id and void *. If necessary, you must do an explicit cast.

  • You cannot use NSAutoreleasePool. Use anautoreleasepool block instead.

  • You cannot use NSZone memory zones.

  • You cannot have a property accessor name starting with new, to ensure interoperability with MRC. This is demonstrated in Example 1-11.

  • Though something to avoid in general, you still can mix ARC and MRC code (we discussed this in “Working with Non-ARC Dependencies”).

Example 1-11. Accessor name with ARC enabled
//Not allowed
@property NSString * newTitle;

//Allowed
@property (getter=getNewTitle) NSString * newTitle;

Keeping these rules in mind, we can update Example 1-5. The resultant code is shown in Example 1-12.

Example 1-12. Updated code with ARC enabled
-(NSString *) address
{
    NSString *result = [[NSString alloc] initWithFormat:@"%@
%@
%@, %@",
        self.line1, self.line2, self.city, self.state]; 1
    return result;
}

-(void) showPerson:(Person *) p
{
    NSString *paddress = [p address];

    NSLog(@"Person's Address: %@", paddress);
    2
}
1

There is no need for autorelease. You cannot call autorelease or retain on the object result.

2

You cannot call release on paddress.

Reference Types

ARC introduced a new reference type: weak references. Understanding the available reference types is important to memory management. The supported types are:

Strong references

A strong reference is the default reference created. Memory referred to by a strong reference cannot be relinquished. A strong reference increases the reference count by 1, resulting in extension of the object’s lifetime.

Weak references

A weak reference is a special reference that does not increase the reference count (and hence does not extend the object’s lifetime). Weak references are a very important part of ARC-enabled Objective-C programming, as we explore later.

Variable Qualifiers

ARC also introduced four lifetime qualifiers for variables:

__strong

This is the default qualifier and does not need explicit mention. An object is kept in memory as long as there is a strong pointer to it. Consider it ARC’s version of the retain call.

__weak

This indicates that the reference does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator, except with the added safety that the pointer is automatically set to nil when the object is dealloced.

__unsafe_unretained

This is similar to __weak except that the reference is not set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator.

__autoreleasing

Used for message arguments passed by reference using id *. It is expected that the method autorelease will have been called in the method where the argument is passed.

The syntax for using these qualifiers is as follows:

TypeName * qualifier variable;

The code in Example 1-13 shows these qualifiers in use.

Example 1-13. Using variable qualifiers
Person * __strong p1 = [[Person alloc] init]; 1
Person * __weak p2 = [[Person alloc] init]; 2
Person * __unsafe_unretained p3 = [[Person alloc] init]; 3
Person * __autoreleasing p4 = [[Person alloc] init]; 4
1

Object created has a reference count of 1 and will not be dealloced until the point p1 is last referenced.

2

Object created has a reference count of 0, will be immediately dealloced and p2 will be set to nil.

3

Object created has a reference count of 1, will be immediately dealloced but p3 will not be set to nil.

4

Object created has a reference count of 1 and will be automatically released once the method returns.

Property Qualifiers

Two new ownership qualifiers have been introduced for property declaration: strong and weak. In addition, the semantics of the assign qualifier have been updated. In all, there are now six qualifiers:

strong

Default, indicates a __strong relationship.

weak

Indicates a __weak relationship.

assign

This is not a new qualifier, but the meaning has now changed. Before ARC, assign was the default ownership qualifier. With ARC enabled, assign now implies __unsafe_unretained.

copy

Implies a __strong relationship. Additionally, it implies the usual behavior of copy semantics on the setter.

retain

Implies a __strong relationship.

unsafe_unretained

Implies an __unsafe_unretained relationship.

Example 1-14 shows these qualifiers in action. Because assign and unsafe_unretained only copy over the value without any sanity check, they should only be used for value types (BOOL, NSInteger, NSUInteger, etc.). They must be avoided for reference types, specifically pointers such as NSString * and UIView *.

Example 1-14. Using property qualifiers
@property (nonatomic, strong) IBOutlet UILabel *titleView;
@property (nonatomic, weak) id<UIApplicationDelegate> appDelegate;
@property (nonatomic, assign) UIView *danglingReference; 1
@property (nonatomic, assign) BOOL selected; 2
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) HPPhoto *photo; 3
@property (nonatomic, unsafe_unretained) UIView *danglingReference;
1

Wrong usage of assign with a pointer.

2

Correct usage of assign with a value.

3

retain is a relic from the pre-ARC era and is rarely used in modern code. It is added here for completeness.

Getting Your Hands Dirty

OK, now that we have learned a bit about the new lifetime qualifiers for variables and properties, let’s put them to use, update our project, and see the effects.

Photo Model

Let’s create a class called HPPhoto that represents a photo in an album. A photo has a title, a url, and a list of comments. We also override the method dealloc to see what’s going on behind the scenes.

Start by adding a new Objective-C class:

File → New → iOS → Cocoa Touch → Objective-C class

A typical declaration of the class is given in Example 1-15.

Example 1-15. Class HPPhoto
//HPPhoto.h
@interface HPPhoto : NSObject

@property (nonatomic, strong) HPAlbum *album;
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) NSArray *comments;

@end

//HPPhoto.m
@implementation HPPhoto

-(void) dealloc
{
    DDLogVerbose(@"HPPhoto dealloc-ed");
}

@end

Storyboard Update

Add a label and four buttons to the view of the First View Controller in the storyboard. The buttons will trigger creation of these variables while the label will be used to display the result. The final UI should look similar to that shown in Figure 1-6.

hpia 0206
Figure 1-6. Updated view of First View Controller

We also add appropriate IBOutlet and IBAction references in the code, as shown in Example 1-16.

Example 1-16. Reference updates in HPFirstViewController.h
@interface HPFirstViewController : UIViewController

@property (nonatomic, strong) IBOutlet UILabel *resultLabel;

-(IBAction)createStrongPhoto:(id)sender;
-(IBAction)createStrongToWeakPhoto:(id)sender;
-(IBAction)createWeakPhoto:(id)sender;
-(IBAction)createUnsafeUnretainedPhoto:(id)sender;

@end

Method Implementations

For each method, we will do the following:

  1. Create an instance of HPPhoto and assign it to a local reference.

  2. Set the title of the photo.

  3. In the resultLabel, display whether the reference is nil or not. If it is not nil, display the title as well.

Let’s now look at the code for each method (Example 1-17 through Example 1-20). The implementation is largely the same for each of them, the only difference being the type of reference created. Note that we will not create a method to return the reference. We explore the reference within the method where the memory was allocated and the reference created. We also make ample use of NSLog to track the order of lifecycle events.

In addition, we cover a special case where a strong reference is assigned to a weak reference in order to see what happens to the object.

The results from the code are covered in “Output Analysis”.

Example 1-17. Implementation for createStrongPhoto:
-(IBAction)createStrongPhoto:(id)sender
{
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
	HPPhoto * __strong photo = [[HPPhoto alloc] init];
    DDLogDebug(@"Strong Photo: %@", photo);
	photo.title = @"Strong Photo";

	NSMutableString *ms = [[NSMutableString alloc] init];
	[ms appendString:(photo == nil ? @"Photo is nil" : @"Photo is not nil")];
	[ms appendString:@"
"];
	if(photo != nil) {
		[ms appendString:photo.title];
	}
	self.resultLabel.text = ms;
	DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}
Example 1-18. Implementation for createWeakPhoto:
-(IBAction)createWeakPhoto:(id)sender
{
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
	HPPhoto * __weak wphoto = [[HPPhoto alloc] init];
    DDLogDebug(@"Weak Photo: %@", wphoto);
	wphoto.title = @"Weak Photo";

	NSMutableString *ms = [[NSMutableString alloc] init];
	[ms appendString:(wphoto == nil ? @"Photo is nil" : @"Photo is not nil")];
	[ms appendString:@"
"];
	if(wphoto != nil) {
		[ms appendString:wphoto.title];
	}
	self.resultLabel.text = ms;
	DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}
Example 1-19. Implementation for createStrongToWeakPhoto:
-(void)createStrongToWeakPhoto:(id)sender
{
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
	HPPhoto * sphoto = [[HPPhoto alloc] init];
    DDLogDebug(@"Strong Photo: %@", sphoto);
	sphoto.title = @"Strong Photo, Assigned to Weak";

	HPPhoto * __weak wphoto = sphoto;
	DDLogDebug(@"Weak Photo: %@", wphoto);

	NSMutableString *ms = [[NSMutableString alloc] init];
	[ms appendString:(wphoto == nil ? @"Photo is nil" : @"Photo is not nil")];
	[ms appendString:@"
"];
	if(wphoto != nil) {
		[ms appendString:wphoto.title];
	}
	self.resultLabel.text = ms;
    DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}
Example 1-20. Implementation for createUnsafeUnretainedPhoto:
-(void)createUnsafeUnretainedPhoto:(id)sender
{
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
	HPPhoto * __unsafe_unretained wphoto = [[HPPhoto alloc] init];
    DDLogDebug(@"Unsafe Unretained Photo: %@", wphoto);
	wphoto.title = @"Strong Photo";

	NSMutableString *ms = [[NSMutableString alloc] init];
	[ms appendString:(wphoto == nil ? @"Photo is nil" : @"Photo is not nil")];
	[ms appendString:@"
"];
	if(wphoto != nil) {
		[ms appendString:wphoto.title];
	}
	self.resultLabel.text = ms;
    DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}

Output Analysis

The output is shown in Figure 1-7.

hpia 0207
Figure 1-7. Lifetime qualifiers for variables

It’s mostly self-explanatory, with some interesting observations:

  1. A __strong reference (method createStrongPhoto:) ensures that the object is not destroyed until it goes out of scope. The object was dealloced only after the method completed.

  2. A __weak reference (method createWeakPhoto:) does not contribute to the reference count. Because the memory was allocated in the method and pointed to a __weak reference, the reference count was 0 and the object was immediately dealloced, even before it could be used in the very next statement.

  3. In the method createStrongToWeakPhoto:, even though the __weak reference does not increase the reference count, the __strong reference created earlier ensures that the object is not released before the method ends.

  4. The results of the method createUnsafeUnretainedPhoto: are more interesting. Notice that the object was dealloced immediately, but because the memory was still not reclaimed, the reference was usable and did not result in an error.

  5. However, when we call the method again, we see not only that the object has been dealloced but also that the memory has been reclaimed and repurposed. As such, using the reference resulted in an illegal access, causing the app to crash with a signal of SIGABRT. This is possible if the memory is reclaimed at a later time (after the object deallocation but before the object access).

    Looking at Figure 1-8, you will notice that the memory was reclaimed just before the title property was set, resulting in an unrecognized selector sent to instance error because the memory is gone and may be now used by some other object.

    hpia 0208
    Figure 1-8. __unsafe_unretained crash

Zombies

Zombie objects are a debugging feature to help catch memory errors.

Normally when an object’s reference count drops to 0 it is freed immediately, but that makes debugging difficult. If zombie objects are enabled, instead of the object’s memory being instantly freed, it’s just marked as a zombie. Any further attempts to use it will be logged, and you can track down where in the code the object was used past its lifetime.

NSZombieEnabled is an environment variable that controls whether the Core Foundation runtime will use zombies. NSZombieEnabled should not be left in place permanently, as by default no objects will ever be truly deallocated, which will cause your app to use tremendous amounts of memory. Specifically, remember to disable NSZombieEnabled for archived release builds.

To set the NSZombieEnabled environment variable, navigate to Product → Scheme → Edit Scheme. Choose the Run section on the left, and the Diagnostics tab on the right. Select the Enable Zombie Objects entry, as shown in Figure 1-9.

hpia 0209
Figure 1-9. Xcode settings to enable zombies

Rules of Memory Management

Now that we know the details of these lifetime qualifiers, it is important to review some basic rules of memory management.

As per Apple’s official documentation, there are four basic rules of memory management:

  • You own any object you create, using, for example, new, alloc, copy, or mutableCopy.

  • You can take ownership of any object using retain in MRC or a __strong reference in ARC.

  • You must relinquish ownership of an owned object when you no longer need it using release in MRC. It’s not necessary to do anything special in ARC. The ownership will be relinquished after the last reference to the owned object (i.e., the last line in a method).

  • You must not relinquish ownership of any object that you do not own.

To help avoid memory leaks or app crashes, you should keep these rules handy when writing Objective-C code.

Retain Cycles

One of the biggest gotchas with reference counting is that it cannot handle cyclic references, or what are known as retain cycles in Objective-C. In this section, we look at common scenarios where retain cycles may be introduced and best practices to avoid them.

If you closely look at the rules described in the previous section, you’ll see that they are nothing more than the implementation of reference counting. A claim of ownership increments the reference count, whereas relinquishing ownership decrements the reference count. When the reference count goes down to zero, the object is dealloced and the memory is released.

In our app, the HPAlbum entity may have a coverPhoto and an array of photos to represent the album’s cover photo and other photos associated with it. Similarly, HPPhoto may represent a photo that belongs to an album, apart from having other attributes (e.g., the URL, title, comments, etc.). Example 1-21 shows representative code for the entity definitions.

Example 1-21. Retain cycle
@class HPPhoto;

@interface HPAlbum : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *creationTime;
@property (nonatomic, copy) HPPhoto *coverPhoto; 1
@property (nonatomic, copy) NSArray *photos; 2

@end


@interface HPPhoto : NSObject

@property (nonatomic, strong) HPAlbum *album; 3
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray *comments;

@end
1

HPAlbum has a strong reference to the coverPhoto, of the type HPPhoto.

2

It also has references to several other HPPhoto objects within the photos array.

3

HPPhoto has a strong reference to the album to which it belongs.

Let’s take a simple scenario of one album with two photos: p1 (the cover photo) and p2. The reference count is as follows:

  • p1 has strong references in photos and coverPhoto. The reference count is 2.

  • p2 has a strong reference in photos. The reference count is 1.

  • album has strong references in both p1 and p2. The reference count is 2.

We discussed strong references earlier, in “Reference Types”.

To start with, let’s also say that these objects are created in some method named createAlbum. Even if the objects are never used after a certain point, the memory will not be released because the reference count never goes down to 0. Figure 1-10 demonstrates this relationship.

hpia 0210
Figure 1-10. Album and photo references

Rules to Avoid Retain Cycles

The previous section demonstrated where retain cycles may be introduced. In this section, we review the rules for writing code that avoids retain cycles:

  • An object should not have a strong reference to (retain) its parent. Use weak references to refer to the parent (see “Reference Types”).

    In the previous scenario, a photo is contained in an album, and we can consider the photo as the child. As such, the reference from a photo to its album should be weak. A weak reference does not contribute toward reference count.

    The updated reference count becomes:

    1. p1 has strong references in photos and coverPhoto. The reference count is 2.

    2. p2 has a strong reference in photos. The reference count is 1.

    3. album does not have any strong references. The reference count is 0.

      As such, when the album object is no longer used, it is dealloced. Once the album is freed, the reference counts of p1 and p2 drop to 0 and they are dealloced.

  • As a corollary, a hierarchical child object should retain an ancestor.

  • Connection objects should not retain their target. The target should be regarded as the owner. Connection objects include:

    1. Objects that use delegates. The delegate should be considered to be the target, and hence, the owner.

    2. As a corollary of the previous guideline, objects with a target and an action. An example would be a UIButton; it invokes the action method on its target. The button should not retain its target.

    3. Observed items in the observer pattern. The observer is the owner and observes changes on the observed item.

  • Use definitive destroy methods to terminate the cycles.

    In the case of a doubly linked list, there will be retain cycles by definition. Similarly, retain cycles will exist in a circular linked list.

    In such cases, when you know that the object will never be used (when the head of the list is about to go out of scope), write code to break the links in the list. Create a method (say, delink) that unlinks itself from the next item in the list. Do this recursively using a visitor pattern to avoid infinite recursion.

Common Scenarios for Retain Cycles

There are more than a handful of common scenarios that can result in retain cycles. For example, using threads, timers, simple blocks, or delegates might result in retain cycles. Let’s explore each of these scenarios and the steps that need to be taken to prevent the retain cycles.

Delegates

Delegates are probably the most common place for introducing retain cycles. At app start, it’s common to retrieve the latest data from the server and update the UI. A similar refresh may be triggered when, for example, the user taps the refresh button.

Consider this specific scenario: a view controller that shows a list of records and has a refresh button that refreshes the list upon tap.

For the implementation, let there be two classes: HPDataListViewController for the UI and HPDataUpdateOp to simulate the network call. Example 1-22 shows the code for the view controller, and Example 1-23 shows the code for the update operation.

Example 1-22. App refresh invocation
//HPDataListViewController.h
@interface HPDataListViewController : UIViewController 1

@property (nonatomic, strong) HPDataUpdateOp *updateOp; 2
@property (nonatomic, strong) BOOL refreshing;

- (IBAction)onRefreshClick:(id)sender;

@end

//HPDataListViewController.m
@implementation HPDataListViewController

//Code of viewDidLoad omitted for brevity

- (IBAction)onRefreshClicked:(id)sender { 3
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__); 4
    if([self.refreshing == NO]) {
        self.refreshing = YES;
        if(self.updateOp == nil) {
            [self.updateOp = [[HPDataUpdateOp new];
        }
        [self.updateOp startWithDelegate:self
            withSelector:@selector(onDataAvailable:)]; 5
    }
    DDLogDebug(@"%s exit", __PRETTY_FUNCTION__); 4
}

- (void)onDataAvailable:(NSArray *)records { 6
    //Update UI using latest records
    self.refreshing = NO;
    self.updateOp = nil;
}

@end
1

HPDataListViewController shows data in a list.

2

updateOp is the network operation that fetches the records.

3

The method called when the user taps the refreshButton.

4

Log to monitor the execution sequence.

5

The updateOp method can invoke a callback when the results are available.

6

onDataAvailable is the callback method. It updates the view controller state and UI.

Example 1-23. Update operation
//HPDataUpdateOp.m
@implementation HPDataUpdateOp

-(void)startWithDelegate:(id)delegate withSelector:(SEL)selector {
 dispatch_async(
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 1
	 //perform some operation 2
		dispatch_async(dispatch_get_main_queue(), ^{ 3
		 if([delegate respondsToSelector:selector]) {
			 [delegate performSelector:selector
     withObject:[NSArray arrayWithObjects:nil]; 4
		 }
	 });
	});
}

-(void)dealloc {
	DDLogDebug(@"%s called", __PRETTY_FUNCTION__);
}

@end
1

All long-running tasks should be done outside of the main thread.

2

Let’s say it took 2 seconds for the operation.

3

Once results are available, switch the context back to the main thread, and…

4

… invoke the selector.

If you look at the method onRefreshClicked, it passes self to updateOp. At the same time, HPDataListViewController holds a reference to updateOp. This is where the cyclic reference is created.

For the solution, one option is to not have updateOp as a property but instead to create an instance of HPDataUpdateOp in the onRefreshClicked: method, so that updateOp holds a reference to the HPDataListViewController object, but not vice versa. The updated code is shown in Example 1-24.

Example 1-24. App refresh without property
- (IBAction)onRefreshClicked:(id)sender {
    DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
    if(self.refreshing == NO) {
        self.refreshing = YES;
        HPDataUpdateOp *updateOp = [[HPDataUpdateOp new]; 1
        [updateOp startWithDelegate:self withSelector:@selector(onDataAvailable:)];
    }
    DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}
1

Create a local variable so that it is not retained.

This does solve the problem of introducing a retain cycle, but it presents another problem. The updateOp object is never referenced elsewhere, and as a result, the moment control exits the onRefreshClicked: method, its reference count goes down to 0 and it may be dealloced immediately. The output is shown in Figure 1-11.

hpia 0211
Figure 1-11. Result using local variable

HPDataUpdateOp, as demonstrated here, is simplistic. Typically, the app will have a network queue on which the update operation will be queued for execution. And it is possible that by the time the operation is complete, the user may have moved to a different view controller. If that is the case, ideally the view controller should be dealloced immediately, but because it is being used by the operation, it will not be. Now imagine this being true for several view controllers being retained by these operations on the queue. This does not create retain cycles, but does increase peak memory requirements. And this is also a definite bug, because if there is no view controller, the operation should ideally release the object.

So, strictly speaking, this doesn’t work either. What’s the cause? The issue is the strong reference to the HPDataListViewController object being held by the HPDataUpdateOp. But it cannot be weak either, because then there may be no link between these objects at all.

The solution is to have a strong reference to the operation in the delegate (which is the view controller in our case) and a weak reference to the delegate in the operation. When the operation is ready to invoke the callback method, it should get the strong reference to the delegate.

Additionally, we should introduce a cancel method in HPDataUpdateOp that can be called when the view controller is about to be dealloced. Example 1-25 shows the updated code to this effect.

Example 1-25. Final HPDataListViewController and HPDataUpdateOp
//HPDataListViewController
-(IBAction)onRefreshClicked:(id)sender {
 DDLogDebug(@"%s enter", __PRETTY_FUNCTION__);
	self.updateOp = [[HPDataUpdateOp new]; 1
	[self.updateOp startUsingDelegate:self
	    withSelector:@selector(onDataAvailable:)];
	DDLogDebug(@"%s exit", __PRETTY_FUNCTION__);
}

-(void)onDataAvailable:(NSArray *)records {
 DDLogDebug(@"%s called", __PRETTY_FUNCTION__);
	self.resultLabel.text = @"[- onDataAvailable] called";
	self.updateOp = nil; 2
}

-(void)dealloc {
 DDLogDebug(@"%s called", __PRETTY_FUNCTION__);
	if(self.updateOp != nil) {
		[self.updateOp cancel]; 3
	}
}

//HPDataUpdateOp.h
@protocol HPDataUpdateOpDelegate <NSObject>

-(void)onDataAvailable:(NSArray *)records;

@end

@interface HPDataUpdateOp

@property (nonatomic, weak) id<HPDataUpdateOpDelegate> delegate; 4

-(void)startUpdate;
-(void)cancel;

@end

//HPDataUpdateOp.m
@implementation HPDataUpdateOp
-(void)startUpdate {
	dispatch_async(
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		//perform network call, and then report the result
  //NSArray *records = ...
		dispatch_async(dispatch_get_main_queue(), ^{
   id<HPDataUpdateOpDelegate> delegate = self.delegate; 5
   if(!delegate) { 6
				return;
			} else { 7
				[delegate onDataAvailable:records];
			}
		});
	});
}

-(void)cancel { 8
 //cancel inflight network request
	self.delegate = nil;
}
1

Use property for the operation. Operation is owned by the view controller.

2

Set property to nil once job is done. Enables deallocation of the operation object.

3

Cancel operation if view controller is about to be dealloced.

4

Operation keeps a weak reference to the callback delegate.

5

Try to get a strong reference for the delegate.

6

If the original object is still around…

7

… use that to report onDataAvailable.

8

The cancel operation explicitly calls for the callback objects to be dereferenced.

In essence, we implemented the first rule of “Rules to Avoid Retain Cycles”. HPDataListViewController is the owner, while HPDataUpdateOp is the object owned (i.e., the child in the ownership hierarchy).

Figure 1-12 shows the result when the response is ready before the user navigates back. Figure 1-13 shows the output when the user navigates back before the response is available.

hpia 0212
Figure 1-12. Result using updated code with view controller available after operation completes
hpia 0213
Figure 1-13. Result using updated code with view controller deallocated before operation completes

Although this might seem straightforward, it becomes more complicated as the execution goes through various layers and results in a complex object graph. It is very important to ensure that you do not retain the references in the lowest layers of networking, database, and storage that are used by the higher layers of the user interface (i.e., the layers whose usage creates objects).

Blocks

Similar to the problem that arises out of improper use of delegate objects is that of capturing outer variables when using blocks.

Consider the simple code in Example 1-26.

Example 1-26. Variable capturing using blocks
-(void)someMethod {
    SomeViewController *vc = [[SomeViewController alloc] init];
    [self presentViewController:vc animated:YES
        completion:^{
            self.data = vc.data;
            [self dismissViewControllerAnimated:YES completion:nil];
        }];
}

Unfortunately, this again results in long-lived objects—the child view controller will not die off because it is shown to the user, and the parent view controller will not clean up because it is captured in the completion block. In a scenario where SomeViewController may perform long-running tasks such as image processing or complex view rendering, with the parent view controller memory not cleared, the application may run the risk of low memory.

The solution, shown in Example 1-27, is similar to what we discussed in the previous section.

Example 1-27. Variable capturing using blocks
-(void)someMethod {
    SomeViewController *vc = [[SomeViewController alloc] init];

    __weak typeof(self) weakSelf = self; 1

    [self presentViewController:vc animated:YES
        completion:^{
            typeof(self) theSelf = weakSelf; 2

            if(theSelf != nil) { 3
                theSelf.data = vc.data; 4
                [theSelf dismissViewControllerAnimated:YES completion:nil];
            }
        }];
}
1

Grab a weak reference.

2

Grab the strong reference from the weak. Note that __strong is implicit. It increases the reference count…

3

…but only if it was not already nil. If so…

4

…proceed with subsequent operations.

Threads and timers

Inappropriate use of NSThread and NSTimer objects can also result in retain cycles. Some common ways of running async operations include the following:

  • Using dispatch_async on the global queue, unless you write advanced code to manage custom queues

  • Using NSThread to spin off async executions whenever and wherever you want

  • Using NSTimer to execute a piece of code periodically

Consider a news app with a UI that shows the newsfeed of the logged-in user and autorefreshes it every 2 minutes.

Example 1-28 presents some commonly used code for performing a periodic update.

Example 1-28. Using NSTimer
@implementation HPNewsFeedViewController

-(void)startCountdown {
 self.timer = [NSTimer scheduledTimerWithTimeInterval:120
  target:self
  selector:@selector(updateFeed:)
  userInfo:nil repeats:YES];
}

-(void)dealloc {
    [self.timer invalidate];
}

@end

The retain cycle in Example 1-28 is obvious—the object retains the timer and the timer retains the object. Similar to the case of Example 1-22, we cannot solve the problem by not having a property. In fact, we will need the property so that it can be invalidated later.

For our code, the run loop will also retain the timer and will not release it until invalidate is called.

This creates a secondary retained reference to the timer object, resulting in a retain cycle even without an explicit reference in our code.

Caution

NSTimer objects result in indirect references held by the runtime. These are strong references, resulting in the reference count of the target going up not by 1 but by 2. You must invalidate the timer to remove the reference.

For a moment, assume that the code in Example 1-28 belongs to a view controller and the view controller is created several times in the app because of user interaction. Imagine the amount of memory leaked.

And don’t get excited if you use NSThread. Exactly the same problem happens here as well. There are two solutions to the problem:

  • Include a deterministic call to invalidate.

  • Split the code into separate classes.

Let’s explore both.

Do not rely on dealloc to clean up these objects. Why? If a retain cycle has been established, dealloc will never be called and the timers will never be invalidated. Because the run loop keeps track of live timers and threads, they are never destroyed by just niling their references in the code. To solve this, you can create a custom method that will perform this cleanup in a more deterministic manner.

For the case of a view controller, a good place to call this method is when the user moves away from the view controller, either by pressing the Back button or by taking any other action (the point is that the class knows when this happens). Let’s call this method cleanup. An implementation is provided in Example 1-29.

Example 1-29. Cleaning up NSTimer
-(void)didMoveToParentViewController:(UIViewController *) parent { 1
 if(parent == nil) {
  [self cleanup];
 }
}

-(void)cleanup {
 [self.timer invalidate];
}
1

didMoveToParentViewController is called whenever the view controller moves into or out of the parent view controller.

In Example 1-29, we do the cleanup when the user navigates out from this view controller into its parent by overriding the didMoveToParentViewController: method. This call is far more deterministic than the dealloc call.

The other way out is to change the target of the Back button, as shown in Example 1-30.

Example 1-30. Cleaning up by intercepting the Back button
-(id)init {
    if(self = [super init]) {
        self.navigationItem.backBarButtonItem.target = self;
        self.navigationItem.backBarButtonItem.action
            = @selector(backButtonPressDetected:); 1
    }
    return self;
}

-(void)backButtonPressDetected:(id)sender {
    [self cleanup]; 2
    [self.navigationController popViewControllerAnimated:TRUE];
}
1

Intercept the Back button press of the navigation controller.

2

Clean up before popping up the view controller.

The next, cleaner option is to split the task ownership into multiple classes—a task class that actually performs the action, and the owner class that executes the task.

The latter option is preferred because:

  • It is cleaner and has well-defined ownership of responsibilities.

  • The task can be reused across multiple owners whenever needed.

We can break the previous code into two classes: HPNewsFeedViewController shows the latest feed, and HPNewsFeedUpdateTask runs periodically and checks for the latest feed that is fed into the view controller.

To this effect, the refactored code will now be as shown in Example 1-31.

Example 1-31. Refactored code for using timers
//HPNewsFeedUpdateTask.h
@interface HPNewsFeedUpdateTask

@property (nonatomic, weak) id target; 1
@property (nonatomic, assign) SEL selector;

@end

//HPNewsFeedUpdateTask.m
@implementation HPNewsFeedUpdateTask

-(void)initWithTimeInterval:(NSTimeInterval)interval
    target:(id)target selector:(SEL)selector { 2

    if(self = [super init]) {
        self.target = target;
        self.selector = selector;

        self.timer = [NSTimer scheduledTimerWithTimeInterval:interval
            target:self selector:@selector(fetchAndUpdate:)
            userInfo:nil repeats:YES];
    }
    return self;
}

-(void)fetchAndUpdate:(NSTimer *)timer { 3
    //Retrieve feed
    HPNewsFeed *feed = [self getFromServerAndCreateModel];
    __weak typeof(self) weakSelf = self; 4

    dispatch_async(dispatch_get_main_queue(), ^{
        __strong typeof(self) sself = weakSelf;
        if(!sself) {
            return;
        }

        if(sself.target == nil) {
            return;
        }

        id target = sself.target; 5
        SEL selector = sself.selector;

        if([target respondsToSelector:selector]) {
            [target performSelector:selector withObject:feed];
        }
    });
}

-(void)shutdown { 6
    [self.timer invalidate];
    self.timer = nil;
}
@end

//HPNewsFeedViewController.m
@implement HPNewsFeedViewController

-(void)viewDidLoad { 7
    self.updateTask = [HPNewsFeedUpdateTask initWithTimeInterval:120
        target:self selector:@selector(updateUsingFeed:)];
}

-(void)updateUsingFeed:(HPNewsFeed *)feed { 8
    //update the UI
}

-(void)dealloc { 9
    [self.updateTask shutdown];
}
@end

Let’s take a look at a detailed analysis of HPNewsFeedUpdateTask:

  1. The target property 1 is weakly referenced. It is the target that instantiates the task here and owns it.

  2. initWithTimeInterval: 2 is the preferred method to be used. It takes the necessary inputs and starts the timer.

  3. The fetchAndUpdate: method 3 is executed periodically.

  4. When using async blocks, we must ensure that we do not introduce a retain cycle. We have a __weak reference 4 to be used inside the block.

  5. In the method fetchAndUpdate: 7, the local variables for the target and the selector are created before calling respondsToSelector: and performing the operation.

    This is done to avoid a race condition arising during the following possible sequence of execution:

    1. Invoking [target respondsToSelector:selector] in some thread A.

    2. Changing either target or selector in some thread B.

    3. Invoking [target performSelector:selector withObject:feed] in thread A. With this code, even if either target or selector is changed, performSelector will be called on the correct target and selector.

  6. The shutdown method 5 invalidates the timer. The run loop deferences it, resulting in it being the only reference held by the task object.

On the usage side, HPNewsFeedViewController uses HPNewsFeedUpdateTask. The controller is not referenced by any object other than its parent controller. So, when the user navigates out of the controller (say, when the Back button is pressed), the reference count goes down to zero and it is dealloced. This in turn causes the update task to be shut down, which causes the timer to be invalidated, triggering the dealloc chain across all the associated objects (including the timer and the updateTask).

Let’s now look at an analysis of the HPNewsFeedViewController code in Example 1-31:

  1. In the viewDidLoad method 7, the task is initialized, which internally triggers the timer.

  2. The updateUsingFeed: method 8 is the callback invoked periodically by the HPNewsFeedUpdateTask object.

  3. dealloc 8 is responsible for invoking the shutdown method on the task, which internally invalidates the timer. Note that dealloc is deterministic here because the object is not referenced anywhere else.

Tip

When using NSTimer and NSThread, always use a layer of indirection with deterministic invalidation. The indirection layer ensures a weak link, causing the owner object to be dealloced when not used in the app.

Observers

Apart from using delegates and callbacks for subscribing to changes for more complex data, there are two built-in options available for listening to changes in the system. They are termed built-in because the observee does not keep track of observers by writing any custom code—the runtime provides support to manage them. These options are:

  • Key-value observing

  • The notification center

Key-value observing

Objective-C allows adding observers on any NSObject subclassed object using the method addObserver:forKeyPath:options:context:. The observer gets a notification in the method observeValueForKeyPath:ofObject:change:context:. The method removeObserver:forKeyPath:context: can be used to unregister or remove the observer. This is known as key-value observing (KVO).

This is an extremely useful feature, especially for the purposes of debugging, to keep track of an object that may be shared across various sections of your app (e.g., user interface, business logic, persistence, and networking).

An example of such an object may be a custom class that keeps the details of the current state of the app—for example, whether the identity of the user is logged in or not, the user that is logged in, items in the shopping cart in an ecommerce app, or the user to whom the last message was sent in a messaging app. For debugging, you may add an observer to this object to keep track of any changes or updates.

KVO is also useful in bidirectional data binding. The views allow attaching delegates to respond to user interactions that can result in model updates. KVO can be used for the reverse binding to update the UI whenever the model is updated.

From the official documentation:

The key-value observing addObserver:forKeyPath:options:context: method does not maintain strong references to the observing object, the observed objects, or the context. You should ensure that you maintain strong references to the observing, and observed, objects, and the context as necessary.

This means that the observer must live long enough to continue to monitor the changes. You should take extra care in deciding where you would like the observer to be dereferenced last for memory relinquishment.

Example 1-32 implements KVO using a central ObserverManager class that returns an ObserverObserveeHandle that can be referred to by the owner. When the observation initiator (the view controller in the example) needs to observe a keyPath, it invokes the addObserverToObject:forKey: method and stores the ObserverObserveeHandle, which is dealloced when the view controller is. The handle removes the observer during deallocation.

Essentially, we are trying to solve a similar problem of reference routing as that encountered in the case of NSTimer. However, there is a weak reference established, and as such the observer may be dealloced prematurely if not handled appropriately.

Example 1-32. Key-value observer
@interface ObserverObserveeHandle

@property (nonatomic, strong) MyObserver *observer;
@property (nonatomic, strong) NSObject *obj;
@property (nonatomic, copy) NSString *keyPath;

-(id)initWithObserver:(MyObserver *)observer
 target:(NSObject *)obj
 keyPath:(NSString *)keyPath;

@end

@implementation ObserverObserveeHandle

-(id)initWithObserver:(MyObserver *)observer
 target:(NSObject *)obj
 keyPath:(NSString *)keyPath {
  //Omitted for brevity
}

-(void)removeObserver {
 [self.obj removeObserver:self forKeyPath:self.keyPath context:nil];
 self.obj = nil;
}

-(void)dealloc {
 [self removeObserver];
}
@end

@interface ObserverManager
//Omitted for brevity
@end

@implementation ObserverManager
NSMutableArray *observers;

+(ObserverObserveeHandle)addObserverToObject:(NSObject *)obj
 forKey:(NSString *)keyPath {
 MyObserver *observer = [[MyObserver alloc] init];
 [obj addObserver:observer forKeyPath:keyPath
  options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
  context:NULL];

 ObserverObserveeHandle *details = [[ObserverObserveeHandle alloc]
  initWithObserver:observer target:obj keyPath:keyPath];
 [observers addObject:details];

 return details;
}

@interface SomeViewController

@property (nonatomic, strong) IBOutlet UILabel *resultLabel;
@property (nonatomic, strong) ObserverObserveeHandle *resultLabelMonitor;

@end

@implementation SomeViewController

-(void)viewDidLoad {
 self.resultLabelMonitor = [ObserverManager
   addObserverToObject:self.nameTextField
   forKey:@"text"];
}

@end
Caution

Whenever you add a key-value observer to a target, the target must have a life at least as long as that of the observer because it should be possible to remove the observer from the target. This can result in the target having a longer life than originally intended, and is something to watch out for.

Example 1-32 seems to provide a great solution because it takes away the burden of cleanup by utilizing well-written code that is bound not to fail. There is, however, still a gotcha. The catch with this code is that for all notifications to the observer, the exact same piece of code executes (i.e., the code defined in the MyObserver class).

How can we solve this problem? Think about it. Hint: use blocks. And in the block, if you need to invoke a method that uses self, do not forget to create a weak reference to self before referring to it internally in the block.

As such, the code for registering the observer may be updated to that shown in Example 1-33.

Example 1-33. Key-value observer with block
@implementation SomeViewController

-(void)viewDidLoad
{
    __weak typeof(self) weakSelf = self;
    self.resultLabelMonitor = [ObserverManager
        addObserverToObject:self.nameTextField
        forKey:@"text" block:^(NSDictionary *changes) {

            typeof(self) sSelf = weakSelf;
            if(sself) {
                NSLog(@"Text changed to %@",
                    [changes objectForKey:NSKeyValueChangeNewKey]);

                //use sSelf if need be
                sSelf.resultLabel.text = @"Name changed";
            }
        }];
}

@end

Notification center

The second option is to use the notification center. An object can register as an observer with the notification center (an NSNotificationCenter object) and receive NSNotification objects. Similar to the key-value observer, the notification center does not keep a strong reference to the observer. This means that we are not responsible for ensuring that the observer is not dealloced earlier or later than intended.

The solution pattern is similar to what we discussed in the previous subsection.

Returning Errors

When working with methods that take NSError ** parameters and fill in the error variable if there is one, always use the __autoreleasing qualifier. The most common place to use this pattern is when you need to process an input and return a value with a possibility of error.

A typical method will have a signature similar to that shown in Example 1-34.

Example 1-34. Returning errors
-(Matrix *)transposeMatrix:(Matrix *)matrix error:(NSError * __autoreleasing *) error
{
    //process
    //if error
    *error = [[NSError alloc] initWithDomain:@"transpose" code:123 userInfo:nil];
}

Pay close attention to the syntax. The keyword __autoreleasing is squeezed between the two asterisks. Always remember this:

NSError * __autoreleasing *error;

As you will notice, the variable and property qualifiers play an important role in helping with the life cycle management of an object and ensuring the object’s precise lifetime—neither too short nor too long. Whenever in doubt, go back to the drawing board, get back to the basics, and define your properties and variables accordingly. At times, you may need to create properties with strong references and lengthen the life, while at other times you may need to use weak references and ensure appropriate memory usage and no memory leaks.

Weak Type: id

There are several cases where we use the type id. It is not uncommon to see this used in the Cocoa framework itself. For example, in the Xcode-generated code, the IBAction methods have a parameter of type id to denote the sender.

Another scenario is working with objects in an NSArray.6 Consider the code in Example 1-35.

Example 1-35. Using an object in an NSArray
@interface HPDataListViewController
    : UITableViewController <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, copy) NSArray *input;

@end

@implementation HPDataListViewController

-(void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{
    NSUInteger value = [self.input objectAtIndex:indexPath.row].someProperty;
    //proceed
}

@end

In the method tableView:didSelectRowAtIndexPath:, we expect the array input to consist of objects of some type—let’s call it ClassX—that has a property someProperty.

This code looks great, and if you try it out, you will most likely get the correct result. We know that as long as the object at the corresponding index responds to the someProperty selector, this code will work as intended. But if the object does not respond to the selector it may result in an app crash.

It is assumed that the compiler does not need to know the type information, because the runtime will know which object and method to invoke. But the fact is that the compiler does require a fair bit of detail—specifically, it must know the sizes of all the parameters and the type of the return value so that it can have correct instruction sets for pushing and popping the values on to and off of the stack. For example, if the method takes two parameters of type int, 8 bytes need to be pushed to the stack.

Normally, we do not need to take any steps for this to happen, though. The compiler obtains the parameter information by looking at the name of the method we are trying to invoke, searching through the included headers for methods matching the invoked method name, and then getting the parameter lengths from the first matching method it finds.

The good part is that this works most of the time. It fails when there are multiple classes that have exactly the same method signature (i.e., name and parameters).

Consider a scenario in which, at compile time, the compiler zeros in not on the ClassX class but say, for example, the ClassY object. The method may not return an NSUInteger, but maybe an NSInteger or even an NSString. In another scenario where we expect an NSUInteger, it may return a reference to an object that we are supposed to invalidate or cleanup ourselves (e.g., CGColor or CGContext), resulting in a memory leak.

Solution to the Problem

Why does the type mismatch happen? How can the compiler be so naïve? It does the hard work of resolving the object for the message to be sent. The compiler is responsible for generating accurate instructions (i.e., the correct values to pass to the objc_msgSend method).

Fortunately, it is not hard to solve the problem of incorrect type matching by the compiler. There are two parts to the solution.

First, we must configure the compiler to report an error if it finds multiple matches for selectors on id objects. This is controlled by the Strict Selector Matching setting, which is turned off by default. It corresponds to the -Wstrict-selector-match flag passed to the compiler. Turn it on to generate warnings when the compiler finds two selectors that have different parameter or return types.

Figure 1-14 shows the project settings in Xcode.

hpia 0214
Figure 1-14. Xcode settings for strict selector matching

There are a few issues related to the use of this option:

  • Built-in frameworks will result in several warnings, even though the majority of them will never cause you any trouble.

  • You will still not be able to catch issues when working with a class rather than an object.

  • It will not help if you did not import the header with the correct definition.

That brings us to the second part of the solution: give enough information to the compiler to generate messages against correct types. You can do that by using a strong type (ClassX in our case). Example 1-36 shows the changes to be made to the code.

Example 1-36. Using strong types
-(void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{
    ClassX *item =  (ClassX *) [self.input objectAtIndex:indexPath.row];
    NSUInteger value = item.someProperty;
    //Proceed
}

In a nutshell, when working with methods that are commonly named, be sure to avoid using id. Use a specific class instead.

Object Longevity and Leaks

The longer the objects live in memory, the higher are the chances of memory never being cleaned up. Avoid long-lived objects as much as possible. Of course, you will need references to key operations all over your code, and you will not want to waste time re-creating them each time. Due diligence must be done on their usage.

One of the most common scenarios of long-lived objects is singletons. Loggers are a good example of this—they are created once but never destroyed. We discuss these kinds of scenarios in depth in the next section.

Another scenario is using global variables. Global variables are dreaded in programming.

Tip

For a variable to qualify as global, it must meet the following criteria:

  • It is not owned by another object.

  • It is not a constant.

  • There is exactly one in the entire app, not just per app component.

If a variable does not meet these requirements, it should not be made into a global variable.

Complex object graphs provide fewer opportunities for reclaiming memory, and hence increase the risk of crashes due to memory exhaustion. App responsiveness can suffer if the main execution thread is forced to wait for subthread operations such as network or database access.

Singletons

The singleton pattern is a design pattern that restricts the instantiation of a class to one object. In practice, the instantiation occurs near the start of the app and the object never dies.

Having an object that has a very long life compared to the overall life of the app is never a good idea. And if the object becomes the source of other objects (more like a service locator), there is risk of memory buildup if the locator is not correctly implemented.

Singletons are necessary—there is no doubt about it. But how they are implemented plays an important role in determining how they will be used.

Before we fully discuss the problems that singletons introduce, let’s take a step back to better understand what singletons are and why we really need them.

Singletons are useful when exactly one object is needed to coordinate actions across the system. We need singletons in several scenarios:

  • Queuing operations (e.g., logging and instrumentation)

  • Shared resource access (e.g., cache)

  • Pooling constrained resources (e.g., thread pool or connection pool)

Singletons, once built up and ready to use, continue to live until the app is shut down. Loggers, instrumentation services, and the cache are good examples of singletons.

More importantly, these singletons are generally initialized at app startup, as other components that intend to use them get them ready. This increases the app load time.

What is the way out? There is no one solution that can be used. The memory constraints become visible as you start integrating more and more off-the-shelf solutions, especially if you do not have their source code.

Here are some guidelines that you can use:

  • Avoid singletons as much as possible.

  • Identify the sections that need memory—for example, an in-memory-buffer for instrumentation (used before flushing to the server).

    Look for ways to minimize the memory overhead. Note that you will have to trade off with something else. If you keep the buffer smaller, you will have to make more frequent server trips.

  • Avoid object-level properties as much as possible, as they will stay with the object forever. Try to use local variables.

Finding Mystery Retains

A class may have been well designed, and the objects well retained, and there may or may not be memory leaks. It may, however, be a good idea to be able to get the reference graph. This brings us to the question, is it possible to find all the retains on an object?

The answer lies in the pre-ARC method retain. All we have to do is get the count of the method invocation. ARC does not allow you to override or call it, but you can temporarily disable ARC for the project (see Figure 1-15 for the details).

hpia 0215
Figure 1-15. Disabling ARC in a project

Then, add the code given in Example 1-39 to all your custom classes. The code not only logs the call to the retain method but also prints the call stack so that you can get details of where exactly it has been invoked, and not only how many times.

Example 1-39. Use retain to get the reference count
#if !__has_feature(objc_arc)
-(id) retain
{
    DDLogInfo(@"%s %@", __PRETTY_FUNCTION__, [NSThread callStackSymbols]);
	return [super retain];
}
#endif

Best Practices

By following these best practices, you will largely avoid any trouble with memory leaks, retain cycles, and large memory requirements (you may want to print out a copy of this section to hang in your workstation for quick reference):

  • Avoid huge singletons. Specifically, do not have God objects (i.e., objects that do too much or have too much state information). This is an antipattern, a common solution pattern that gets counterproductive sooner rather than later.

    Helper singletons like loggers, instrumentation services, and task queues are good, but global state objects are bad.

  • Use __strong references to child objects.

  • Use __weak references to parent objects.

  • Use __weak references to off-the-graph objects such as delegates.

  • For scalar properties (NSInteger, SEL, CGFloat, etc.), use the assign qualifier.

  • For block properties, use the copy qualifier.

  • When declaring methods with NSError ** parameters, use __autoreleasing with the correct syntax: NSError* __autoreleasing *.

  • Avoid directly referencing outer variables in a block. weakify them outside the block and then strongify them inside the block. See the libextobjc library for helper macros @weakify and @strongify.

  • Follow these guidelines for deterministic cleanup:

    • Invalidate timers.

    • Remove observers (specifically, unregister for notifications).

    • Unlink callbacks (specifically, nil any strong delegates).

Memory Usage in Production

Note that whatever setup you do to your Xcode, it will work only when debugging the app on a device. You really do not know what additional variations may arise until the app has gone live and been used by tens of thousands of users, if not millions.

To be able to profile your app in varying scenarios, use instrumentation. Send periodic information about your app to the server—memory consumed, especially if it grows beyond a threshold, along with a breadcrumb navigation trail is a great option.

As an example, if the memory consumption is beyond 40 MB, you may want to send the details of what screens the user navigated to and key operations performed. Another option is to keep track of memory consumption and log it locally at periodic intervals, and then upload the data to the server. You can use the code in Example 1-40 to find the memory used as well as the available memory.

Tip

Instrument your app to include the memory used, as well as other statistics, on low-memory warnings, and send this information to the server on app relaunch. Use this data to identify common scenarios and/or corner cases where the app runs out of memory.

Example 1-40. Track available memory and memory used
//HPMemoryAnalyzer.m

#import <mach/mach.h>

vm_size_t getUsedMemory() {
 task_basic_info_data_t info;
 mach_msg_type_number_t size = sizeof(info);
 kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO,
  (task_info_t) &info, &size);

 if(kerr == KERN_SUCCESS) {
  return info.resident_size;
 } else {
  return 0;
 }
}

vm_size_t getFreeMemory() {
 mach_port_t host = mach_host_self();
 mach_msg_type_number_t size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
 vm_size_t pagesize;
 vm_statistics_data_t vmstat;

 host_page_size(host, &pagesize);
 host_statistics(host, HOST_VM_INFO, (host_info_t) &vmstat, &size);

 return vmstat.free_count * pagesize;
}

Summary

Now that you have a deeper understanding of how memory is managed by the iOS runtime and the basic rules to avoid retain cycles (the single largest source of memory leaks), you can now minimize your app’s memory consumption and lower its average and peak memory requirements.

You can use zombies to keep track of overreleased objects, which are one of the most common sources of app crashes.

The code in this lesson can be used to track memory usage in production, not just in the test lab.

1 iOS Developer Library, “Technical Note TN2151: Understanding and Analyzing iOS Application Crash Reports”.

2 Stack Overflow, “iOS Equivalent to Increasing Heap Size”.

3 The term assigned is used loosely here. We will explore it later.

4 The complete specification on Automatic Reference Counting is available on the LLVM site.

5 iOS Developer Library, “Transitioning to ARC Release Notes”.

6 iOS 9 introduces lightweight generics for Objective-C collections for interoperability with Swift. See iOS Developer Library, “Lightweight Generics”.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset