iOS App Development For Dummies (2014)

Part II. Building RoadTrip

Chapter 6. The Runtime, Managing Memory, and Using Properties

In This Chapter

arrow Understanding the application life cycle

arrow Handling interruptions

arrow Using Automatic Reference Counting to manage memory

arrow Understanding the five rules of memory management

arrow Customizing framework behaviors

arrow Taking advantage of the power of declared properties

Previous chapters provide you with at least a basic understanding of how to graphically build your user interface. Now it’s time to add some code to have your app actually do something. But before you do that, I want to explain three things about writing iOS apps.

First, a lot of what you’ll be doing is customizing and extending the behavior of framework classes. You customize and extend the behavior of these classes through subclassing, delegation, and using a powerful Objective-C feature called declared properties.

Second, on iPhone or iPad, like any other device, you create objects to do your bidding — which means that you allocate memory, which happens to be a scarce resource, particularly on relatively small mobile devices. Running out of memory is the main cause of apps crashing (not to mention being rejected from the App Store), so you need to understand memory management.

And finally, to know what message to send to what objects at what time, as well as what messages will be sent to your app at runtime, you need to understand the application life cycle.

Dealing with these three aspects of writing iOS apps is your pass to the Successful iOS Programmers' Society, and in this chapter, you start your initiation. And because you’ll find all this stuff easier to understand if you understand the overall context, I begin with the application life cycle.

image This chapter is enough to get you started and also keep you going as you develop your own iOS apps. It provides a frame of reference on which you can hang the concepts I throw around with abandon in upcoming chapters — as well as the groundwork for a deep enough understanding of the application life cycle to give you a handle on the detailed documentation. However, the full and definitive documentation is on https://developer.apple.com/.

So relax. Get yourself a cup of coffee (or something stronger if you want) and be prepared to be entertained.

Stepping Through the App Life Cycle

Although simple for the eventual user, the birth, life, and death of an app is a pretty complex process. In this section, I explain what happens throughout the time that the user launches the app from the Home screen, uses the app, and then stops using the app, either because she is done or decides to respond to an interruption such as an SMS message or phone call.

The life of an iOS app begins when a user launches it by tapping its icon on the Home screen. The system launches your app by calling its main function — which Xcode kindly lets you peek at if you go to the Project navigator, open the disclosure triangle next to the Supporting Files group, and select main.m.

image The details of the implementation shown here may change, but the overall architecture will stay the same from one iOS version to another.

  #import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char *argv[])
{
  @autoreleasepool {
      return UIApplicationMain(argc, argv, nil, NSString
FromClass([RTAppDelegate class]));
  }
}

The main function is where a program starts execution. This function is responsible for the high-level organization of the program's functionality and typically has access to the arguments given to the program when it gets executed.

The main function does only these two things:

