Memory Management - ADDITIONAL TOPICS - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 3: ADDITIONAL TOPICS

19. Memory Management

To get the most efficient use of the limited amount of random access memory (RAM) that machines generally have, computer systems typically use some kind of memory management scheme. This is usually automatic garbage collection, which seeks out memory that is no longer used by a program and makes it available for reuse. Many scripting languages that run on a personal computer simply ignore the issue of managing RAM, assuming that automatic garbage collection will be implemented, and that it will solve the problem. In the case of a personal computer, that is not an unreasonable assumption. PCs generally have substantial RAM. A MacBook Air 2, for example, has a minimum of 4 Gigabytes and can have 8. Automatic garbage collection is typically implemented on personal computers and usually works well.

Mobile devices, however, are a different world. Devices like iPhone 5s and 6s have 1 Gigabyte of RAM. The iPad has recently had 1 Gigabyte as well until the iPad Air, which has 2 Gigabytes. Engineers who work on memory and processor chips generally believe that it is unlikely that iOS devices will have substantially more memory any time soon because the ARM chips used in these devices put both the memory and processor on the same die. Making the memory larger would mean making the chip larger, when it is generally considered desirable to make the chip smaller or use any additional space to make the processor more powerful. (There have, however, been recent rumors claiming that upcoming iPhones will have 2 Gigabytes of RAM.)

Devices like iPhones and iPads have remarkably high-quality media, including high resolution displays and cameras. The iPhone 6 Plus has a resolution of 401 pixels per inch. The iPhone 6 has a camera with a slow-motion version capable of operating at 240 frames per second, which has obvious high requirements for memory. Given that this camera is in a phone that users carry everywhere in their pocket, it is natural to want to use it in apps. Thus apps for these devices will frequently need access to large amounts of memory to hold these images.

Thus, the demand for memory in mobile devices is generally higher than the demand for memory in PCs, while the RAM that is available to mobile devices is typically more limited than that of a PC.

Apple's Use of Reference Counting

Until relatively recently, iOS required programmers to manage blocks of memory manually, using a system known as reference counting (explained below). Programmers would allocate memory when an object was created and then write statements to release memory when no longer required. This was not only tedious and time-consuming, but, more importantly, unsafe. A failure to release memory when required meant a memory leak (see below). Worse, an attempt to release memory that had already been released caused a crash.

It was thus highly anticipated that Apple would replace manual memory management with something better.

The only serious competitor to iOS as a mobile operating system, Android, uses garbage collection to reclaim memory. And before Apple introduced Automatic Reference Counting (ARC) to iOS, many developers were expecting that Apple would use garbage collection, just as it used garbage collection in Mac OS X. That didn't happen. Instead, Apple not only implemented ARC in iOS, it implemented it on OS X, and actually deprecated garbage collection on OS X.

Apple explained why in a session at the 2011 World Wide Developer's Conference:

Unfortunately garbage collection has a suboptimal impact on performance. Garbage can build up in your applications and increase the high water mark of your memory usage. And the collector tends to kick in at undeterministic times which can lead to very high CPU usage and stutters in the user experience. And that’s why GC has not been acceptable to us on our mobile platforms.

As Drew Crawford has discussed in his paper "Why mobile web apps are slow" (July, 2013, at sealedabstract.com), garbage collection is highly sensitive to the amount of memory available—if the memory available is many times that which your app minimally requires, garbage collection is highly efficient. If memory is less than this, it starts getting pretty inefficient, and your app can run up to 70% slower than it would if manual memory management were used. Crawford says that "in a memory constrained environment garbage collection performance degrades exponentially [with the level of memory constaints]."

Crawford asserted that apps can need up to 6 times their minimal required memory for garbage collection to be efficient. Other analysts have taken issue with this, suggesting that better garbage collection algorithms that have been more recently implemented do not require as much memory.

What seems clear is that neither garbage collection nor reference counting completely solves the problem. As discussed below, it takes some programming effort to use reference counting in a way that is not necessary for garbage collection, to avoid reference cycles (which will be explained below). But it is also clear that programmers have to be generally aware of memory use in apps, particularly when making use of capabilities (like cameras) that use large amounts of memory.

Crawford's summary of the issue:

