iOS 8 Programming Fundamentals with Swift: Swift, Xcode, and Cocoa Basics (2015)

Part III. Cocoa

Chapter 13. Communication Between Objects

As soon as an app grows to more than a few objects, puzzling questions can arise about how to send a message or communicate data between one object and another. The problem is essentially one of architecture. It may require some planning to construct your code so that all the pieces fit together and information can be shared as needed at the right moment. This chapter presents some organizational considerations that will help you arrange for one object to be able to communicate with another.

The problem of communication often comes down to one object being able to see another: the object Manny needs to be able to find the object Jack repeatedly and reliably over the long term so as to be able to send Jack messages.

One obvious solution is an instance property of Manny whose value is Jack. This is appropriate particularly when Manny and Jack share certain responsibilities or supplement one another’s functionality. The application object and its delegate, a table view and its data source, a view controller and the view that it controls — these are cases where the former must have an instance property pointing at the latter.

This does not necessarily imply that Manny needs to assert ownership of Jack as a matter of memory management policy (see Chapter 12) — but it might. An object does not typically retain its delegate or its data source; similarly, an object that implements the target–action pattern, such as a UIControl, does not retain its target. By using a weak reference and typing the property as an Optional, and then treating the Optional coherently and safely, Manny can avoid owning Jack while coping with the possibility that his supposed reference to Jack will turn out to be nil. On the other hand, a view controller is useless without a view to control; once it has a view, it will retain it, releasing it only when it itself goes out of existence.

Objects can perform two-way communication without both of them holding references to one another. It may be sufficient for one of them to have a reference to the other — because the former, as part of a message to the latter, can include a reference to himself. For example, Manny might send a message to Jack where one of the parameters is a reference to Manny; this might merely constitute a form of identification, or an invitation to Jack to send a message back to Manny if Jack needs further information while doing whatever this method does. Manny thus makes himself, as it were, momentarily visible to Jack; Jack should not wantonly retain Manny (especially since there’s an obvious risk of a retain cycle). Again, this is a common pattern. The parameter of the delegate message textFieldShouldBeginEditing: is a reference to the UITextField that sent the message. The first parameter of a target–action message is a reference to the sender.

But how is Manny to obtain a reference to Jack in the first place? That’s a very big question. Much of the art of iOS programming, and of object-oriented programming generally, lies in one object getting a reference to some other object (see Instance References). Every case is different and must be solved separately, but certain general patterns emerge, and this chapter will outline some of them.

There are also ways for Manny to send a message that Jack receives without having to send it directly to Jack — possibly without even knowing or caring who Jack is. Notifications and KVO are examples, and I’ll mention them in this chapter as well.

Finally, the chapter ends with a section on the larger question of what kinds of objects need to see one another, within the general scope of a typical iOS program.

Visibility by Instantiation

Every instance comes from somewhere and at someone’s behest: some object sent a message commanding this instance to come into existence in the first place. The commanding object therefore has a reference to the instance at that moment. When Manny creates Jack, Manny has a reference to Jack.

That simple fact can serve as the starting point for establishing future communication. If Manny creates Jack and knows that he (Manny) will need a reference to Jack in the future, Manny can keep the reference that he obtained by creating Jack in the first place. Or, it may be that what Manny knows is that Jack will need a reference to Manny in the future; Manny can supply that reference immediately after creating Jack, and Jack will then keep it.

Delegation is a case in point. Manny may create Jack and immediately make himself Jack’s delegate, as in my example code in Chapter 11:

let cpc = ColorPickerController(colorName:colorName, andColor:c)

cpc.delegate = self

Indeed, if this crucial, you might endow Jack with an initializer so that Manny can create Jack and hand Jack a reference to himself at the same time, to help prevent any slip-ups. Compare the approach taken by UIBarButtonItem, where three different initializers, such asinit(title:style:target:action:), require as a parameter the target to which future messages will be sent by the UIBarButtonItem.

When Manny creates Jack, it might not be a reference to Manny himself that Jack needs, but to something that Manny knows or has. You will presumably endow Jack with a method so that Manny can hand that information across; again, it might be reasonable to make that method Jack’s initializer, if Jack simply cannot live without the information.