1.     Sets up an autorelease pool:

  @autoreleasepool {

This is a piece of memory-management plumbing that you don’t need to use in this book (other than here), or perhaps ever, but feel free to investigate on your own if you are interested.

2.     Calls the UIApplicationMain function to create the application object and delegate and set up the event loop:

  return UIApplicationMain(argc, argv, nil, 
            NSStringFromClass([AppDelegate class]));

This is your entrance into the entire app startup process and its underlying architecture.

image You may notice that with the exception of the @autoreleasepool directive, what you’re looking at in main is C code. This is the bootstrap code that gets you into the world of Objective-C.

UIApplicationMain

The UIApplicationMain function creates the application object (a singleton — the only — UIApplication object) and the application delegate (a class created for you by the Xcode template). It also sets up the main event loop, including the app’s run loop (which is responsible for polling input sources) and begins processing events.

In the following section, I explain the role of each of these elements in the application life cycle.

UIApplication provides application-wide control

The UIApplication object provides the application-wide control and coordination for an iOS app. It's responsible for handling the initial routing of incoming user events (touches, for example) as well as for dispatching action messages from control objects (such as buttons) to the appropriate target objects. The application object sends messages to its application delegate to allow you to respond in an application-unique way to occurrences such as application launch, low-memory warnings, and state transitions such as moving into background and back into foreground.

image Delegation is a mechanism used to avoid subclassing complex UIKit objects, such as the UIApplication object. Instead of subclassing and overriding methods in a framework or other object, you go ahead and use that object unmodified and opt for putting your custom code inside a delegate object instead. As interesting events occur, the framework or other object sends messages to your delegate object. You use these methods to execute your custom code and implement the behavior you need. I explain the delegation pattern more in “The Delegation pattern” section, later in this chapter.

The Application Delegate object (the AppDelegate you see in the template) is responsible for handling several critical system messages and must be present in every iOS app. The object can be an instance of any class you like, as long as it adopts the UIApplicationDelegate protocol. In the template, you’ll find that it's a subclass of UIResponder, which enables it to respond to and handle events. (UIApplication is also derived from UIResponder.)

The methods of this protocol correspond to behaviors that are needed during the application life cycle and are your way of implementing this custom behavior. Although you aren’t required to implement all the methods of the UIApplicationDelegate protocol, you’ll often find yourself writing code to handle the following:

·        Initialization in your application delegate’s application:didFinish
LaunchingWithOptions: method.

·        State transitions such as moving in and out of background and foreground. I explain these in more detail in the section “Knowing what to do when the normal processing of your application is interrupted,” later in this chapter.

·        Low-memory warnings, which I cover in the section “Observing Low-Memory Warnings,” later in this chapter.

The UIApplication is a singleton object (there is just the one in an app). To get a reference to it, you send the sharedApplication message to the UIApplication class. (In Objective-C, you can send messages to classes, which are really objects on their own.) Sending the sharedApplicationobject the delegate message gives you a pointer to the delegate object:

  AppDelegate *appDelegate = 
             [[UIApplication sharedApplication] delegate];

You’ll be doing that a lot, so much so, in fact, that it should become second nature to you.

UIApplicationMain loads the storyboard

If the application’s Info.plist file specifies a storyboard file (or a main nib file), as RoadTrip’s Info.plist file does, the UIApplicationMain function loads it. The app’s Info.plist file provides a map to the high-level structure of the app.

image To see the RoadTrip-Info.plist file, select RoadTrip-Info.plist under the Supporting Files heading in the Project navigator, as shown in Figure 6-1. The file dutifully appears in the Editor area.

image A nib file is a resource file that contains the specifications for one or more objects and is used to graphically create your user interface using Interface Builder in apps when you’ve opted not to use a storyboard. (A storyboard consists of a series of linked nib files created for you; for more on storyboards, see Chapters 4 and 5.)

If you’re using a storyboard, the initial view controller is instantiated for you. As you can see in the Attributes inspector in Figure 6-2, the Initial View Controller setting is a View Controller property. It is set for you by most of the Xcode templates. Note the check box in the View Controller attributes in the Utility area at the right of Figure 6-2. (You usually don’t even have to think about it.) In the iPad storyboard file, the initial view controller is a Split view controller that was included by the template. The nib-loader will also instantiate both Navigation controllers as well as their Root view controllers. (I explain Navigation controllers and Root view controllers in Chapter 5.)

image

Figure 6-1: The Road Trip-Info.plist file.

image

Figure 6-2: The initial view controller is specified for the iPad storyboard.

In the iPhone storyboard file, the initial view controller is a Navigation controller that uses a Table View controller as its root view controller.

UIApplication sends the application:didFinishLaunchingWithOptions: message to its delegate

If the method is implemented in the application delegate, the application: didFinishLaunchingWithOptions: message is sent to the application delegate.

Launch time is a particularly important point in an application’s life cycle. In addition to the user launching an app by tapping its icon, an app can be launched to respond to a specific type of event. For example, it could be launched in response to an incoming push notification, it could be launched to open a file, or it could be launched to handle some background event that it had specified it wanted to handle (a location update, for example). In all these cases, an Options dictionary passed to the application:didFinishLaunchingWithOptions: method provides information about the reason for the launch. (An app launched by the system for a specific reason — other than the user tapping its icon — is beyond the scope of this book.)

The application:didFinishLaunchingWithOptions: message is sent to the delegate when the app has launched and its storyboard has been loaded. In this step, as you will see, you initialize and set up your app. At the time this message is sent, your app is in the inactive state. At some point after this method returns (completes) — unless your app does some kind of background processing — your app will become active and will receive the applicationDidBecomeActive: message when it enters the foreground (becomes the app that the user sees on the screen).

image If you're thinking that somewhere in this startup process you should display your app's splash screen with a welcome message (and maybe some copyright mumbo-jumbo), forget about it. Ideally, you get your users directly into the app as quickly as possible without those outdated welcome screens. In fact, the way in which you do this is to provide one or more launch images that you typically place in your app's asset catalog. A launch image is the background of the first screen the user sees. An easy way to create them is to set a breakpoint in a viewDidLoad method for the first view the user will see. Stop the action just before you add any data to the view. At runtime, the launch image for the appropriate device and orientation will be presented immediately, and, if you look very carefully, you'll be able to see the content appear to be quickly placed on the background. In fact, as long as your launch image is the same size, shape, and general color of the live view, the illusion of putting content onto the background will succeed. In reality, you're simply presenting another view instead of the launch image.

image The class interface (usually declared in the .h file) lists the messages to which an object of that class can respond. The actual code for implementing a message is called a method and will be found in the associated .m file. When you want to have an object execute a method, you send it a message. In other words, the message is what you want done, while the method is how to do it.

image Your goal during startup is to present your app’s user interface as quickly as possible — and quick initialization equals happy users. Don’t load large data structures that your app won’t use right away. If your app requires time to load data from the network (or perform other tasks that take noticeable time), get your interface up and running first and then launch the task that takes a longer time on a background thread. Then you can display a progress indicator or other feedback to the user to indicate that your app is loading the necessary data or doing something important.

In the templates that don’t use a storyboard, the application:didFinishLaunchingWithOptions: method allocates and initializes the window and the Split view controller (as well as its initial view controllers), adds it all to the window, and makes the window visible.

In a storyboard-based app, this is all done by the storyboard for you, and the application:didFinishLaunchingWithOptions: method does nothing other than return YES (the usual return).

image You would return NO only if your app was launched because another app opened a URL that's owned by your app and your app can't handle the URL.

You will be adding some code to this method in Chapter 8.

Handling events while your application is executing

Most events sent to an app are encapsulated in an event object — an instance of the UIEvent class. In the case of touch-related events, the event object contains one or more touch objects (UITouch) representing the fingers that are touching the screen. As the user places fingers on the screen, moves them around, and finally removes them from the screen, the system reports the changes for each finger in the corresponding touch object.

Distributing and handling events is the job of responder objects, which are instances of the UIResponder class. The UIApplication, UIViewController, UIWindow, and UIView classes (and your own AppDelegate) all inherit from UIResponder. After pulling an event off the event queue, the app dispatches that event to the UIWindow object where it occurred. The window object, in turn, forwards the event to its first responder, designated to be the first recipient of events other than touch events. In the case of touch events, the first responder is typically the view object (UIView) in which the touch took place. For example, a touch event occurring in a button is delivered to the corresponding button object.

If the first responder is unable to handle an event, it forwards the event to its next responder, which is typically a Parent view or view controller. If that object is unable to handle the event, it forwards it to its next responder, and so on until the event is handled. This series of linked responder objects is known as the responder chain. Messages continue traveling up the responder chain — toward higher-level responder objects, such as the window, the app, and the app’s delegate — until the event is either handled or discarded.

The responder object that handles an event often sets in motion a series of programmatic actions by the app. The following list provides the chronology of what actually happens when the user taps something:

1.     A touch event object is created in response to the user’s tap.

The touch of a finger (actually the lifting of a finger from the screen) adds a touch event to the app’s event queue, where that event is encapsulated in — placed into, in other words — a UIEvent object. A UITouch object exists for each finger touching the screen, so that you can track individual touches. As the user manipulates the screen with her fingers, the system reports the changes for each finger in the corresponding UITouch object.

2.     The run loop monitor dispatches the event.

When something occurs that needs to be processed, the event-handling code of the UIApplication processes touch events by dispatching them to the appropriate responder object — the object that has signed up to take responsibility for doing something when a specific type of event happens (when the user touches the screen, for example). As mentioned previously, responder objects can include instances of UIApplication, UIWindow, and UIView (and any of its subclasses), as well as UIViewController (and any of its subclasses). All these classes inherit fromUIResponder.

3.     A responder object decides how to handle the event.

For example, a touch event occurring with a button in a view is delivered to the button object. The button object handles the event by sending an action message to another object — in this case, the UIViewController object. This enables you to use standard button objects without having to muck about in their internals — you just tell the button what method you want to have invoked in your target (usually the view controller, as I explain in Chapter 9), and you’re basically set.

Processing the message may result in changes to a view, a new view altogether, or some other kind of change in the user interface. When one of these results occurs, the view and graphics infrastructure takes over and processes the required drawing events.

4.     Your app then returns to the run loop.

After an event is handled or discarded, app control passes back to the run loop. The run loop then processes the next event or puts the thread to sleep if it has nothing more to do.

But because your app isn't alone on the device, it can be interrupted by an SMS message, or the user touching the Home button. When your app is interrupted, you’ll have to take care of some things before control is switched to another app.

Knowing what to do when the normal processing of your application is interrupted

On an iOS device, various events besides termination can interrupt your app to allow the user to respond — for example, calendar notifications or the user pressing the Sleep/Wake button — and your app moves into the inactive state. If the user chooses to ignore an interruption, your app moves back into the active state and continues running as before. If the user decides to tap the alert to deal with it (or if the interruption was from the user touching the Home button to switch out of your app), your app then moves into its background state, where it's suspended but remains in memory.

iOS sends you a number of messages to let you know exactly what’s happening as well as to give you the opportunity to take actions such as save user data and state information, which means saving the point where the user was in the app. (If an app needs to continue running, it can request execution time from the system.) Because the app is in the background (running or suspended) and still in memory, relaunching is nearly instantaneous. An app’s objects (including its windows and views) remain in memory, so they don’t need to be re-created when the app relaunches. If memory becomes constrained, iOS may purge background apps to make more room for the foreground app.

image Because these interruptions cause a temporary loss of control by your app, touch events are no longer sent to your app. When developing your app, you need to take this fact into account. For example, if your app is a game, you should pause the game when your game is interrupted. In general, your app should store information about its current state when it moves to the inactive state and be able to restore itself to the current state upon a subsequent relaunch.

In all cases, the sequence of events starts the same way — with the applicationWillResignActive: message sent to your app delegate when the app is about to move from active to inactive state. In this method, you should pause ongoing tasks, disable timers, throttle down OpenGL ES frame rates (that is, you should use this method to pause the game), and generally put things on hold.

What happens after this depends on a) the nature of the interruption, and b) how the user responds to the interruption. Your app may be either moved to the background or reactivated. I explain these occurrences next.

