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 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.
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:
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.
main
()
{
method1
();
}
method1
()
{
method2
();
}
All variables are loaded on the method stack frame, and hence contribute to the stack space consumed.
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.
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.
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.
@interface
AClass
@property
(
nonatomic
,
assign
)
NSInteger
anInteger
;
@property
(
nonatomic
,
copy
)
NSString
*
aString
;
@end
//some other class
-
(
AClass
*
)
createAClassWithInteger
:
(
NSInteger
)
i
string
:
(
NSString
*
)
s
{
AClass
*
result
=
[
AClass
new
]
;
result
.
anInteger
=
i
;
result
.
aString
=
s
;
}
-
(
void
)
someMethod
:
(
NSArray
*
)
items
{
NSInteger
total
=
0
;
NSMutableString
*
finalValue
=
[
NSMutableString
string
]
;
for
(
AClass
*
obj
in
items
)
{
total
+
=
obj
.
anInteger
;
[
finalValue
appendString
:
obj
.
aString
]
;
}
}
The class AClass
has two properties.
anInteger
is of type NSInteger
, which is passed by value.
aString
is of type NSString *
, which is passed by reference.
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.
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.
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.
someMethod:
processes an array of AClass
objects.
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
.
When aString
is used, it is passed by reference. In this example, appendString:
uses the reference to the aString
object.
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.
NSString
*
message
=
@"
Objective-C is a verbose yet awesome language
"
;
NSString
*
messageRetained
=
[
message
retain
]
;
[
messageRetained
release
]
;
[
message
release
]
;
NSLog
(
@"
Value of message: %@
"
,
message
)
;
Object created, ownership claimed by message
, reference count of 1.
Ownership claimed by messageRetained
, reference count increases to 2.
Ownership relinquished by messageRetained
, reference count decreases to 1.
Ownership relinquished by message
, reference count decreases to 0.
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.
//part of a class Person
-
(
NSString
*
)
address
{
NSString
*
result
=
[
[
NSString
alloc
]
initWithFormat
:
@"
%@
%@
%@, %@
"
,
self
.
line1
,
self
.
line2
,
self
.
city
,
self
.
state
]
;
return
result
;
}
-
(
void
)
showPerson:
(
Person
*
)
p
{
NSString
*
paddress
=
[
p
address
]
;
NSLog
(
@"
Person's Address: %@
"
,
paddress
)
;
[
paddress
release
]
;
}
Object first created; reference count of memory pointed to by result
is 1.
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
.
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 release
s it. If the object had a reference count of 1, it will become 0 and object will be dealloc
ed.
Official Apple and LLVM documentation prefers the term ownership. The terms ownership and reference count are used interchangeably in the lesson.
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.
-
(
NSString
*
)
address
{
NSString
*
result
=
[
NSString
stringWithFormat
:
@"
%@
%@
%@, %@
"
,
self
.
line1
,
self
.
line2
,
self
.
city
,
self
.
state
]
;
return
result
;
}
-
(
void
)
showPerson:
(
Person
*
)
p
{
NSString
*
paddress
=
[
p
address
]
;
NSLog
(
@"
Person's Address: %@
"
,
paddress
)
;
}
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 retain
s 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.
-(
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:
You own the object (NSString
, in this case) returned by the alloc
method.
To ensure no memory leak, you must relinquish the ownership before losing the reference.
However, if you use release
, the object will be dealloc
ed before return and the method, as a result, will return an invalid reference.
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 dealloc
ed.
The autorelease pool block is a tool that allows you to relinquish ownership of an object but avoid it being dealloc
ed immediately. This is a very useful feature when returning objects from a method.
It also ensures that the objects created within the block are dealloc
ed 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.
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 dealloc
ed, 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 dealloc
ed at the end, resulting in no memory leak.
Like other code blocks, autoreleasepool
blocks can be nested, as shown in Example 1-8.
@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:
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.
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.
//Bad code
{
@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
{
@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
]
;
}
}
}
}
This code is bad because there is only one autoreleasepool
and the memory cleanup happens after all the iterations of the loop are complete.
In this case, there are two autoreleasepool
s. The inner autoreleasepool
ensures that the memory cleanup happens after each iteration. This results in less memory requirements.
-(
void
)
myThreadStart:
(
id
)
obj
{
@autoreleasepool
{
//New thread's code
}
}
//Somewhere else
{
NSThread
*
myThread
=
[[
NSThread
alloc
]
initWithTarget
:
self
selector
:
@selector
(
myThreadStart
:)
object
:
nil
];
[
myThread
start
];
}
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.
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).
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”).
//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.
-
(
NSString
*
)
address
{
NSString
*
result
=
[
[
NSString
alloc
]
initWithFormat
:
@"
%@
%@
%@, %@
"
,
self
.
line1
,
self
.
line2
,
self
.
city
,
self
.
state
]
;
return
result
;
}
-
(
void
)
showPerson:
(
Person
*
)
p
{
NSString
*
paddress
=
[
p
address
]
;
NSLog
(
@"
Person's Address: %@
"
,
paddress
)
;
}
ARC introduced a new reference type: weak references. Understanding the available reference types is important to memory management. The supported types are:
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.
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.
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 dealloc
ed.
__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.
Person
*
__strong
p1
=
[
[
Person
alloc
]
init
]
;
Person
*
__weak
p2
=
[
[
Person
alloc
]
init
]
;
Person
*
__unsafe_unretained
p3
=
[
[
Person
alloc
]
init
]
;
Person
*
__autoreleasing
p4
=
[
[
Person
alloc
]
init
]
;
Object created has a reference count of 1 and will not be dealloc
ed until the point p1
is last referenced.
Object created has a reference count of 0, will be immediately dealloc
ed and p2
will be set to nil
.
Object created has a reference count of 1, will be immediately dealloc
ed but p3
will not be set to nil
.
Object created has a reference count of 1 and will be automatically released once the method returns.
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 *
.
@property
(
nonatomic
,
strong
)
IBOutlet
UILabel
*
titleView
;
@property
(
nonatomic
,
weak
)
id
<
UIApplicationDelegate
>
appDelegate
;
@property
(
nonatomic
,
assign
)
UIView
*
danglingReference
;
@property
(
nonatomic
,
assign
)
BOOL
selected
;
@property
(
nonatomic
,
copy
)
NSString
*
name
;
@property
(
nonatomic
,
retain
)
HPPhoto
*
photo
;
@property
(
nonatomic
,
unsafe_unretained
)
UIView
*
danglingReference
;
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.
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.
//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
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.
We also add appropriate IBOutlet
and IBAction
references in the code, as shown in Example 1-16.
@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
For each method, we will do the following:
Create an instance of HPPhoto
and assign it to a local reference.
Set the title
of the photo.
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”.
-(
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__
);
}
-(
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__
);
}
-(
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__
);
}
-(
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__
);
}
The output is shown in Figure 1-7.
It’s mostly self-explanatory, with some interesting observations:
A __strong
reference (method createStrongPhoto:
) ensures that the object is not destroyed until it goes out of scope. The object was dealloc
ed only after the method completed.
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 dealloc
ed, even before it could be used in the very next statement.
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.
The results of the method createUnsafeUnretainedPhoto:
are more interesting. Notice that the object was dealloc
ed immediately, but because the memory was still not reclaimed, the reference was usable and did not result in an error.
However, when we call the method again, we see not only that the object has been dealloc
ed 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.
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.
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.
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 dealloc
ed 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.
@class
HPPhoto
;
@interface
HPAlbum
:
NSObject
@property
(
nonatomic
,
copy
)
NSString
*
name
;
@property
(
nonatomic
,
strong
)
NSDate
*
creationTime
;
@property
(
nonatomic
,
copy
)
HPPhoto
*
coverPhoto
;
@property
(
nonatomic
,
copy
)
NSArray
*
photos
;
@end
@interface
HPPhoto
:
NSObject
@property
(
nonatomic
,
strong
)
HPAlbum
*
album
;
@property
(
nonatomic
,
strong
)
NSURL
*
url
;
@property
(
nonatomic
,
copy
)
NSString
*
title
;
@property
(
nonatomic
,
copy
)
NSArray
*
comments
;
@end
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.
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:
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
does not have any strong references. The reference count is 0.
As such, when the album
object is no longer used, it is dealloc
ed. Once the album
is freed, the reference counts of p1
and p2
drop to 0 and they are dealloc
ed.
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:
Objects that use delegates. The delegate should be considered to be the target, and hence, the owner.
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.
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.
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 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.
//HPDataListViewController.h
@interface
HPDataListViewController
:
UIViewController
@property
(
nonatomic
,
strong
)
HPDataUpdateOp
*
updateOp
;
@property
(
nonatomic
,
strong
)
BOOL
refreshing
;
-
(
IBAction
)
onRefreshClick:
(
id
)
sender
;
@end
//HPDataListViewController.m
@implementation
HPDataListViewController
//Code of viewDidLoad omitted for brevity
-
(
IBAction
)
onRefreshClicked:
(
id
)
sender
{
DDLogDebug
(
@"
%s enter
"
,
__PRETTY_FUNCTION__
)
;
if
(
[
self
.
refreshing
=
=
NO
]
)
{
self
.
refreshing
=
YES
;
if
(
self
.
updateOp
=
=
nil
)
{
[
self
.
updateOp
=
[
[
HPDataUpdateOp
new
]
;
}
[
self
.
updateOp
startWithDelegate
:
self
withSelector
:
@selector
(
onDataAvailable
:
)
]
;
}
DDLogDebug
(
@"
%s exit
"
,
__PRETTY_FUNCTION__
)
;
}
-
(
void
)
onDataAvailable:
(
NSArray
*
)
records
{
//Update UI using latest records
self
.
refreshing
=
NO
;
self
.
updateOp
=
nil
;
}
@end
HPDataListViewController
shows data in a list.
updateOp
is the network operation that fetches the records.
The method called when the user taps the refreshButton
.
Log to monitor the execution sequence.
The updateOp
method can invoke a callback when the results are available.
onDataAvailable
is the callback method. It updates the view controller state and UI.
//HPDataUpdateOp.m
@implementation
HPDataUpdateOp
-
(
void
)
startWithDelegate:
(
id
)
delegate
withSelector:
(
SEL
)
selector
{
dispatch_async
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_DEFAULT
,
0
)
,
^
{
//perform some operation
dispatch_async
(
dispatch_get_main_queue
(
)
,
^
{
if
(
[
delegate
respondsToSelector
:
selector
]
)
{
[
delegate
performSelector
:
selector
withObject
:
[
NSArray
arrayWithObjects
:
nil
]
;
}
}
)
;
}
)
;
}
-
(
void
)
dealloc
{
DDLogDebug
(
@"
%s called
"
,
__PRETTY_FUNCTION__
)
;
}
@end
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.
-
(
IBAction
)
onRefreshClicked:
(
id
)
sender
{
DDLogDebug
(
@"
%s enter
"
,
__PRETTY_FUNCTION__
)
;
if
(
self
.
refreshing
=
=
NO
)
{
self
.
refreshing
=
YES
;
HPDataUpdateOp
*
updateOp
=
[
[
HPDataUpdateOp
new
]
;
[
updateOp
startWithDelegate
:
self
withSelector
:
@selector
(
onDataAvailable
:
)
]
;
}
DDLogDebug
(
@"
%s exit
"
,
__PRETTY_FUNCTION__
)
;
}
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 dealloc
ed immediately. The output is shown in Figure 1-11.
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
dealloc
ed 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 dealloc
ed. Example 1-25 shows the updated code to this effect.
//HPDataListViewController
-
(
IBAction
)
onRefreshClicked:
(
id
)
sender
{
DDLogDebug
(
@"
%s enter
"
,
__PRETTY_FUNCTION__
)
;
self
.
updateOp
=
[
[
HPDataUpdateOp
new
]
;
[
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
;
}
-
(
void
)
dealloc
{
DDLogDebug
(
@"
%s called
"
,
__PRETTY_FUNCTION__
)
;
if
(
self
.
updateOp
!
=
nil
)
{
[
self
.
updateOp
cancel
]
;
}
}
//HPDataUpdateOp.h
@protocol
HPDataUpdateOpDelegate
<
NSObject
>
-
(
void
)
onDataAvailable
:
(
NSArray
*
)
records
;
@end
@interface
HPDataUpdateOp
@property
(
nonatomic
,
weak
)
id
<
HPDataUpdateOpDelegate
>
delegate
;
-
(
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
;
if
(
!
delegate
)
{
return
;
}
else
{
[
delegate
onDataAvailable
:
records
]
;
}
}
)
;
}
)
;
}
-
(
void
)
cancel
{
//cancel inflight network request
self
.
delegate
=
nil
;
}
Use property for the operation. Operation is owned by the view controller.
Set property to nil
once job is done. Enables deallocation of the operation object.
Cancel operation if view controller is about to be dealloc
ed.
Operation keeps a weak reference to the callback delegate.
Try to get a strong reference for the delegate
.
If the original object is still around…
… use that to report onDataAvailable
.
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.
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).
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.
-(
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.
-
(
void
)
someMethod
{
SomeViewController
*
vc
=
[
[
SomeViewController
alloc
]
init
]
;
__weak
typeof
(
self
)
weakSelf
=
self
;
[
self
presentViewController
:
vc
animated
:
YES
completion
:
^
{
typeof
(
self
)
theSelf
=
weakSelf
;
if
(
theSelf
!
=
nil
)
{
theSelf
.
data
=
vc
.
data
;
[
theSelf
dismissViewControllerAnimated
:
YES
completion
:
nil
]
;
}
}
]
;
}
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.
@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 invalidate
d 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.
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 invalidate
d. Because the run loop keeps track of live timers and threads, they are never destroyed by just nil
ing 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.
-
(
void
)
didMoveToParentViewController:
(
UIViewController
*
)
parent
{
if
(
parent
=
=
nil
)
{
[
self
cleanup
]
;
}
}
-
(
void
)
cleanup
{
[
self
.
timer
invalidate
]
;
}
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.
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.
//HPNewsFeedUpdateTask.h
@interface
HPNewsFeedUpdateTask
@property
(
nonatomic
,
weak
)
id
target
;
@property
(
nonatomic
,
assign
)
SEL
selector
;
@end
//HPNewsFeedUpdateTask.m
@implementation
HPNewsFeedUpdateTask
-
(
void
)
initWithTimeInterval:
(
NSTimeInterval
)
interval
target:
(
id
)
target
selector:
(
SEL
)
selector
{
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
{
//Retrieve feed
HPNewsFeed
*
feed
=
[
self
getFromServerAndCreateModel
]
;
__weak
typeof
(
self
)
weakSelf
=
self
;
dispatch_async
(
dispatch_get_main_queue
(
)
,
^
{
__strong
typeof
(
self
)
sself
=
weakSelf
;
if
(
!
sself
)
{
return
;
}
if
(
sself
.
target
=
=
nil
)
{
return
;
}
id
target
=
sself
.
target
;
SEL
selector
=
sself
.
selector
;
if
(
[
target
respondsToSelector
:
selector
]
)
{
[
target
performSelector
:
selector
withObject
:
feed
]
;
}
}
)
;
}
-
(
void
)
shutdown
{
[
self
.
timer
invalidate
]
;
self
.
timer
=
nil
;
}
@end
//HPNewsFeedViewController.m
@
implement
HPNewsFeedViewController
-
(
void
)
viewDidLoad
{
self
.
updateTask
=
[
HPNewsFeedUpdateTask
initWithTimeInterval
:
120
target
:
self
selector
:
@selector
(
updateUsingFeed
:
)
]
;
}
-
(
void
)
updateUsingFeed
:
(
HPNewsFeed
*
)
feed
{
//update the UI
}
-
(
void
)
dealloc
{
[
self
.
updateTask
shutdown
]
;
}
@end
Let’s take a look at a detailed analysis of HPNewsFeedUpdateTask
:
The target
property is weakly referenced. It is the target
that instantiates the task here and owns it.
initWithTimeInterval:
is the preferred method to be used. It takes the necessary inputs and starts the timer.
When using async blocks, we must ensure that we do not introduce a retain cycle. We have a __weak
reference to be used inside the block.
In the method fetchAndUpdate:
, 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:
Invoking [target respondsToSelector:selector]
in some thread A.
Changing either target
or selector
in some thread B.
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
.
The shutdown
method invalidate
s 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 dealloc
ed. This in turn causes the update task to be shut down, which causes the timer to be invalidate
d, 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:
In the viewDidLoad
method , the task is initialized, which internally triggers the timer.
The updateUsingFeed:
method is the callback invoked periodically by the HPNewsFeedUpdateTask
object.
dealloc
is responsible for invoking the shutdown
method on the task, which internally invalidate
s the timer. Note that dealloc
is deterministic here because the object is not referenced anywhere else.
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
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 dealloc
ed 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 dealloc
ed prematurely if not handled appropriately.
@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
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.
@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
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
dealloc
ed earlier or later than intended.
The solution pattern is similar to what we discussed in the previous subsection.
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.
-(
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.
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.
@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.
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.
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.
-(
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.
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.
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.
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.
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).
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.
#if !__has_feature(objc_arc)
-(
id
)
retain
{
DDLogInfo
(
@"%s %@"
,
__PRETTY_FUNCTION__
,
[
NSThread
callStackSymbols
]);
return
[
super
retain
];
}
#endif
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. weak
ify them outside the block and then strong
ify 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).
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.
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.
//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
;
}
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”.