Recall this example from Chapter 11. It comes from a table view controller. The user has tapped a row of the table. We create a secondary table view controller, a TracksViewController instance, handing it the data it will need, and display the secondary table view. I have deliberately devised TracksViewController to have a designated initializer init(mediaItemCollection:), making it virtually obligatory for a TracksViewController to have access, from the moment it comes into existence, to the data it needs:

override func tableView(tableView: UITableView,

    didSelectRowAtIndexPath indexPath: NSIndexPath) {

        delay(0.1) { // let spinner start spinning

            let t = TracksViewController(

                mediaItemCollection: self.albums[indexPath.row])

            self.navigationController!.pushViewController(

                t, animated: true)

        }

}

In that example, self does not keep a reference to the new TracksViewController instance, nor does the TracksViewController acquire a reference to self. But self does create the TracksViewController instance, and so, for one brief shining moment, it has a reference to it. Thereforeself takes advantage of that moment to hand the TracksViewController instance the information it needs. There will be no better moment to do this. Knowing the moment, and taking care not to miss it, is part of the art of data communication.

Nib-loading is also a case in point. The loading of a nib is a way of instantiating objects from the nib. Proper preparation is essential in order to ensure that there’s a reference for those objects, so that they don’t simply vanish in a puff of smoke (Nib Loading and Memory Management). The moment of the nib loading is the moment when the nib’s owner or the code that loads the nib is in contact with those objects; it takes advantage of that moment to secure those references.

Beginners are often puzzled by how two objects are to get a reference to one another if they will be instantiated from different nibs — either different .xib files or different scenes in a storyboard. It is frustrating that you can’t draw a connection between an object in nib A and an object in nib B; it’s particularly frustrating when you can see both objects sitting right there in the same storyboard. But, as I explained earlier (Connections Between Nibs — Not!), such a connection would be meaningless, which is why it’s impossible. These are different nibs, and they will load at different times. However, some object (Manny) is going to be the owner when nib A loads, and some object (Jack) is going to be the owner when nib B loads. Perhaps they (Manny and Jack) can then see each other, in which case, given all the necessary outlets, the problem is solved. Or perhaps some third object (Moe) can see both of them and will provide a communication path for them.

For example, when a segue in a storyboard is triggered, the segue’s destination view controller is instantiated, and the segue has a reference to it. At the same time, the segue’s source view controller already exists, and the segue has a reference to it as well. So the segue sends the source view controller the prepareForSegue:sender: message, containing a reference to itself (the segue). The segue is Moe; it is bringing Manny (the source view controller) and Jack (the destination view controller) together. This is the source view controller’s chance (Manny’s moment) to obtain a reference to the newly instantiated destination view controller (a reference to Jack), by asking the segue for it — and now the source view controller can make itself the destination view controller’s delegate, hand it any needed information, and so forth.

Visibility by Relationship

Objects may acquire the ability to see one another automatically by virtue of their position in a containing structure. Before worrying about how to supply one object with a reference to another, consider whether there may already be a chain of references leading from one to the other.

For example, a subview can see its superview, through its superview property. A superview can see all its subviews, through its subviews property, and can pick out a specific subview through that subview’s tag property, by calling viewWithTag:. A subview in a window can see its window, through its window property. Thus, by working your way up or down the view hierarchy by means of these properties, it may be possible to obtain the desired reference.

Similarly, a responder (Chapter 11) can see the next object up the responder chain, through the nextResponder method — which also means, because of the structure of the responder chain, that a view controller’s main view can see the view controller. In this code from one of my apps, I work my way up from a view some way down the view hierarchy to obtain a reference to the view controller that’s in charge of this whole scene (and there are similar examples in Chapter 5):

var r : UIResponder

for (r = self; !(r is UIViewController); r = r.nextResponder()!) {}

Similarly, view controllers are themselves part of a hierarchy and therefore can see one another. If a view controller is currently presenting a view through a second view controller, the latter is the former’s presentedViewController, and the former is the latter’spresentingViewController. If a view controller is the child of a UINavigationController, the latter is its navigationController. A UINavigationController’s visible view is controlled by its visibleViewController. And from any of these, you can reach the view controller’s view through its view property, and so forth.