Before I do that, however, check out Figure 6-3, which shows the application life cycle and how interruptions are handled.

image

Figure 6-3: The application life cycle.

If the user responds to the interruption (the SMS message, for example) or has launched another app, your app is moved to the background.

The next two bullets explain the messages your app can respond to after it's been moved into the background:

·        The applicationDidEnterBackground: message: When your app first enters the background state, it's sent the applicationDidEnterBackground: message. In this method, you should save any unsaved data or state (where the user is in the app — the current view, options selected, and stuff like that) to a temporary cache file or to the preferences database on disk. (Apple calls the iOS storage system a disk even though it is a solid-state drive.)

image Even though your app enters the background state, you have no guarantee that it will remain there indefinitely. If memory becomes constrained, iOS will purge background apps to make more room for the foreground app. You need to do everything necessary to be able to restore your app in case it’s subsequently purged from memory so that the next time the user launches your app, your app can use that information to restore your app to its previous state. You also have to do additional cleanup operations, such as deleting temporary files.

image If your app is purged when it's in this suspended state, it receives no notice that it's removed from memory. That’s why you need to save any data when you receive the applicationDidEnterBackground: message.

When your delegate is sent the applicationDidEnterBackground: method, your app has an undocumented amount of time to finish things up. If the method doesn’t return before time runs out (or if your app doesn’t request more execution time from iOS), your app is terminated and purged from memory.

If your app requests more execution time or it has declared that it does background execution, it's allowed to continue running after the applicationDidEnterBackground: method returns. If not, your (now) background application is moved to the suspended state shortly after returning from the applicationDidEnterBackground: method.

If the app is in the background, it then may be relaunched. This can happen if the user selects the app from the Home screen or the multitasking bar, or it's launched by the system if the app processes events in the background or monitors a significant location change, for example.

·        The applicationWillEnterForeground: message: When your app is relaunched from the background, it's sent the applicationWillEnterForeground: message. In this method, you need to undo what you did in the applicationDidEnterBackground: method.

If the user ignores the SMS message, or the app is relaunched from the background, your app is reactivated and starts getting touch and other events.

When your app is reactivated, it's sent the applicationDidBecomeActive: message.

You can use the applicationDidBecomeActive: method to restore the app to the state it was in before the interruption. Here you undo what you did in the applicationWillResignActive method, such as restart any tasks that were paused (or not yet started) while the app was inactive. If the app was previously in the background, you might need to refresh the user interface.

While an app is in the suspended state, the system tracks and coalesces (really nets out) events that might have an impact on that app when it relaunches. As soon as your app is up and running again, the system delivers those events to it. For most of these events, your app’s existing infrastructure should just respond appropriately. For example, if the device orientation changed, your app’s view controllers would automatically update the interface orientation in an appropriate way.

Apps are generally moved to the background when interrupted or when the user quits. But if the app was compiled with a very early version of the SDK, or is running on an early version of the operating system that doesn’t support multitasking — or if you decide that you don’t want your app to run in the background and you set the UIApplicationExitsOnSuspend key in its Info.plist file — iOS terminates your app.