Here’s the point: memory management is hard on mobile. iOS has formed a culture around doing most things manually and trying to make the compiler do some of the easy parts. Android has formed a culture around improving a garbage collector that they try very hard not to use in practice. But either way, everybody spends a lot of time thinking about memory management when they write mobile applications. There’s just no substitute for thinking about memory. Like, a lot.

Using Memory in Apps

In designing an app, the general rule is to use memory only when you need it, and to give it up as soon as you no longer need it. The operating system is designed to help apps use memory very efficiently. Memory is allocated in blocks to apps when required. When a block is no longer required, it is deallocated and made available for use by other parts of the app or by other apps.

In app design, one has to be generally aware of how much memory is being used by the app. With today's larger memories, doing things like displaying large, high-resolution photographs, even a large number of them that a user might scroll through, won't necessarily use a prohibitive amount of memory. Roughly half of the amount of RAM that a device physically has is available for use by an app if necessary. This means about 512 Megabytes, for example, for recent phones like iPhone 5 to iPhone 6 Plus (that have 1 Gig RAM), and 1 Gigabyte for the iPad Air 2 (has 2 Gig RAM).

You want to insure not only that there is enough memory for what your app is doing at every moment but preferably also that there is memory available for other apps that may be running in the background. If your app uses all of the available memory, these other apps may get pushed out, and there may be a delay in restarting them.

Aside from insuring that there is enough memory for the app, you must particularly make sure that there are no significant memory leaks. A memory leak is a situation in which memory is allocated to an object but then never deallocated. This is particularly a problem in cases where the allocation and failure to deallocate happens repeatedly during execution of an app. This can cause large amounts of memory to be allocated to the app but not actually used, often ultimately resulting in a crash.

How Reference Counting Works

As discussed above, iOS devices make use of reference counting to manage memory. In reference counting, when a block of memory is allocated, a counter is maintained for that block. When a block of memory is allocated, the allocation is done by some other object, which then maintains a reference to that object, meaning that it is making use of the information in that block. When first allocated, the counter is set to one, meaning that one other object has a reference to it.

Should another object have a need to use information in that block, it will establish its own reference to the block, resulting in the counter for the block being increased by one, to two.

When an object having a reference to an object no longer needs information from it, it releases the object it is pointing to, which causes the reference count to be decreased by one.

When a reference count for an object, that is a block, reaches 0, the operating system will reclaim the memory and make it available for reuse.

Another (perhaps easier) way of looking at reference counting is by tracking ownership. When an object and its block of memory is referenced by another object, it is said to become an owner of that object. An object is allowed to have more than one owner. When an object removes a reference, it is said to give up ownership. When an object no longer has any owners, the operating system can reclaim its memory for reuse.

Swift makes use of Automatic Reference Counting, a system in which the compiler and runtime system take care of nearly all of the details of memory management. When an object is created, the compiler will generate code to cause the operating system to allocate a block of memory for it and set the reference count. When a block of memory is no longer needed (reference count of 0 or no owners), code inserted by the compiler will deallocate that memory.

This is all done automatically by the compiler and operating system. The system works well without any intervention by the programmer, with one exception. That exception involves what is known as a reference cycle, also known as a retain cycle. A reference cycle prevents automatic reference counting from working properly, and programmers need to ensure that reference cycles are avoided.

How Reference Cycles Cause Memory Leaks

Suppose you have code that creates a new object known as Parent. The Parent object will then create another object known as Child. Now suppose further that the Child object makes use of information in the Parent object, say by accessing one of its properties. There will be a reference from the code that created the new object to Parent, setting the reference counter for Parent to 1, and then there will be an additional reference from the Child object to Parent that will set the reference counter for Parent to 2. (Another way to look at this is that Parent will have two owners, the object that created it and Child.)

At some point, the Parent object will no longer be needed, and the code that created it will remove its reference to Parent, causing Parent to have a reference count of 1. However, it needs a reference count of 0 to be deallocated, and that will not happen. The reason is that Child still maintains a reference to Parent.