All of these relationships are public. So if you can get a reference to just one object within any of these structures or a similar structure, you can effectively navigate the whole structure through a chain of references and lay your hands on any other object within the structure.

Global Visibility

Some objects are globally visible — that is, they are visible to all other objects. Object types themselves are an important example. As I pointed out in Chapter 4, it is perfectly reasonable to use a Swift struct with static members as a way of providing globally available namespaced constants (Struct As Namespace).

Classes sometimes have class methods that vend singleton instances. Some of these singletons, in turn, have properties pointing to other objects, making those other objects likewise globally visible. For example, any object can see the singleton UIApplication instance by callingUIApplication.sharedApplication(). So any object can also see the app’s primary window, because that is the singleton UIApplication instance’s keyWindow property, and any object can see the app delegate, because that is its delegate property. And the chain continues: any object can see the app’s root view controller, because that is the primary window’s rootViewController — and from there, as I said in the previous section, we can navigate the view controller hierarchy and the view hierarchy.

You, too, can make your own objects globally visible by attaching them to a globally visible object. For example, a public property of the app delegate, which you are free to create, is globally visible by virtue of the app delegate being globally visible (by virtue of the shared application being globally visible).

Another globally visible object is the shared defaults object obtained by calling NSUserDefaults.standardUserDefaults(). This object is the gateway to storage and retrieval of user defaults, which is similar to a dictionary (a collection of values named by keys). The user defaults are automatically saved when your application quits and are automatically available when your application is launched again later, so they are one of the ways in which your app maintains information between launches. But, being globally visible, they are also a conduit for communicating values within your app.

For example, in one of my apps there’s a setting I call HazyStripy. This determines whether a certain visible interface object (a card in a game) is drawn with a hazy fill or a stripy fill. This is a setting that the user can change, so there is a preferences interface allowing the user to make this change. When the user displays this preferences interface, I examine the HazyStripy setting in the user defaults to configure the interface to reflect it in a segmented control (called self.hazyStripy):

func setHazyStripy () {

    let hs = NSUserDefaults.standardUserDefaults()

        .objectForKey(Default.HazyStripy) as! Int

    self.hazyStripy.selectedSegmentIndex = hs

}

Conversely, if the user interacts with the preferences interface, tapping the hazyStripy segmented control to change its setting, I respond by changing the actual HazyStripy setting in the user defaults:

@IBAction func hazyStripyChange(sender:AnyObject) {

    let hs = self.hazyStripy.selectedSegmentIndex

    NSUserDefaults.standardUserDefaults().setObject(

        hs, forKey: Default.HazyStripy)

}

But here’s the really interesting part. The preferences interface is not the only object that uses the HazyStripy setting in the user defaults; the drawing code that actually draws the hazy-or-stripy-filled card also uses it, so as to know how the card should draw itself! When the user leaves the preferences interface and the card game reappears, the cards are redrawn — consulting the HazyStripy setting in NSUserDefaults in order to do so. Thus there is no need for the card object and the view controller object that manages the preferences interface to be able to see one another, because they can both see this common object, the HazyStripy user default. NSUserDefaults becomes, in itself, a global conduit for communicating information from one part of my app to another.

Notifications and KVO

Notifications (Chapter 11) can be a way to communicate between objects that are conceptually distant from one another without bothering to provide any way for one to see the other. All they really need to have in common is a knowledge of the name of the notification. Every object can see the notification center — it is a globally visible object — so every object can arrange to post or receive a notification.

Using a notification in this way may seem lazy, an evasion of your responsibility to architect your objects sensibly. But sometimes one object doesn’t need to know, and indeed shouldn’t know, what object (or objects) it is sending a message to.

Recall the example I gave in Chapter 11. In a simple card game app, the game needs to know when a card is tapped. A card, when it is tapped, knowing nothing about the game, simply emits a virtual shriek by posting a notification; the game object has registered for this notification and takes over from there:

func singleTap(_:AnyObject) { // select me

    NSNotificationCenter.defaultCenter()

        .postNotificationName("cardTapped", object: self)

}