image Even if your app supports multitasking (almost all do at this point), you must still be prepared for your app to be killed without any notification. The user can kill apps explicitly using the multitasking bar. In addition, if memory becomes constrained, the system might remove apps from memory to make more room. If it does remove your suspended app, it doesn't give you any warning, much less notice after the fact! However, if your app is currently running in the background state, the system does call the applicationWillTerminate: method of the app delegate.

When your application delegate is sent the applicationWillTerminate: message in nonmultitasking applications, or those running in the background, you need to do the same kinds of things you do in applicationDidEnterBackground:, except this time you do them knowing that your app won't be returning from the background.

Your applicationWillTerminate: method implementation has a limited (albeit undocumented) amount of time to do what it needs to do and return. Any longer than that and your app is terminated and purged from memory. (The Terminator doesn’t kid around.)

An overview of the view controller life cycle

View controllers have a life cycle just as apps do, but I don’t need to go into much detail about it here. The important part to know is certain messages that are sent as views are displayed and hidden.

The two methods you need to know about in order to work with views are the following:

·        viewDidLoad

·        viewWillAppear:

The viewDidLoad message is sent to your view controller. This message is sent after the view controller has loaded its associated views into memory. This method is used to perform additional view initialization on views loaded from the storyboard or nib file, and the message isn'tnecessarily sent every time the view appears. If, for example, the user makes a selection in the view that causes a new view controller to load and slide its view into place, and the user then taps the Back button, this message isn't sent when the originating view reappears. That's the job ofviewWillAppear:.

The viewWillAppear: message is sent when the view is about to become visible. The first time it's sent is after the viewDidLoad message, and then whenever the view reappears, such as when you tap the Back button, for example. You use this method to do the things that are necessary to present the view. For example, if you are displaying the location of the nearest book store that carries iOS App Development For Dummies, update that information in this method.

Numerous other methods are also placed in the view controller for you as stubs. I leave you to explore them on your own.

Of course, aside from all this system stuff that happens, your app will be chugging along doing what the user wants it to do. And in responding to user requests, you’ll create objects to do the user’s bidding — which means that you allocate memory. And because memory is a scarce resource on iOS devices (and, indeed, on even the largest computers), you need to understand memory management, discussed in the next section.

Working within the Managed Memory Model Design Pattern

As powerful as it is, the iPhone — and even the iPad — are limited in resources, and the most critical of these resources is memory. To truly understand how to manage memory correctly in your app, you need to understand how the iOS memory works.

Understanding memory management

Whenever you (or a framework object) create an object using Objective-C, you allocate memory for the object. Although iOS devices and the Mac all use what’s known as virtual memory, unlike the Mac, virtual memory in iOS is limited to the actual amount of physical memory. So when it begins to run low on memory, the Memory Manager frees memory pages that contain read-only content (such as code); this way, all it has to do is load the “originals” back into memory when they’re needed. In contrast to what the Mac does, iOS doesn’t temporarily store “changeable” memory (such as object data) to the disk to free space and then read the data back later when it’s needed. This state of affairs limits the amount of available memory.

So as you can see, when one object is done using memory, it's critical that the memory be released for use by other objects.

If memory continues to be limited, the system may also send notifications to the running app, asking it to free additional memory. This is one of the critical events that all apps must respond to, and I explain this process in the section “Observing Low-Memory Warnings,” later in this chapter.

In Objective-C, memory is managed in iOS apps by reference counting — keeping the system up-to-date on whether an object is currently being used. Read on for all the details.

Using reference counting

In fact, memory management is simply an exercise in counting. Every object has its own reference count, or retain count, which is the number of other objects that are currently using the object. As long as the retain count is greater than zero, the memory manager assumes that someone cares about that object and leaves it alone. When an object’s retain count goes to zero, the memory manager knows that no one needs it anymore and sends the object a dealloc message, and after that, its memory is returned to the system to be reused.

That process sounds pretty straightforward, but how does the retain count get incremented and decremented? Until Xcode 4.2 and iOS 5.0, you had to manage the retain count in your app. When an object is created via alloc or new or through a copy or mutableCopy message (which creates a copy of an object but has subtleties beyond the scope of this book), the object’s retain count is set to 1. When your app uses one of those methods, ownership is transferred to the object that sent the message — that is, the object has been retained and that object that sent the message becomes a nonexclusive owner of the object. Ownership here means that the object will be there to use until it’s explicitly released by sending it a release message when it's no longer needed (although if other active owners exist, it wouldn’t be deallocated until all of them have released it).

Before Xcode 4.2 and iOS 5.0, if you didn’t create an object by one of those methods but you wanted to become an owner, thereby making sure that the object stayed around for you to use until you were done with it, it was up to you to send a retain message to increase the retain count, and when you were done, to send a release message. This was because the creator of the object (which caused the retain count to be set to 1) may have autoreleased it — sent an object a release message that will cause it to be released later (usually the next time the run loop is entered). This is useful in situations in which you want to relinquish ownership of an object but avoid the possibility of its being deallocated immediately (such as when you return an object from a method). In either instance, you were maintaining a pointer to the object so that it could be used.

Although this approach was simple in theory, it was a real headache for programmers. The vast majority of system crashes occurred because apps ran out of memory and were shut down by the system. In some of these cases, the app didn’t respond to the memory warning methods and manage the low-memory warnings I explain in the section “Observing Low-Memory Warnings,” later in this chapter.

Most of the time, however, even if the app responded to the low-memory warnings, it was limited to what it could do because the memory was leaked. Memory was actually available because some objects were not being used, but those objects’ memory had not been released back to the system. In fact, there were no longer pointers to these objects (for a variety of reasons), so they couldn’t be released and then deallocated and the memory reused.

image Developers have had a number of ways to manage memory automatically. One is garbage collection, which scans through memory and releases objects that have no pointers to them. Garbage collection for Objective-C was available on the Mac (and for many other languages on other platforms), but garbage collection has a few problems. It can start up and pause your apps at the most inopportune time, and it affects performance and the user experience because you have no control, or any idea, when it will occur. It was never implemented on iOS and is deprecated beginning with OS X 10.8 (Mountain Lion).