The Child object still has a reference count of one. It is owned, in other words, by the Parent object. But the Parent object will only remove its reference to Child, and thus its ownership, if it itself is deallocated and fails to exist. Similarly, the Child object will continue to maintain its reference to Parent, and thus its ownership, as long as it exists. The result is a deadlock situation in which neither the Parent or Child object will be deallocated.

How to Avoid Reference Cycles with weak and unowned

When Apple developed Automatic Reference Counting, it created what are essentially tags for references. These are often known as modifiers, and like other keywords used when declaring a property, are also known as declaration modifiers. If, say, one object uses another object (or one of it's properties) in an assignment statement, that constitutes a reference. It means that the first object is dependent upon the second object to obtain its value. For that assignment statement to work properly, the second object must still exist in memory.

There are three kinds of tags: strong, weak, and unowned. When a reference is made, that reference is normally tagged as strong. This is done automatically as the default—you won't see many strong keywords in a Swift program, although a programmer could put them in whenever a strong reference is made, just to remind readers of what kind of reference was being made.

If a strong reference is made to an object, the system will keep that object around. The problem comes, as discussed earlier, when object A has a strong reference to object B, and object B has a strong reference to object A.

To avoid this, one of the other types is used. The most common is weak. Thus:

weak var length = 56

declares a variable that, should it make reference to an object, will make that reference a weak reference. Without the weak modifier, the reference would be a strong one. With a strong reference, that object would be kept around, resulting in a reference cycle. A weak reference, however, is no reference at all from the point of view of the memory management system. The object will be kept around only if something else is pointing to it with a strong reference. (In cases where there are potential reference cycles there is normally something else referencing one of the objects that will keep it around as long as it needs to be around.)

There are conventions about which kinds of references are used in different situations.

In general, when two objects have a parent and child relationship, meaning that the parent created the child, the rule is that the reference from parent to child will be strong, while the reference from child to parent will be weak. Since the parent created the child, it needs to keep a strong reference to it to keep that child alive. But since the parent created the child, the child can reasonably assume that some other object is referencing the parent and keeping it alive. It is thus reasonable for the child's reference to the parent to be weak (or unowned).

Weak versus Unowned Modifiers

The modifiers weak and unowned mean pretty much the same thing from the viewpint of avoiding a reference cycle—that the reference is not strong. A strong reference means that an object has ownership in another object and wants to keep it around. Either a weak or an unowned reference means that an object deliberately has NO ownership in another.

However, there is a difference between a weak and an unowned variable. A weak variable must be an optional, because the ARC system, after deallocating the object that the variable points to, will set that variable to nil.

In contrast, an unowned variable must be a non optional, and the ARC system will not attempt to set it to nil. It is assumed that an unowned variable has a longer lifetime than a weak one.

Reference Cycles in Closure Expressions

Closures (whether functions or closure expressions) are reference types and are thus subject to the memory management of heap memory.

The main problem with a closure expression is when it is used with an instance of a class. It is common for a closure expression to be assigned to the property of a class. When this happens, the code in the body of a closure expression can refer to the instance, or a property of an instance, using the keyword self. The assignment from the property of the class instance creates a strong reference to the closure expression; the reference to self creates a strong reference from the closure expression to the class instance, and there is a reference cycle.

The solution is the use of a capture list. A capture list is list of the names of entities that statements in the body of the closure reference that might cause a reference cycle. A typical capture list is:

[weak self, unowned self.multiply]

The syntax of a capture list consists of a list of memory reference modifiers along with the name of a variable or other entity, separated by commas. This list is surrounded by square brackets. The capture list itself comes right after the leading (left) brace of the closure, and before the input parameters (if any).

Stack versus Heap Memory

Random-access memory in an iOS device is divided into a number of separate sections. Two sections of particular interest are the stack and the heap.

The stack is a set of fixed-size areas of memory that contain basic context information.

The heap, in contrast, has a very large amount of memory divided into blocks, and in which those blocks are dynamically allocated in response to demand. Thus, when a new object is created, a sufficient amount of memory will be allocated for it, and that memory will continue to be allocated to that object until the object reaches the end of its lifetime and the blocks of memory are deallocated. Allocation and deallocation of memory are performed by code created by the compiler as part of the Automatic Reference Counting system.

Hands-On Exercises

Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.

For Chapter 19 exercises, go to

understandingswiftprogramming.com/19