Here’s another example, taking advantage of the fact that notifications are a broadcast mechanism. In one of my apps, the app delegate may detect a need to tear down the interface and build it back up again from scratch. If this is to happen without causing memory leaks (and all sorts of other havoc), every view controller that is currently running a repeating NSTimer needs to invalidate that timer (Chapter 12). Rather than my having to work out what view controllers those might be, and endowing every view controller with a method that can be called, I simply have the app delegate shout “Everybody stop timers!”, by posting a notification. All my view controllers that run timers have registered for this notification, and they know what to do when they receive it.

Similarly, KVO (Chapter 11) can be used to keep two conceptually distant objects synchronized with one another: a property of one object changes, and the other object hears about the change.

Model–View–Controller

In Apple’s documentation and elsewhere, you’ll find references to the term model–view–controller, or MVC. This refers to an architectural goal of maintaining a distinction between three functional aspects of a program where the user can view and edit information — meaning, in effect, a program with a graphical user interface. The notion goes back to the days of Smalltalk, and much has been written about it since then, but informally, here’s what the terms mean:

Model

The data and its management, often referred to as the program’s “business logic;” the hard-core stuff that the program is really all about.

View

What the user sees and interacts with.

Controller

The mediation between the model and the view.

Consider, for example, a game where the current score is displayed to the user:

§  A UILabel that shows the user the current score for the game in progress is view; it is effectively nothing but a pixel-maker, and its business is to know how to draw itself. The knowledge of what it should draw — the score, and the fact that this is a score — lies elsewhere.

A rookie programmer might try to use the score displayed by the UILabel as the actual score: to increment the score, read the UILabel’s string, turn that string into a number, increment the number, turn the number back into a string, and present that string in place of the previous string. That is a gross violation of the MVC philosophy! The view presented to the user should reflect the score; it should not store the score.

§  The score is data being maintained internally; it is model. It could be as simple as an instance property along with a public increment method or as complicated as a Score object with a raft of methods.

The score is numeric, whereas a UILabel displays a string; this alone is enough to show that the view and the model are naturally different.

§  Telling the score when to change, and causing the updated score to be reflected in the user interface, is the work of the controller. This will be particularly clear if we imagine that the model’s numeric score needs to be transformed in some way for presentation to the user.

For example, suppose the UILabel that presents the score reads: “The score is 20.” The model is presumably storing and providing the number 20, so what’s the source of the phrase “The score is…”? Whoever is causing this phrase to precede the score in the presentation of the score to the user is a controller.

Model–view–controller

Figure 13-1. Model–view–controller

Even this simplistic example (Figure 13-1) illustrates very well the advantages of MVC. By separating powers in this way, we allow the aspects of the program to evolve with a great degree of independence. Do you want a different font and size in the presentation of the score? Change the view; the model and controller need know nothing about it, but will just go on working exactly as they did before. Do you want to change the phrase that precedes the score? Change the controller; the model and view are unchanged.

Adherence to MVC is particularly appropriate in a Cocoa app, because Cocoa itself adheres to it. The very names of Cocoa classes reveal the MVC philosophy that underlies them. A UIView is a view. A UIViewController is a controller; its purpose is to embody the logic that tells the view what to display. In Chapter 11 we saw that a UIPickerView does not hold the data it displays; it gets that data from a data source. So the UIPickerView is a view; the data maintained by the data source is model.

A further distinction, found in Apple’s documentation, is this: true model material and true view material should be quite reusable, in the sense that they can be transferred wholesale into some other app; controller material is generally not reusable, because it is concerned with how this app mediates between the model and the view.

In one of my own apps, for example, we download an XML (RSS) news feed and present the article titles to the user as a table. The storage and parsing of the XML are pure model material, and are so reusable that I didn’t even write this part of the code (I used some code called FeedParser, by Kevin Ballard). The table is a UITableView, which is obviously reusable, seeing as I obtained it directly from Cocoa. But when the UITableView turns to my code and asks what to display in this cell, and my code turns to the XML and asks for the title of the article corresponding to this row of the table, that’s controller code, and is applicable only to this app.

MVC helps to provide answers about what objects need to be able to see what other objects in your app. A controller object will usually need to see a model object and a view object. A model object, or a group of model objects, usually won’t need to see outside itself. A view object typically doesn’t need to see outside itself specifically, but structural devices such as delegation, data source, and target–action allow a view object to communicate agnostically with a controller.