Having to do all this memory management in your app has changed with the latest versions of the Objective-C compiler, which now comes with automatic reference counting (ARC), which is enabled by default whenever you create a project. (For more on options when creating a project, see Chapter 3.) ARC does for you in the compiler what you used to have to do on your own. It handles all those releases, autoreleases, and retains for you. I tell you much more about ARC in the next section, but in a nutshell it's still about counting. The only difference is that Xcode and the compiler do the counting for you.

Automatic Reference Counting (ARC)

Automatic reference counting (ARC) is a compiler-level feature that simplifies the process of managing the lifetimes of Objective-C objects. Instead of you having to remember when to retain or release an object, ARC evaluates the lifetime requirements of your objects and automatically synthesizes the appropriate method calls at compile time. It isn't a new runtime memory model — and it isn't a garbage collector. All the action takes place in the compiler.

ARC takes care of the process of retaining and releasing objects by taking advantage of (and having the compiler enforce) naming conventions. It also relies on new object pointer ownership qualifiers (more on that later).

Lest you worry, ARC is actually much faster (has better performance) than doing memory management on your own.

image ARC doesn't automate malloc() and free() (C functions I won’t get into here) and doesn't automate CoreFoundation (CF) or CoreGraphics (CG). You'll be using some of those kinds of functions, and I talk about them in Chapter 10.

To be able to manage memory for you, ARC imposes some restrictions — primarily enforcing some best practices and disallowing some other practices. You won’t have to worry about most of this in an app that was created to use ARC. You may see some things in non-ARC samples, but hopefully my discussion here will help you figure out how to work within the ARC restrictions.

In the following, I explain the rules that you have to follow to use ARC in your app.

·        Rule 1: Don't call the retain, release, or autorelease methods. In addition, you can't implement custom retain or release methods.

If you're new to Objective-C programming, this rule won’t mean anything to you because it isn't something you'll have been doing in your existing apps. The only reason you’ll need to know about this rule is to understand what non-ARC code is doing to manage memory. If you’re an old hand, you'll have been using these methods, and you’ll be happy to be told not to use them.

image You can provide a custom implementation of dealloc if you need to manage other resources — but I don’t have you do that for the example app developed in this book.

·        Rule 2: Don't store object pointers in C structures. Because the compiler must know when references come and go, you can’t store object pointers in structs. For most readers, that won’t be a problem because you'll be using objects rather than C structures.

·        Rule 3: Inform the compiler about ownership when using Core Foundation–style objects. In iOS apps, you often use the CoreFoundation framework. An example is in Chapter 10, when you add sound to your app.

CoreFoundation objects are anything beginning with a CF — things like the address book functions, for example. A CoreFoundation object would look like this:

  AudioServicesCreateSystemSoundID(
(__bridge CFURLRef)burnRubberURL, burnRubberSoundID);

ARC doesn't automatically manage the lifetimes of CoreFoundation types, and there are CoreFoundation memory management rules and functions you can use, such as CFRetain and CFRelease (or the corresponding type-specific variants).

In this book, and most of the time, you don’t have to worry about memory management because you usually will be casting an Objective-C object to a CoreFoundation type object, or vice versa — with the result that you end up with no CoreFoundation memory management in your code. You still have to let the compiler know about any memory management implications, though.

Again, in this book, and much of the time elsewhere, you simply tell ARC not to worry by using a __bridge cast. (You’ll use a __bridge cast in Chapter 10.)

If you do have CoreFoundation memory management, macros such as CFBridgingRetain or CFBridgingRelease will transfer ownership between ARC and CoreFoundation. (This topic is beyond the scope of this book, however.)

·        Rule 4: Use the @autoreleasepool keyword to mark the start of an autorelease block. This isn't something you’ll be concerned about — or will ever do, for that matter. But it's a rule nonetheless.

·        Rule 5: Follow the naming conventions. The compiler knows whether to retain an object based on what gets returned. Sometimes the object being returned by a method is retained, and sometimes it's autoreleased later. If the object is going to be autoreleased, the object needs to be retained. If it’s already retained, you don’t want the compiler to do anything.

The only way the compiler knows whether an object has been retained when it's returned is through certain naming conventions. Under ARC, these naming conventions are now part of the language, and you must follow them.

The compiler knows that a retained object has been returned when the first word in the first part of the selector (the method name) is alloc, new, copy, mutableCopy, or init. These methods transfer ownership — where transferred ownership means that the object has been retained for you. An example is the NSStringinitWithFormat: method. (Remember that “ownership” is not exclusive ownership: Several object may simultaneously own a single object.)

In addition, you can't give a property a name that begins with new.

·        Rule 6: Just follow the rules. That’s it — no retaining releasing or autoreleasing. Just follow the rules and code to your heart’s content without worrying about memory management.

Except, of course, in some situations, you’ll need to explicitly tell the compiler about what you want to do. In those cases, you’ll have to tell the compiler explicitly about an object’s lifetime. I explain how to do that in the next section.

Working with variable types according to ARC

Because the reference to an object lives in a variable, object pointers can be qualified using ownership type or lifetime qualifiers. These qualifiers determine when the compiler can deallocate an object to which a pointer points. These qualifiers are as follows:

  __strong
__weak
__unsafe_unretained
__autoreleasing

The following sections describe the function of each of these qualifiers.

·        __strong variables retain their values.__strong is the default. You almost never have to specify it, and all stack local variables, including parameters, are __strong. A __strong pointer to an object will cause that object to be retained while it's in scope (or not set to nil). No moredangling references (objects that have been deallocated that you expect to be there)!

·        __weak variables don't retain values._ weak variables don't cause an object to be retained (that is, you don’t use them in the reference count) and are, in fact, set to nil (zeroed) as soon as the referenced object starts deallocating. You need to be concerned with these only to prevent retain cycles, which I explain shortly.

·        __unsafe_unretained variables don't retain values and aren't zeroed. Some Apple-provided classes (only on the Mac and some third-party libraries) don't work with zeroing weak references. These have to be cleared in a dealloc method elsewhere.

image Using ARC, __strong, __weak, and __autoreleasing stack variables are now implicitly initialized with nil.

·        __autoreleasing for indirect pointers. These variables aren't for general use. They're used for out parameters that pass values back to the calling routine. They're retained and then autoreleased when they're read into, and are beyond the scope of this book.

Understanding the deadly retain cycle

ARC works very well to manage memory except in one circumstance. In this section, I explain how that circumstance can arise, and what you’ll need to do to keep it from happening.

When you create an object, the compiler makes sure that ownership is transferred and all is well. The compiler will release that object when it goes out of scope, so if it's an instance variable, it will stay in scope until the object itself is deallocated.

I take you through this process using a little program called RetainCycle that I wrote to illustrate the retain cycle.

I create a new RetainIt object in the viewDidLoad method of my RetainCycleViewController object. It will be released only when the retainIt variable goes out of scope (it's __strong by default). In this case, it will be released and then deallocated (assuming that no other object takes ownership) at the end of viewDidLoad because the retainIt variable will go out of scope:

  - (void)viewDidLoad
{
  [super viewDidLoad];
  RetainIt* retainIt = [[RetainIt new]init];
}

But when I create the RetainIt object, in the RetainIt class’s initialization method, init (see the following), I create a Cycle object and assign it to the cycle instance variable I declared. As you might expect, the Cycle object will be retained until the RetainIt object is deallocated because it's referenced by an instance variable, which stays in scope until the object is deallocated:

  - (id)init
{
  self = [super init];
  if (self) {
    self.cycle = [[Cycle new] init];
    cycle.retainIt = self;
  }
  return self;
}

I also, however, assign to the Cycle object’s retainIt property a reference back to the RetainIt object. The Cycle class looks like this:

  @interface Cycle : NSObject

@property (strong, nonatomic) RetainIt* retainIt;

@end

At the end of the day, it looks like Figure 6-4.

This means that the RetainIt object won't be deallocated until the Cycle object is deallocated, and the Cycle object will be deallocated only when the RetainIt object is deallocated. Whoops!

Although this example may appear a bit contrived, it actually can occur in real life when you have one object with a back pointer to the object that creates it (either directly or through a chain of other objects, each with a strong reference to the next leading back to the first).

image

Figure 6-4: The retain cycle.

The __weak lifetime qualifiers for objects take care of this. Although I haven’t explained properties yet, the solution is to make the lifetime qualifier back pointer __weak.

  @property (weak, nonatomic) RetainIt* retainIt;

I explain this more when I explain property attributes later in this chapter.

Observing Low-Memory Warnings

Even if you've done everything correctly, in a large app, you may simply run out of memory. When that situation occurs, the system dispatches a low-memory notification to your app — and it’s something you must pay attention to. If you don’t, it’s a reliable recipe for disaster. (Think of your low-fuel light going on as you approach a sign on the highway that says, “Next services 100 miles.”) UIKit provides several ways for you to set up your app so that you receive timely low-memory notifications:

·        Override the didReceiveMemoryWarning methods in your custom UIViewController subclass.

·        Implement the applicationDidReceiveMemoryWarning: method of your application delegate.

·        Register to receive the UIApplicationDidReceiveMemoryWarningNotification: notification.

The didReceiveMemoryWarning method

The didReceiveMemoryWarning method is sent to the view controller when the app receives a memory warning. Your implementation of this method should do anything it can to reduce its use of memory. This may involve drastic steps such as actually closing views, but there are many other steps you can take that depend on your specific view controller and its views. For example, if you have stored data and objects, they may be able to be removed and set to nil if you can recreate them as needed.

The applicationDidReceive MemoryWarning: method

Your application delegate should set any references to objects it can safely free to nil.

The UIApplicationDidReceiveMemory WarningNotification: notification

Low-memory notifications are sent to the Notification Center, where all notifications are centralized. An object that wants to get informed about any notifications registers itself to the Notification Center by telling which notification it wants to be informed about, and a block (to be explained in Chapter 10) to be called when the notification is raised. Instead of a block, you can supply a target method to be called. A model object, for example, could then release data structures or objects it owns that it doesn’t need immediately and can re-create later by setting references to nil. However, this approach is beyond the scope of this book.

image For those of you who are curious, in your model object (which you create in Chapter 11), you could add the following:

  [[NSNotificationCenter defaultCenter] addObserverForName:
  UIApplicationDidReceiveMemoryWarningNotification                                                       
  object:[UIApplication sharedApplication] queue:nil 
  usingBlock:^(NSNotification *notif) {
                                                       //your code here
}];

image You can test applicationDidReceiveMemoryWarning: and UIApplicationDidReceiveMemoryWarningNotification: in the Simulator by choosing Hardware⇒Simulate Memory Warning.

Picking the right memory-management strategy for your application

Each of these strategies gives a different part of your app a chance to free the memory it no longer needs (or doesn’t need right now). How you actually get these strategies working for you depends on your app’s architecture, so you’ll have to explore that on your own.

image Not freeing enough memory will result in iOS sending your app the application WillTerminate: message and shutting the app down. For many apps, though, the best defense is a good offense, which means you need to manage your memory effectively and eliminate any memory leaks in your code by following the ARC rules.

image For testing, implement all of these methods and place NSLog statements in them along with breakpoints so that you can see the memory issues as they happen. It's easy to pinpoint places where you can free up memory, but it's most efficient to address the ones that provide the biggest bang for your programming buck.

Customizing the Behavior of Framework Classes

Although you'll be creating classes of your own (especially model classes), often you’re going to want to customize the behavior of a particular framework class. You have three ways to go about it:

·        Subclassing

·        Delegating

·        Declared properties

In this section, I’ll cover the first two, and the third in the following section.

Subclassing

Objective-C, like other object-oriented programming languages, permits you to base a new class on a class that’s already declared. The base class is called a superclass; the new class is its subclass. Each subclass that you define inherits methods and instance variables of its superclass.

Some framework classes are expected to be subclassed. Among them are view controllers, which you’ll be subclassing quite a bit. In fact, there are some classes that are never instantiated directly: These are called abstract classes. Their subclasses can be instantiated directly: These are called concrete instances.

Almost all object-oriented programming languages support subclassing. However, there's a bit of a problem sometimes. It's called the multiple inheritance problem: it's when you want a class to be a subclass of two classes. There's no problem if both subclasses are in the class hierarchy. You could have a class called structure, a subclass called residentialstructure, and a subclass of both called house.

But what do you do if you want house to be a subclass both of structure and of investment? Read on.

The Delegation pattern

Delegation is a pattern used extensively in the iOS frameworks, so much so that, if you want to do any serious app development work, you’re going to have to get your head around it. In fact, when you do understand it, your life will instantly become much easier to manage.

Delegation is a way of customizing the behavior of an object without subclassing it. Instead, one object (a framework or any other object) delegates the task of implementing one of its responsibilities to another object. You’re using a behavior-rich object supplied by the framework as isand putting the code for program-specific behavior in a separate (delegate) object. When a request is made of the framework object, the method of the delegate that implements the program-specific behavior is automatically called.

iOS frameworks rely heavily on the delegation pattern.

For example, the UIApplication object handles most of the actual work needed to run the app. But, as you saw, it sends your application delegate the application:didFinishLaunchingWithOptions: message to give you an opportunity to create model objects that are unique to your app.

When a framework object has been designed to use delegates to implement certain behaviors, the behaviors it requires (or gives you the option to implement) are defined in a protocol.

Protocols define an interface that the delegate object implements. In iOS, protocols can be formal or informal, although I concentrate solely on the former because formal protocols include support for things like type checking and runtime checking to see whether an object conforms to the protocol.

In a formal protocol, you usually don’t have to implement all the methods; many are declared optional, meaning that you have to implement only the ones relevant to your app. Before a formal protocol attempts to send a message to its delegate, the host object determines whether the delegate implements the method (via a respondsToSelector: message) to avoid the embarrassment of branching into nowhere if the method isn't implemented.

A protocol can be adopted by any class as long as it implements the required methods of the protocol. Thus, by adopting one or more protocols and becoming a delegate, a single class can implement functionality from two other classes — it provides an approximation of multiple inheritance.

Understanding Declared Properties

Although properties and instance variable access and accessors are often mashed together in the minds of programmers, I want to make sure that you understand the unique nature of properties and how they really work.

Whereas methods are concerned with sending messages to objects to get things done, properties are concerned with the state of the objects. Frameworks and other objects behave based on what they find in their properties (hence you can modify object behavior by changing a property); for example, a button’s background image is a property you set (indirectly, in Interface Builder) in Chapter 5.

You also may want to know something about the state of the object, such as its color, or about a window’s Root view controller.

In Chapter 12, I discuss creating a model object — Trip. Your app’s view controllers, which act as a bridge between the views and the model, need to be able to find the Trip object to get data and send it updates. All of this is done using properties. A property looks like the following:

  @property (strong, nonatomic) IBOutlet UIImageView *car;

But not all properties are outlets. If you select the RTAppDelegate.h file in the Project inspector, you can see that it includes a window property:

  @property (strong, nonatomic) UIWindow *window;

And in Chapter 11, you add a trip property to RTAppDelegate:

  @property (nonatomic, strong) Trip *trip;

image As you can see, the order of the attributes (strong, nonatomic versus nonatomic, strong, which I explain in the later section “Setting attributes for a declared property”) doesn’t matter.

What comprises a declared property

A declared property has two parts: its declaration and its implementation.

The declaration uses the @property keyword, followed by an optional parenthesized set of attributes, the type information, and the name of the property.

Access to properties is implemented by accessor methods (although within the class that declares the property, the property can be accessed directly, just as instance variables are). You can write your own accessor methods or you can let the compiler do it for you. To do it yourself, you use the attributes in the upcoming “Setting attributes for a declared property” section.

The default names for the getter and setter methods associated with a property are whateverThePropertyNameIs for the getter and setWhateverTheProperty NameIs: for the setter. In the case of trip, the getter method is trip, and the setter method is setTrip:.

To access the trip property in the appDelegate, you would use

  AppDelegate* appDelegate = 
             [[UIApplication sharedApplication] delegate];
Trip* thisTrip = [appDelegate trip];

or to set that property, use

  AppDelegate* appDelegate = 
             [[UIApplication sharedApplication] delegate];
[appDelegate setTrip:newTrip];

delegate, by the way is a UIApplication property.

Using dot syntax

Objective-C provides a dot (.) operator that offers an alternative to square bracket notation ([ ]) to invoke accessor methods. You use dot syntax in the same way you would when accessing a C structure element:

  Trip* thisTrip = appDelegate.trip;

or to set that property, use

  appDelegate.trip = newTrip;

When used with objects, however, dot syntax acts as “syntactic sugar” — it's transformed by the compiler into an accessor message. Dot syntax doesn't directly get or set an instance variable. The code examples using it are the exact equivalent to using the bracket notation.

Many programmers like the dot syntax because it may be more readable; just think of those bracket notation situations where you’re accessing a property that is a property of another object (that itself is a property of another object, and so on). The real advantage of dot syntax, though, is that the compiler will generate an error when it detects an attempt to write to a read-only declared property. This is so much better than having to settle for an undeclared method warning because you invoked a nonexistent setter method, with the app subsequently failing at runtime.

When you use the compiler to create accessor methods for you, the compiler creates an instance variable of the type you have declared that it will then use to store and retrieve the property value with the name of the property. For example for the following property:

  @property (weak, nonatomic) IBOutlet UIImageView *car;

the statement

  @synthesize car;

generates an instance variable with the name of car of type UIImage.

However, if you let the compiler automatically generate an @synthesize statement for you, it actually uses an instance variable name beginning with an underscore character, so you would get the following code generated for you behind the scenes:

  @synthesize car = _car;

This allows you to distinguish between the property name (accessed by self.car) and the instance variable name (accessed simply as _car).

Apple recommends that you use the property reference (self.car) in normal methods, but use the _car variable in init methods. This applies only to the code within a .m file. Code elsewhere in your app accesses the property as it is declared in the @interface section of the .h. There is no way that code anywhere in the app except in the .m file of a class can access the instance variable directly when you let the compiler do the work. And that’s a good thing to ensure that encapsulation is properly provided.

Setting attributes for a declared property

I mention earlier in this chapter that you can set certain property attributes when you declare a property. I cover some of those attributes in this section.

Setter semantics/ownership

These properties specify how instance variable storage should be managed (see the earlier section “Working with variable types according to ARC” for more):

·        strong (similar to retain, which was used previous to ARC) creates an accessor method that means that the object this property points to will be retained while it is in scope (or until it’s set to nil ). This is the default value.

·        weak (similar to assign, which was used previous to ARC) creates an accessor that uses simple assignment. You typically use this attribute for scalar types such as NSInteger and CGRect, or (in a reference-counted environment) for objects you don’t own — delegates, for example — and to avoid retain cycle problems, as I explain in “Understanding the deadly retain cycle,” earlier in this chapter.

·        copy specifies that a copy of the object should be used for assignment. The previous value is sent a release message.

The copy is made by invoking the copy method. This attribute is valid only for object types, which must implement the NSCopying protocol (and is beyond the scope of this book).

For object properties, you must explicitly specify one of the types listed previously; otherwise, you get a compiler warning. So you need to think about what memory management behavior you want, and type the behavior explicitly.

Writability

The following attributes specify whether a property has an associated set accessor. They are mutually exclusive.

·        readwrite indicates that the property should be treated as read/write. This attribute is the default. The getter and setter methods are synthesized automatically.

·        readonly indicates that the property is read-only. Only a getter method is synthesized. If you implement your own accessors, only a getter method is required. If you attempt to assign a value using the dot syntax, you get a compiler error.

Accessor method names

You’ll remember that the default names for the getter and setter methods associated with a property are propertyName and setPropertyName:, respectively. For example, for the property trip, the accessors are trip and setTrip:. You can, however, specify custom names instead. They're both optional and can appear with any other attribute (except for readonly in the case of setter =):

·        getter = getterName specifies the name of the get accessor for the property. The getter must return a type matching the property’s type and take no parameters.

·        setter = setterName specifies the name of the set accessor for the property. The setter method must take a single parameter of a type matching the property’s type and must return void.

image Typically, you should specify accessor method names that are key-value coding compliant (which is beyond the scope of this book). A common reason for using the getter decorator is to adhere to the isPropertyName convention for Boolean values. If you have a Boolean property called alphabetized that you use to keep track of how an array of names is sorted, the default getter would be alphabetized. Using the naming convention, you would create an accessor method called isAlphabetized, which is a bit clearer.

Atomicity

You can use this attribute to specify that accessor methods aren't atomic. (No keyword denotes atomic.) This has to do with concurrency issues that are way beyond the scope of this book. If you specify nonatomic, a synthesized accessor for an object property simply returns the value directly. Otherwise, a synthesized get accessor for an object property uses a lock and retains and autoreleases the returned value. You use nonatamic throughout this book.

Writing your own accessors

You don’t have to use the accessors generated by the compiler; and, sometimes, it even makes sense to implement them yourself (although such times don’t arise in this book). If you implement the accessor methods yourself, you should make sure that your approach matches the attributes you've declared. (For example, if you specify copy, you must make sure that you do copy the input value in the setter method.)

For example, if you have a lot of overhead to create an object that might not be used, you can create your own getter accessor that creates the object the first time it's accessed. In addition, writing your own accessor means you don’t have to have an instance variable associated with the property. You could, for example, have an area property on a object representing a rectangle. The getter for area might perform length x width and never bother with an instance variable.

Accessing instance variables with accessors

If you don't use self, you access the instance variable directly. In the following example, the set accessor method for _currentDestinationIndex isn't invoked:

  _currentDestinationIndex = [[NSUserDefaults 
standardUserDefaults]objectForKey:CurrentDestinationKey];

The preceding isn't the same as

  self.currentDestinationIndex = [[NSUserDefaults 
standardUserDefaults]objectForKey:CurrentDestinationKey];  

To use an accessor, you must use self.

Hiding Instance Variables

When properties were first developed, they were looked at as a way to avoid the tedium of writing accessors for instance variable-based properties.

People used to think about properties as a way to access instance variables. In fact, instance variables shouldn't be equated to properties, and more important, instance variables shouldn't be made public. (Doing so violates the object-oriented principle of encapsulation, but that’s a conversation for a different time.) In fact, Apple’s new approach is to put instance variable declarations in the implementation file of the class.

Xcode 4.2 came about, we declared instance variables in the header file in the @interface class declaration. In the old times, you would've added the following bolded code to the TestDriveController.h file:

  @interface TestDriveController : UIViewController 
                       <DestinationControllerDelegate> {
  
  AVAudioPlayer *backgroundAudioPlayer; 
  SystemSoundID burnRubberSoundID; 
  BOOL touchInCar;
}

This approach made instance variables (ivars) visible to everyone and everything and was, as I mentioned, at odds with the principle of encapsulation (even if the variables couldn’t be accessed).

Starting with Xcode 4.2, you can now hide instance variables by declaring them in the implementation file in one of two ways. The first is as a class extension, which you create by adding a second interface section in the implementation file followed by open and close parentheses:

  @interface TestDriveController () {
  
  AVAudioPlayer *backgroundAudioPlayer;
  SystemSoundID burnRubberSoundID;
  BOOL touchInCar;
}
@end

The second way is by declaring the instance variable in the @implementation block of the class:

  @implementation TestDriveController

AVAudioPlayer *backgroundAudioPlayer;
SystemSoundID burnRubberSoundID;
BOOL touchInCar;

image A class extension is a variation of an Objective-C category, which is beyond the scope of this book.

The approach you use is your choice; I prefer the class extension because I think it makes the variables easier to distinguish.

image You can also use class extensions to have a publicly declared set of methods and then declare additional methods for use solely by the class:

  @interface TestDriveController () {
  
  AVAudioPlayer *backgroundAudioPlayer;
  SystemSoundID burnRubberSoundID;
  BOOL touchInCar;
}
- (void) privateMethod;
@end

image These methods are not really private, but are not visible in the header file. They are “private APIs.”