All of your app’s executable code lies in its functions. The impetus for a function being called must come from somewhere. One of your functions may call another, but who will call the first function in the first place? How, ultimately, will any of your code ever run?
After your app has completely finished launching, none of your code runs. UIApplicationMain
(see “How an App Gets Going”) just sits and loops — the event loop — waiting for something to happen. In general, the user needs to do something, such as touching the screen, or switching away from your app. When something does happen, the runtime detects it and informs your app, and Cocoa can call your code.
But Cocoa can call your code only if your code is there to be called. Your code is like a panel of buttons, ready for Cocoa to press one. If something happens that Cocoa feels your code needs to know about and respond to, it presses the right button — if the right button is there. Cocoa wants to send your code a message, but your code must have ears to hear.
The art of Cocoa programming lies in knowing what messages Cocoa would like to send your app. You organize your code, right from the start, with those messages in mind. Cocoa makes certain promises about how and when it will dispatch messages to your code. These are Cocoa’s events. Your job is to know what those events are and how they will arrive; armed with that knowledge, you can arrange for your code to respond to them.
Broadly speaking, the reasons you might receive an event may be divided informally into four categories. These categories are not official; I made them up. Often it isn’t completely clear which of these categories an event fits into. But they are still generally useful for visualizing how and why Cocoa interacts with your code:
The user does something interactive, and an event is triggered directly. Obvious examples are events that you get when the user taps or swipes the screen, or types a key on the keyboard.
These are events notifying you of the arrival of a stage in the life of the app, such as the fact that the app is starting up or is about to go into the background, or of a component of the app, such as the fact that a UIViewController’s view has just loaded or is about to be removed from the screen.
Cocoa is about to do something by calling its own code, and is willing to let you subclass and override that code so as to modify its behavior. I would put into this category UIView’s draw(_:)
(your chance to have a view draw itself),
with which we experimented in Chapter 10.
Cocoa turns to you to ask a question; its behavior will depend upon your answer. The way data appears in a table (a UITableView) is that Cocoa asks you how many rows the table should have, and then, for each row, asks you for the corresponding cell.
A built-in Cocoa class may define methods that Cocoa itself will call if you override them in a subclass, so that your custom behavior, and not (merely) the default behavior, will take place. As I explained in Chapter 10, this is not a commonly used architecture in Cocoa, but for many classes it’s there if you need it, and for certain classes it is downright essential. UIView and UIViewController are the best examples.
UIView’s draw(_:)
is what I call a functional event. By default it does nothing, but by overriding it in a UIView subclass, you dictate how a view draws itself. You don’t know exactly when this method will be called, and you don’t care; when it is, you draw, and this guarantees that the view will always appear the way you want it to.
UIViewController is a class meant for subclassing, and is probably the only Cocoa class that you will regularly subclass. Of the methods listed in the UIViewController class documentation, just about all are methods you might have reason to override. If you create a UIViewController subclass in Xcode, you’ll see that the template already includes some method overrides to get you started. viewDidLoad
is called to let you know that your view controller has obtained its main view (its view
), so that you can perform initializations; it’s an obvious example of a lifetime event. And UIViewController has many other lifetime events that you can and will override in order to get fine control over what happens when.
Not only methods but also properties may be overridden in order to get an event. A case in point is UIViewController’s supportedInterfaceOrientations
. You’ll override this property as a computed variable in order to receive what I call a query event. Whenever Cocoa wants to know what orientations your view can appear in, it fetches the value of this property; your getter is a function that is called at that moment, and its job is to return a bitmask (“Option sets”) providing the answer to that question. You trust Cocoa to trigger this call at the appropriate moments, so that if the user rotates the device, your app’s interface will or won’t be rotated to compensate, depending on what value you return.
When you’re looking for events that you can receive through subclassing, be sure to look upward though the inheritance hierarchy. If you’re wondering how to get an event when your custom UILabel subclass is embedded into another view, you won’t find the answer in the UILabel class documentation; a UILabel receives the appropriate event by virtue of being a UIView. In the UIView class documentation, you’ll learn that you can override didMoveToSuperview
to be informed when this happens.
By the same token, look upward through adopted protocols as well. If you’re wondering how to get an event when your view controller’s view is about to undergo app rotation, you won’t find out by looking in the UIViewController class documentation; a UIViewController receives the appropriate event by virtue of adopting the UIContentContainer protocol. In the UIContentContainer protocol documentation, you’ll learn that you can override viewWillTransition(to:with:)
.
Cocoa provides your app with a single NotificationCenter instance (Objective-C NSNotificationCenter), available as NotificationCenter.default
. This instance, the notification center, is the basis of a mechanism for sending and receiving messages called notifications. A notification is a Notification instance (Objective-C NSNotification).
Think of a notification as having a topic and a sender. The topic is some subject matter that might be of interest to others; the sender is some object that others might be interested in hearing from. The notification center functions as a kind of broker for message transmission:
A potential recipient of messages can register with the notification center, saying: “Hey, if any messages on this topic or from this sender arrive, please pass them on to me.”
A sender does in fact hand the notification center a message to send out; this is called posting a notification.
When the notification center receives a posting on a certain topic or from a certain sender, it looks through its list of registered recipients and passes along the message to any recipients that match.
More than one recipient can register for messages with the same topic or sender. The notification mechanism is well described as a dispatching or broadcasting mechanism. It lets the poster send a message without knowing or caring whether there are recipients or, if there are, who or how many they many be. And it lets the recipient arrange to receive the message without being in direct contact with the sender (possibly without even knowing who the sender is).
Who can post a notification? Anyone who cares to! There are two main posters of notifications to consider — Cocoa and you:
Cocoa posts notifications through the notification center, and your code can register to receive them. Thus, notifications are a way of receiving events from Cocoa. You’ll find a separate Notifications section in the documentation for a class that provides them.
You can post notifications yourself as a way of communicating with your own code. This relieves your app’s architecture from the formal responsibility of somehow hooking up instances just so a message can pass from one to the other (which can sometimes be quite tricky or onerous, as I’ll discuss in Chapter 13). When objects are conceptually “distant” from one another, notifications can be a fairly lightweight way of permitting one to message the other.
A Notification instance has three pieces of information associated with it, which can be retrieved through properties:
name
A string which identifies the topic of the notification. This string is typed as Notification.Name, a struct adopting RawRepresentable with a String rawValue
. Built-in Cocoa notification names are vended as static/class Notification.Name properties, either of Notification.Name itself or of the class that sends them.
object
An instance associated with the notification; typically, the sender who posted it.
userInfo
An Optional dictionary; if not nil
, it contains additional information associated with the notification. What information it will contain, and under what keys, depends on the particular notification; you have to consult the documentation.
When you post a notification yourself, you can put anything you like into the userInfo
for the notification’s recipient(s) to retrieve.
Do not misuse a notification’s object
as a way of passing along a value.
That’s what the userInfo
is for.
To register to receive notifications, you send one of two messages to the notification center.
One way to register for notifications is to call the notification center’s addObserver(_:selector:name:object:)
. The parameters are:
observer:
The first parameter is the instance to which the notification is to be sent. This will typically be self
; it would be quite unusual for one instance to register a different instance as the receiver of a notification.
selector:
The message to be sent to the observer instance when the notification occurs. The designated method should take one parameter, which will be the Notification instance. The selector must specify correctly a method that is exposed to Objective-C; Swift’s #selector
syntax will help you with that (see Chapter 2).
name:
The name
of the notification you’d like to receive. (This is what I’ve been calling the topic.) If this is nil
, you’re asking to receive all notifications associated with the object designated in the object:
parameter.
object:
The object
of the notification you’re interested in, which will usually be the object that posted it. (This is what I’ve been calling the sender.) If this is nil
, you’re asking to receive all notifications with the name designated in the name:
parameter. (If both the name:
and object:
parameters are nil
, you’re asking to receive all notifications!)
Here’s a real-life example. There is a music player belonging to the MPMusicPlayerController class; this class promises to post a notification whenever the music player starts playing a different song. (To find this out, I look under Notifications in the MPMusicPlayerController class documentation; the notification in question is called MPMusicPlayerControllerNowPlayingItemDidChange
.) In my app, I want to receive that notification and change my interface accordingly.
It turns out that this notification won’t be posted unless I first call MPMusicPlayerController’s beginGeneratingPlaybackNotifications
instance method. This architecture is not uncommon; Cocoa saves itself some time and effort by not sending out certain notifications unless they are switched on, as it were. So my first job is to get an instance of MPMusicPlayerController and call this method:
let mp = MPMusicPlayerController.systemMusicPlayer mp.beginGeneratingPlaybackNotifications()
Now I register myself to receive the desired playback notification:
NotificationCenter.default.addObserver(self, selector: #selector(nowPlayingItemChanged), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
As a result, whenever an MPMusicPlayerControllerNowPlayingItemDidChange
notification is posted, my nowPlayingItemChanged
method will be called. Note that this method must be marked @objc
so that Objective-C can see it (the Swift compiler will help out by ensuring this when you use #selector
syntax):
@objc func nowPlayingItemChanged (_ n:Notification) { self.updateNowPlayingItem() // ... and so on ... }
Heavy use of addObserver(_:selector:name:object:)
means that your code ends up peppered with methods that exist solely in order to be called by the notification center. There is nothing about these methods that tells you what they are for — you may want to use explicit comments to remind yourself — and the methods are separate from the registration call, which can make your code rather confusing.
One way to solve that problem is to use the other way of registering to receive a notification, addObserver(forName:object:queue:using:)
. The parameters are:
The queue:
will usually be nil
; a non-nil
queue:
is for background threading.
The name:
and object:
parameters are just like those of the addObserver(_:selector:name:object:)
method.
Instead of providing an observer and a selector, you provide (as the using:
parameter) a function consisting of the actual code to be executed when the notification arrives. This function should take one parameter — the Notification itself. You can use an anonymous function, and typically you will.
This method also returns a value, which is in fact the observer that has been registered with the notification center. I’ll talk more about that in a moment.
The outcome is that your registration for a notification and your response when the notification arrives are encapsulated in a single call:
let ob = NotificationCenter.default.addObserver( forName: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil, queue: nil) { _ in self.updateNowPlayingItem() // ... and so on ... }
That can be a much cleaner way of dealing with notifications. Unfortunately, though, using addObserver(forName:...)
correctly is a little more complicated than that, because you still need to unregister the observer, as I’ll discuss in the next section.
To unregister an object as a recipient of notifications, call the notification center’s removeObserver(_:)
method. Alternatively, you can unregister an object for just a specific set of notifications with removeObserver(_:name:object:)
. The object passed as the first argument is the object that is no longer to receive notifications. What object that is depends on how you registered it in the first place:
addObserver(_:selector:name:object:)
You supplied an observer originally, as the first argument; that is the observer you will now unregister. This will typically be self
.
addObserver(forName:object:queue:using:)
The call returned an observer token object typed as an NSObjectProtocol (its real class and nature are undocumented); that is the observer you will now unregister.
In the old days, if you failed to unregister an object as a notification recipient and that object went out of existence, your app would crash the next time the notification was sent — because the runtime was trying to send a message to an object that was now missing in action. But in iOS 9, Apple introduced a safety check. Nowadays, if the notification center tries to send a message to a nonexistent object, there is no crash, and the notification center helpfully unregisters the object for you.
What you need to do as you go out of existence depends, once again, on how you registered in the first place:
addObserver(_:selector:name:object:)
You probably don’t need to unregister the object passed as the first argument. If that object goes out of existence, and if the notification is posted subsequently, there won’t be any crash.
addObserver(forName:object:queue:using:)
You do need to unregister the observer, because otherwise the notification center keeps it alive and can continue to send notifications to it (which means that the attached function will continue to be called).
So the question now boils down to how you’re going to unregister the observer returned by a call to addObserver(forName:object:queue:using:)
. If you only need to receive a notification once, you can unregister from within the anonymous function that runs when the notification is received (because the observer is in scope within the anonymous function). Otherwise, you’ll have to keep a separate persistent reference to the observer object so that you can unregister it later.
What’s a good way to do that? Let’s assume you’re going to be calling addObserver(forName:object:queue:using:)
many times from within the same class. Then you’re going to end up receiving many observer tokens, and you’ll need to preserve a reference to all of them. One obvious approach is to store the observers in an instance property that is a mutable collection. My favored approach is a Set property:
var observers = Set<NSObject>()
Each time I register for a notification by calling addObserver(forName:object:queue:using:)
, I capture the result and add it to the set:
let ob = NotificationCenter.default.addObserver( forName: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil, queue: nil) { _ in self.updateNowPlayingItem() // ... and so on ... } self.observers.insert(ob as! NSObject)
When it’s time to unregister all observers, I enumerate the set and empty it:
for ob in self.observers { NotificationCenter.default.removeObserver(ob) } self.observers.removeAll()
Use of addObserver(forName:...)
can also involve you in some memory management complications that I’ll talk about in Chapter 12.
Notifications can be a way of communicating between your own objects. You post a notification yourself (in one object) and receive it yourself (in another object). This is probably not a good way to compensate for a failure to devise proper lines of communication between objects, but it can be appropriate when the objects are conceptually distant or independent from one another, or when you need the flexibility of broadcasting to multiple recipients.
To post a notification, send post(name:object:userInfo:)
to the notification center. You are defining the name:
yourself, so you’ll have to coerce a string into a Notification.Name.
There are two main places to do this:
name:
argumentYou perform the coercion directly in the method call. That’s simple but error-prone: you’ll need to perform the same coercion twice (to post the notification and to register to receive it), and the repeated string literal is an invitation to make a typing mistake and have things mysteriously go wrong.
You define a namespaced constant and use it both when posting the notification and when registering for it. This approach localizes the coercion in a single place; it’s a little more work than the first approach, but it’s more correct and you should use it.
For example, one of my apps is a simple card game. The game needs to know when a card is tapped. But a card knows nothing about the game; when it is tapped, it simply emits a virtual shriek by posting a notification. I’ve defined my notification name by extending my Card class:
extension Card { static let tappedNotification = Notification.Name("cardTapped") }
When a card is tapped, it responds like this:
NotificationCenter.default.post(name: Self.tappedNotification, object: self)
The game object has registered for Card.tappedNotification
, so it hears about this and retrieves the notification’s object
; now it knows what card was tapped and can proceed appropriately.
The notification center has no API for introspecting it in code, but you can introspect it while paused in the debugger; enter po NotificationCenter.default
to see a list of registered notifications, with the name, object, recipient, and options for each. The object and recipient are listed as memory addresses, but you can learn more from such an address by entering expr -l objc -O --
followed by the address.
A Timer (Objective-C NSTimer) is not a notification, but it behaves quite similarly. It gives off a signal (fires) after the lapse of a certain time interval. Thus you can arrange to get an event when a certain time has elapsed. The timing is not perfectly accurate, nor is it intended to be, but it’s good enough for most purposes.
A timer that is actively watching the clock is said to be scheduled. A timer may fire once, or it may be a repeating timer. To stop a timer, it must be invalidated. A timer that is set to fire once is invalidated automatically after it fires; a repeating timer repeats until you invalidate it by sending it the invalidate
message. An invalidated timer should be regarded as dead: you cannot revive it or use it for anything further, and you should probably not send any messages to it.
For example, one of my apps is a game with a score; I want to penalize the user by diminishing the score for every ten seconds that elapses after each move without the user making a further move. So I create and schedule a repeating timer whose time interval is ten seconds. Whenever the timer fires, I diminish the score. Whenever the user moves, I invalidate the existing timer and start over with a new repeating timer.
The simplest way to create a timer is with a class method that also schedules the timer, so that it begins watching the clock immediately:
scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
The target:
and selector:
determine what message will be sent to what object when the timer fires; the method in question should take one parameter, which will be a reference to the timer. The userInfo:
is just like the userInfo:
of a notification.
scheduledTimer(withTimeInterval:repeats:block:)
You provide a function to be called when the timer fires; the function should take one parameter, which will be a reference to the timer.
A repeating Timer is often maintained as an instance property, so that you can invalidate it later on. But be careful! There is a temptation to call scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
directly as the initializer in your declaration of a Timer instance property, like this:
class ViewController : UIViewController { var timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
If the target
is self
, that won’t work, because self
doesn’t exist yet at the time you’re initializing the instance property.
(In my opinion, Swift should warn you about this, and I regard its failure to do so as a bug.) Use deferred initialization instead (“Deferred initialization of properties”):
class ViewController: UIViewController { var timer : Timer! override func viewDidLoad() { super.viewDidLoad() self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true) }
Timers have some memory management implications that I’ll be discussing in Chapter 12.
Delegation is an object-oriented design pattern, a relationship between two objects in which a primary object’s behavior is customized or assisted by a secondary object. The secondary object is the primary object’s delegate. No subclassing is involved, and indeed the primary object is agnostic about the delegate’s class.
The class of the primary object can be Cocoa’s class or your class. As with notifications, you’ll want to understand Cocoa’s delegation pattern because it’s an important way of getting events from Cocoa, but you might also want to implement the pattern yourself as a useful way of communicating between your own objects.
As implemented by Cocoa, here’s how delegation works:
A built-in Cocoa class has an instance property, usually called delegate
(it will certainly have delegate
in its name).
The Cocoa class promises that at certain moments it will turn to its delegate for instructions by sending it a certain message.
One of those moments arrives! If the Cocoa instance finds that its delegate is not nil
, and that its delegate is prepared to receive that message, the Cocoa instance sends the message to the delegate.
Delegation is one of Cocoa’s main uses of protocols (Chapter 10). In the old days, delegate methods were listed in the Cocoa class’s documentation, and their names were made known to the compiler through an informal protocol (a category on NSObject). Nowadays, a class’s delegate methods are usually listed in a genuine protocol with its own documentation. There are over 70 Cocoa delegate protocols, showing how heavily Cocoa relies on delegation. Most delegate methods are optional, but in a few cases you’ll discover some that are required.
To take advantage of Cocoa delegation, you’ll have one of your classes adopt a Cocoa delegate protocol, and you’ll set some Cocoa object’s delegate
(typed as that protocol) to an instance of your class. You might form the connection in code; alternatively, you might do it in a nib by connecting an object’s delegate
outlet to an appropriate object within the nib. Now you are the delegate, and you get to help determine the Cocoa object’s behavior.
Your delegate class will probably do other things besides serving as this instance’s delegate. Indeed, one of the nice things about delegation is that it leaves you free to slot delegate code into your class architecture however you like; the delegate type is a protocol, so the actual delegate can be an instance of any class.
Here’s a typical example. I want to ensure that my app’s root view controller, a UINavigationController, should appear only in portrait orientation when this view controller is in charge. But UINavigationController isn’t my class; my class is a different view controller, a UIViewController subclass, which acts as the UINavigationController’s child. How can the child tell the parent how to rotate?
Delegation to the rescue! UINavigationController has a delegate
property, typed as UINavigationControllerDelegate (a protocol). It promises to send this delegate the navigationControllerSupportedInterfaceOrientations(_:)
message when it needs to know how to rotate. So my view controller, very early in its lifetime, sets itself as the UINavigationController’s delegate. It also implements the navigationControllerSupportedInterfaceOrientations(_:)
method. Presto, the problem is solved:
class ViewController : UIViewController, UINavigationControllerDelegate { override func viewDidLoad() { super.viewDidLoad() self.navigationController?.delegate = self } func navigationControllerSupportedInterfaceOrientations( _ nav: UINavigationController) -> UIInterfaceOrientationMask { return .portrait } }
When you’re searching the documentation for how you can be notified of a certain event, be sure to consult the corresponding delegate protocol, if there is one. Suppose you’d like to know when the user taps in a UITextField to start editing it. You won’t find anything relevant in the UITextField class documentation; what you’re after is textFieldDidBeginEditing(_:)
in the UITextFieldDelegate protocol.
You might be tempted to try to inject a method into a class that adopts a Cocoa delegate protocol by extending the protocol and implementing the delegate method in the protocol extension. That isn’t going to work, because Objective-C can’t see Swift protocol extensions (see Appendix A). You can call such a method from Swift, but Cocoa is never going to call it, because it doesn’t know that the method implementation exists.
The Cocoa protocol-and-delegate pattern is very useful, and you’ll probably want to adopt it in your own code. Setting up the pattern takes some practice, and can be a little time-consuming. But it’s a clean solution to the problem of apportioning knowledge and responsibilities among your objects. I’ll demonstrate with an example from one of my apps.
The app declares a view controller, a UIViewController subclass called ColorPickerController; its view contains three sliders that the user can move to choose a color. Some other view controller will create and present the ColorPickerController instance, displaying its view. When the user taps Done or Cancel, the view should be dismissed and the ColorPickerController instance can go out of existence; but first, I need to send a message from the ColorPickerController instance back to the view controller that presented it, reporting what color the user chose.
Here’s the declaration for the message that I want the ColorPickerController to send before it goes out of existence:
func colorPicker(_ picker:ColorPickerController, didSetColorNamed theName:String?, to theColor:UIColor?)
The question is: where and how should this method be declared?
Now, it happens that in my app I know the class of the instance that will in fact present the ColorPickerController: it is a SettingsController. So I could simply declare this method in SettingsController and stop. But that would mean that the ColorPickerController, in order to send this message to the SettingsController, must know that the instance that presented it is a SettingsController. That’s wrong. Surely it is a mere contingent fact that the instance being sent this message is a SettingsController; it should be open to any class to present and dismiss a ColorPickerController.
Therefore we want ColorPickerController itself to declare the method that it itself is going to call; and we want it to send that message blindly to some receiver, without regard to the class of that receiver. That’s what a protocol is for!
The solution, then, is for ColorPickerController to define a protocol, with this method as part of that protocol, and for the class that presents a ColorPickerController to conform to that protocol. ColorPickerController will also need an appropriately typed delegate
instance property; this provides the channel of communication, and tells the compiler that sending this message is legal:
protocol ColorPickerDelegate : AnyObject { // color == nil on cancel func colorPicker(_ picker:ColorPickerController, didSetColorNamed theName:String?, to theColor:UIColor?) } class ColorPickerController : UIViewController { weak var delegate: ColorPickerDelegate? // ... }
(For the weak
attribute and the AnyObject
designation, see Chapter 5.) When my SettingsController instance creates and configures and presents a ColorPickerController instance, it also sets itself as that ColorPickerController’s delegate
— which it can do, because it adopts the protocol:
extension SettingsController : ColorPickerDelegate { func showColorPicker() { let colorName = // ... let c = // ... let cpc = ColorPickerController(colorName:colorName, color:c) cpc.delegate = self self.present(cpc, animated: true) } func colorPicker(_ picker:ColorPickerController, didSetColorNamed theName:String?, to theColor:UIColor?) { // ... } }
When the user picks a color, the ColorPickerController knows to whom it should send colorPicker(_:didSetColorNamed:to:)
— namely, its delegate! And the compiler allows this, because the delegate has adopted the ColorPickerDelegate protocol:
@IBAction func dismissColorPicker(_ sender : Any?) { // user tapped Done let c : UIColor? = self.color self.delegate?.colorPicker(self, didSetColorNamed: self.colorName, to: c) }
A data source is like a delegate, except that its methods supply the data for another object to display. The chief Cocoa classes with data sources are UITableView, UICollectionView, UIPickerView, and UIPageViewController. In each case, the data source must formally adopt a data source protocol with required methods.
It comes as a surprise to some beginners that a data source is necessary at all. Why isn’t a table’s data just a property of the table? The reason is that such an architecture would violate generality. A view displays data; the structure and management of that data is a separate matter, and is up to the data source. The only requirement is that the data source must be able to supply information quickly, because it will be asked for it in real time when the data needs displaying.
Another surprise is that the data source is different from the delegate. But this again is only for generality; it’s an option, not a requirement. There is no reason why the data source and the delegate should not be the same object, and most of the time they probably will be.
In this example from one of my apps, I implement a UIPickerView that allows the user to configure a game by saying how many stages it should consist of (“1 Stage,” “2 Stages,” and so on). The first two methods are UIPickerView data source methods; the third method is a UIPickerView delegate method. It takes all three methods to supply the picker view’s content:
extension NewGameController: UIPickerViewDataSource, UIPickerViewDelegate { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return 9 } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return "\(row+1) Stage" + ( row > 0 ? "s" : "") } }
An action is a message emitted by an instance of a UIControl subclass (a control) reporting a significant user event taking place in that control. The UIControl subclasses are all simple interface objects that the user can interact with directly, such as a button (UIButton) or a segmented control (UISegmentedControl).
The significant user events (control events) are listed under UIControl.Event in the Constants section of the UIControl class documentation. Different controls implement different control events: a segmented control’s Value Changed event signifies that the user has tapped a segment, but a button’s Touch Up Inside event signifies that the user has tapped the button. Of itself, a control event has no external effect; the control responds visually (a tapped button looks tapped), but it doesn’t automatically share the information that the event has taken place. If you want to know when a control event takes place, so that you can respond to it in your code, you must arrange for that control event to trigger an action message.
Here’s how it works. A control maintains an internal dispatch table: for each control event, there can be any number of target–action pairs, in each of which the action is a selector designating the name of a method, and the target is an object on which that method is to be called. When a control event occurs, the control consults its dispatch table, finds all the target–action pairs associated with that control event, and sends each action message to the corresponding target (Figure 11-1).
There are two ways to manipulate a control’s action dispatch table:
You can configure an action connection in a nib. I described in Chapter 7 how to do this, but I didn’t completely explain the underlying mechanism. Now all is revealed: an action connection formed in the nib editor is a visual way of configuring a control’s action dispatch table.
Your code can directly configure the control’s action dispatch table. The key method here is the UIControl instance method addTarget(_:action:for:)
, where the target:
is an object, the action:
is a selector, and the for:
parameter is a UIControl.Event bitmask (“Option sets”).
Recall the example of a control and its action from Chapter 7. We have a buttonPressed(_:)
method:
@IBAction func buttonPressed(_ sender: Any) { let alert = UIAlertController( title: "Howdy!", message: "You tapped me!", preferredStyle: .alert) alert.addAction( UIAlertAction(title: "OK", style: .cancel)) self.present(alert, animated: true) }
That sort of method is an action handler. Its purpose is to be called when the user taps a certain button in the interface. In Chapter 7, we arranged for that to happen by setting up an action connection in the nib: we connected the button’s Touch Up Inside event to the ViewController buttonPressed(_:)
method. In reality, we were forming a target–action pair and adding that target–action pair to the button’s dispatch table for the Touch Up Inside control event.
Instead of making that arrangement in the nib, we could have done the same thing in code. Suppose we had never drawn that action connection. And suppose that, instead, we have an outlet connection from the view controller to the button, called self.button
. Then the view controller, after the nib loads, can configure the button’s dispatch table like this:
self.button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
The signature for the action selector can be in any of three forms:
The fullest form takes two parameters:
The control.
The UIEvent that generated the control event. This will rarely be needed. (I’ll talk more about UIEvents in the next section.)
A shorter form, the one most commonly used, omits the second parameter. buttonPressed(_:)
is an example; it takes one parameter. When buttonPressed(_:)
is called through an action message emanating from the button, its parameter will be a reference to the button.
There is a still shorter form that omits both parameters.
Curiously, none of the action selector parameters provide any way to learn which control event triggered the current action selector call! To distinguish a Touch Up Inside control event from a Touch Up Outside control event, their corresponding target–action pairs must specify two different action handlers; if you dispatch them to the same action handler, that handler cannot discover which control event occurred.
A control event can have multiple target–action pairs. You might configure it this way intentionally, but it is also possible to do so accidentally. Unintentionally giving a control event a target–action pair without removing its existing target-action pair is an easy mistake to make, and can cause some very mysterious behavior. If you form an action connection in the nib and configure the dispatch table in code, a tap on the button will cause the action handler method to be called twice.
Whenever the user does something with a finger (sets it down on the screen, moves it, raises it from the screen), a touch object (UITouch) is used to represent that finger. UIEvents are the lowest-level objects charged with communication of touch objects to your app; a UIEvent is basically a timestamp (a Double) along with a collection (Set) of touch objects. As I said in the previous section, you can receive a UIEvent along with a control event, but you will rarely need to do so.
A responder is an object that knows how to receive UIEvents directly. It is an instance of UIResponder or a UIResponder subclass. If you examine the Cocoa class hierarchy, you’ll find that just about any class that has anything to do with display on the screen is a responder. A UIView is a responder. A UIWindow is a responder. A UIViewController is a responder. Even a UIApplication is a responder. Even the app delegate is a responder!
A UIResponder has four low-level methods for receiving touch-related UIEvents:
touchesBegan(_:with:)
touchesMoved(_:with:)
touchesEnded(_:with:)
touchesCancelled(_:with:)
These methods — the touch methods — are called to notify a responder that a touch event has occurred: the user has placed, moved, or lifted a finger from the screen. No matter how your code ultimately hears about a user-related touch event — indeed, even if your code never hears about a touch event directly — the touch was initially communicated to a responder through one of the touch methods.
The mechanism for this communication starts by deciding which responder the user touched. The UIView methods hitTest(_:with:)
and point(inside:with:)
are called until the correct view (the hit-test view) is located. Then UIApplication’s sendEvent(_:)
method is called, which calls UIWindow’s sendEvent(_:)
, which now wants to call the correct touch method in some responder.
So now the runtime starts looking for a responder that implements the correct touch method, so that the touch event can be reported by calling it. That responder need not be the hit-test view! The hit-test view is just the starting place for the search. The search depends upon the fact that your app’s responders participate in a responder chain, which essentially links them up through the view hierarchy.
The responder chain, from bottom to top, looks roughly like this:
The UIView that we start with (here, the hit-test view).
If this UIView is a UIViewController’s view
, that UIViewController.
The UIView’s superview.
Go back to step 2 and repeat! Keep repeating until we reach…
The UIWindow (and, new in iOS 13, the UIWindowScene).
The UIApplication.
The UIApplication’s delegate.
The next responder up the responder chain is a responder’s next responder, which is obtained from a responder through its next
property (which returns an Optional wrapping a UIResponder). Thus the responder chain can be walked upward from any responder to the top of the chain.
A nil-targeted action is a UIControl target–action pair in which the target is nil
. There is no designated target object, so the following rule is used: starting with the hit-test view (the view with which the user is interacting), Cocoa walks up the responder chain looking for an object that can respond to the action message:
If a responder is found that handles this message, that method is called on that responder, and that’s the end.
If we get all the way to the top of the responder chain without finding a responder to handle this message, nothing happens; the message goes unhandled, with no penalty.
Here’s a UIButton subclass that configures itself to call a nil-targeted action when tapped:
override func awakeFromNib() { super.awakeFromNib() class Dummy { @objc func buttonPressed(_:Any) {} } self.addTarget(nil, // nil-targeted action: #selector(Dummy.buttonPressed), for: .touchUpInside) }
That’s a nil-targeted action. So what happens when the user taps the button? First, Cocoa looks in the UIButton itself to see whether it responds to buttonPressed
. If not, it looks in the UIView that is its superview. And so on, up the responder chain. There is surely a view controller that owns the view that contains the button; if the view controller is the first responder encountered in the search whose class implements buttonPressed
, tapping the button will cause the view controller’s buttonPressed
to be called — even though the view controller is not the target!
The declaration for your action handler method (such as buttonPressed
) must be marked @objc
(or @IBAction
). Otherwise, Cocoa won’t be able to find it as it walks up the responder chain.
It’s obvious how to construct a nil-targeted action in code: you set up a target–action pair where the target is nil
, as in the preceding example. But how do you construct a nil-targeted action in a nib? The answer is: you form a connection to the First Responder proxy object (in the dock). That’s what the First Responder proxy object is for! The First Responder isn’t a real object with a known class, so before you can connect an action to it, you have to define the action message within the First Responder proxy object, like this:
Select the First Responder proxy in the nib, and switch to the Attributes inspector.
You’ll see a table (probably empty) of user-defined nil-targeted First Responder actions. Click the Plus button and give the new action a name; it must take a single parameter (so that its name will end with a colon).
Now you can Control-drag from a control, such as a UIButton, to the First Responder proxy to specify a nil-targeted action with the name you specified.
Key–value observing, or KVO, is rather like a target–action mechanism that works between any two objects. One object (the observer) registers directly with another object (the observed) so as to be notified when a value in the observed object changes. The observed object doesn’t actually have to do anything; when the value in the observed object changes, the observer is automatically notified.
The process of using KVO may be broken down into stages:
The observer — that is, the object that desires to hear about future changes in a value belonging to the observed object — must register with that observed object.
A change takes place in the value belonging to the observed object, and it must take place in a special way — a KVO compliant way. Typically, this means using a key–value coding compliant accessor to make the change. Setting a property passes through a key–value coding compliant accessor.
The observer is automatically notified that the value in the observed object has changed.
The observer eventually unregisters to prevent the arrival of further notifications about the observed value of the observed object.
As with notifications and delegation, you can use KVO with Cocoa objects or you can implement it as a form of communication between your own objects. When you use KVO with Cocoa:
The observer will be your object; you will write the code that will respond when the observer is notified of the change for which it has registered.
The observed object will be Cocoa’s object. Many Cocoa objects promise to behave in a KVO compliant way. Certain frameworks, such as the AVFoundation framework, don’t implement delegation or notifications very much; instead, they expect you to use KVO to hear about what they are doing. Thus, KVO can be an important form of Cocoa event.
When you use KVO with your own observed object, you have to configure that object to be KVO compliant for one or more values. I’ll explain later how to do that.
There are two different ways to configure KVO registration and notification. The first way is the Cocoa way; it basically just translates the Objective-C API directly into Swift. The second way is provided by Swift; it uses the Cocoa way under the hood, but it shields you from some of the messy details.
I’ll describe the Cocoa way just so that you understand the mess that the Swift way shields you from. In the Cocoa way, you call addObserver(_:forKeyPath:options:context:)
on the object whose property you want to observe, using a Cocoa key path (Chapter 10). Subsequently, the observer’s observeValue(forKeyPath:of:change:context:)
is called for every change for which this observer has been registered. This single observer method constitutes a nasty bottleneck, especially if this observer is observing more than one value, possibly in more than one observed object. That’s what the Swift way protects you from.
Here’s how the Swift way works. You register by calling observe(_:options:changeHandler:)
on the object whose property you want to observe, with these parameters:
keyPath:
The first parameter is a Swift key path (Chapter 5), not a Cocoa key path.
options:
An NSKeyValueObservingOptions bitmask (an option set) specifying such things as when you want to be notified (only when the observed value changes, or now as well) and what information you want included in the notification (the old value, the new value, or both).
changeHandler:
A function to be called as a way of sending the notification. It will typically be an anonymous function, making it part of the registration. It should take two parameters:
This will be the observed object with which we are registered.
An NSKeyValueObservedChange object. Its properties give you information such as the old value and the new value if you requested them in the options:
argument.
The call to observe(_:options:changeHandler:)
also returns a value, an NSKeyValueObservation instance. That is the registered observer.
The Swift key–value observing API is a language feature, not an SDK feature. It puts a convenient mechanism in front of the Cocoa API, but it still uses the Cocoa API. When you call observe(_:options:changeHandler:)
, Swift calls addObserver(_:forKeyPath:options:context:)
to register the observer with the observed object. And the NSKeyValueObservation object implements the bottleneck method observeValue(forKeyPath:of:change:context:)
to receive notification messages, which it passes on to you.
Unregistration is performed through a message to the observed object, namely removeObserver(_:forKeyPath:context:)
. If the observer goes out of existence without unregistering, the observed object might later try to send a message to a nonexistent observer, resulting in a crash. So the observer needs to maintain a reference to the observed object and, at the latest, must unregister itself when going out of existence.
That can be a daunting responsibility — and is yet another thing that the Swift API helps you with. The NSKeyValueObservation object returned from the call to observe(_:options:changeHandler:)
maintains a reference to the observed object, so you don’t have to; and it will unregister itself (by calling removeObserver(_:forKeyPath:context:)
on the observed object) either if you send it the invalidate
message or automatically when it itself is about to go out of existence.
Your job is to capture the NSKeyValueObservation object and maintain it, probably in an instance property of the observer. That’s all, because this means it will go out of existence, at the latest, when the observer does — and at that moment will unregister itself in good order. But you must capture and maintain that object somehow! If you don’t, it will go out of existence and unregister itself immediately — before a notification is ever sent — and you’ll never get any notifications in the first place.
Another problem is what happens if the observed object goes out of existence when observers are still registered on it. There are two cases:
Nothing happens. This isn’t anything to worry about.
Your app will crash immediately. To prevent that, if the observed object is about to go out of existence while the observer continues to exist, you must unregister the NSKeyValueObservation object explicitly by sending it invalidate
.
To demonstrate KVO, I’ll declare classes to play both roles, the observer and the observed.
First, the observed. My Observed class has a value
instance property that we want other objects to be able to observe:
class Observed : NSObject { @objc dynamic var value : Bool = false }
The observed object’s class must derive from NSObject; otherwise, you won’t be able to call observe(_:options:changeHandler:)
on it. That’s because the mechanism for being observed is a feature of NSObject.
The property to be observed must be declared @objc
in order to expose it to Objective-C — and it must also be declared dynamic
. That’s because KVO works by swizzling the accessor methods; Cocoa needs to be able to reach right in and change this object’s code, and it can’t do that unless the property is dynamic
.
The Observer class contains code that registers with an Observed to hear about changes in its value
property:
class Observer { var obs = Set<NSKeyValueObservation>() func registerWith(_ observed:Observed) { let opts : NSKeyValueObservingOptions = [.old, .new] let ob = observed.observe(\.value, options: opts) { obj,change in if let oldValue = change.oldValue { print("old value was \(oldValue)") } if let newValue = change.newValue { print("new value is \(newValue)") } } obs.insert(ob) } }
Observer has an instance property for maintaining NSKeyValueObservation objects. As with Notification observer tokens (discussed earlier in this chapter), I like to use a Set for this purpose.
Observer (in its registerWith(_:)
method) will register with an Observed instance by calling observe(_:options:changeHandler:)
, using a Swift key path to specify the value
property. I’ve illustrated the use of NSKeyValueObservingOptions by asking for both the old and new values of the observed property when a notification arrives. That information will arrive into the notification function inside an NSKeyValueObservedChange object.
The call to observe(_:options:changeHandler:)
returns an NSKeyValueObservation object. It is crucial to ensure the continued existence of this object; otherwise, it will go out of existence and unregister itself before the notification can ever arrive. Therefore, I immediately store it in the Set instance property that was declared for this purpose.
Presume now that we have a persistent Observer instance, observer
, and that its registerWith(_:)
has been called with argument observed
, an Observed instance that is also persistent. So much for registration!
Now let’s talk about change and notification. Somehow, someone sets observed
’s value
to true
, which changes it in a KVO compliant way. At that moment, the notification is sent and the anonymous function is called! The following appears in the console:
old value was false new value is true
Finally, let’s talk about unregistering. As long as we are running in iOS 11 or later, there is nothing to talk about! It doesn’t matter whether observed
or observer
goes out of existence first; everything happens automatically and in good order:
If observed
goes out of existence first, there is no crash and there will be no further notifications.
When observer
goes out of existence, the obs
property is destroyed, and so the NSKeyValueObservation object is destroyed — and at that moment, if observed
still exists, the NSKeyValueObservation object unregisters itself (and if observed
no longer exists, nothing bad happens).
In general your real-life use of KVO in programming iOS will likely be no more complex than that. Cocoa key–value observing, however, is a deep and complex mechanism; consult Apple’s Key-Value Observing Programming Guide in the documentation archive for full information.
Cocoa has the potential to send lots of events, telling you what the user has done, informing you of each stage in the lifetime of your app and its objects, asking for your input on how to proceed. To receive the events that you need to hear about, your code is peppered with entry points — methods that you have written with just the right name and in just the right class so that they can be called as Cocoa events. In fact, it is easy to imagine that in many cases your code for a class will consist almost entirely of entry points.
Arranging all those entry points is one of your primary challenges as a Cocoa programmer. You know what you want to do, but you don’t get to “just do it.” You have to divide up your app’s functionality and allocate it in accordance with when and how Cocoa is going to call into your code. You know the events that Cocoa is going to want to send you, and you need to be prepared to receive them. Before you’ve written a single line of your own code, the skeleton structure of a class is likely to have been largely mapped out for you.
Suppose that your iPhone app presents an interface consisting of a table view. You’ll probably subclass UITableViewController (a built-in UIViewController subclass); an instance of your subclass will own and control the table view, and you’ll probably use it as the table view’s data source and delegate as well. In this single class, then, you’re likely to want to implement at a minimum the following methods:
init(coder:)
or init(nibName:bundle:)
UIViewController lifetime method, where you perform instance initializations.
viewDidLoad
UIViewController lifetime method, where you perform view-related initializations and deferred initializations.
viewDidAppear
UIViewController lifetime method, where you set up states that need to apply only while your view is onscreen. If you’re going to register for a notification or set up a timer, this is a likely place to do it.
viewDidDisappear
UIViewController lifetime method, where you reverse what you did in viewDidAppear
. This would be a likely place to unregister for a notification or invalidate a repeating timer that you set up in viewDidAppear
.
supportedInterfaceOrientations
UIViewController query method, where you specify what device orientations are allowed for this view controller’s main view.
numberOfSections(in:)
tableView(_:numberOfRowsInSection:)
tableView(_:cellForRowAt:)
UITableView data source query methods, where you specify the contents of the table.
tableView(_:didSelectRowAt:)
UITableView delegate user action method, where you respond when the user taps a row of the table.
deinit
Swift class instance lifetime method, where you perform end-of-life cleanup.
Suppose, further, that you do in fact use viewDidAppear
to register for a notification and to set up a timer, using the target–selector architecture; then you must also implement the methods specified by those selectors.
We already have, then, about a dozen methods whose presence is effectively boilerplate. These are not your methods; you are never going to call them. They are Cocoa’s methods, which you have placed here so that each can be called at the appropriate moment in the life story of your app.
A Cocoa program consists of numerous disconnected entry points, each with its own meaning, each called at its own set moment. The logic of such a program is far from obvious; a Cocoa program, even your program, even while you’re writing it, is hard to read and hard to understand. To figure out what our hypothetical class does, you have to know already such things as when viewDidAppear
is called and how it is typically used; otherwise, you don’t know what this method is for. Moreover, because of your code’s object-oriented structure, multiple methods in this class (and perhaps others) will be managing the same instance properties; your program’s logic is divided between methods and even across different classes.
Your challenges are compounded by surprises involving the order of events. Beginners (and even experienced programmers) are often mystified when their program doesn’t work as expected, because they have wrong expectations about when an entry point will be called, or what the state of an instance will be when it is called. To make matters worse, the order of events isn’t even reliable; my apps often break when I upgrade them from one iOS version to the next, because the new version of iOS is sending certain events in a different order from the old version.
How will you find your way through the swamp of events that a Cocoa program consists of? There’s no easy solution, but here’s some simple advice:
Comment every method, quite heavily if need be, saying what that method does and under what circumstances you expect it to be called — especially if it is an entry point, where it is Cocoa itself that will do the calling.
Instrument your code heavily during development with caveman debugging (see Chapter 9). As you test your code, keep an eye on the console output and check whether the messages make sense. You may be surprised at what you discover. If things don’t work as expected, add breakpoints and run the app again so you can see the order of execution and watch the variables and properties as they change.
Perhaps the most common kind of mistake in writing a Cocoa app is not that there’s a bug in your code itself, but that you’ve put the code in the wrong place. Your code isn’t running, or it’s running at the wrong time, or the pieces are running in the wrong order. I see questions about this sort of thing all the time on the various online user forums (these are all actual examples that appeared over the course of just two days):
There’s a delay between the time when my view appears and when my button takes on its correct title.
That’s because you put the code that sets the button’s title in viewDidAppear
. That’s too late; your code needs to run earlier, perhaps in viewWillAppear
.
My subviews are positioned in code and they’re turning out all wrong.
That’s because you put the code that positions your subviews in viewDidLoad
. That’s too early; your code needs to run later, when your view’s dimensions have been determined.
My view is rotating even though my view controller’s supportedInterfaceOrientations
says not to.
That’s because you implemented supportedInterfaceOrientations
in the wrong class. Only the topmost view controller in the view controller hierarchy is consulted through this property.
I set up an action connection for Value Changed on a text field, but my code isn’t being called when the user edits.
That’s because you connected the wrong control event; a text field emits Editing Changed, not Value Changed.
Your code is executed in response to some event; but your code in turn may trigger a new event or chain of events. Sometimes this causes bad things to happen: there might be a crash, or Cocoa might appear not to have done what you said to do. To solve this problem, perhaps you just need to step outside Cocoa’s own chain of events for a moment and wait for everything to settle down before proceeding.
The technique for doing this is called delayed performance. You tell Cocoa to do something, not right this moment, but in a little while, when things have settled down. Perhaps you need only a very short delay, just to let Cocoa finish doing something, such as laying out the interface. Technically, you’re allowing the current run loop to finish, completing and unwinding the entire current call stack, before proceeding further with your own code.
When you program Cocoa, you’re likely to be using delayed performance a lot more than you might expect. With experience, you’ll develop a kind of sixth sense for when delayed performance might be the solution to your difficulties.
The main way to get delayed performance is by calling DispatchQueue’s after(when:execute:)
method. It takes a function stating what should happen after the specified time has passed. Here’s a utility function that encapsulates the call:
func delay(_ delay:Double, closure:@escaping () -> ()) { let when = DispatchTime.now() + delay DispatchQueue.main.asyncAfter(deadline: when, execute: closure) }
That utility function is so important that I routinely paste it at the top level of the AppDelegate class file in every app I write.
To use it, I call delay
with a delay time (usually a very small number of seconds such as 0.1
) and an anonymous function saying what to do after the delay. Note that what you propose to do in this anonymous function will be done later on; you’re deliberately breaking out of your own code’s line-by-line sequence of execution. So a delayed performance call will typically be the last call in its own surrounding function, and cannot return any value.
In this example from one of my own apps, the user has tapped a row of a table, and my code responds by creating and showing a new view controller:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let t = TracksViewController( mediaItemCollection: self.albums[indexPath.row]) self.navigationController?.pushViewController(t, animated: true) }
Unfortunately, the innocent-looking call to my TracksViewController initializer init(mediaItemCollection:)
can take a moment to complete, so the app comes to a stop with the table row highlighted — very briefly, but just long enough to startle the user. To cover this delay with a sense of activity, I’ve rigged my UITableViewCell subclass to show a spinning activity indicator when it’s selected:
override func setSelected(_ selected: Bool, animated: Bool) { if selected { self.activityIndicator.startAnimating() } else { self.activityIndicator.stopAnimating() } super.setSelected(selected, animated: animated) }
But there’s a problem: the spinning activity indicator never appears and never spins. The reason is that the events are stumbling over one another here. UITableViewCell’s setSelected(_:animated:)
isn’t called until the UITableView delegate method tableView(_:didSelectRowAt:)
has finished. But the delay we’re trying to paper over is during tableView(_:didSelectRowAt:)
; the whole problem is that it doesn’t finish fast enough.
Delayed performance to the rescue! I’ll rewrite tableView(_:didSelectRowAt:)
so that it finishes immediately — triggering setSelected(_:animated:)
immediately and causing the activity indicator to appear and spin — and I’ll use delayed performance to call init(mediaItemCollection:)
later on, when the interface has ironed itself out:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { delay(0.1) { let t = TracksViewController( mediaItemCollection: self.albums[indexPath.row]) self.navigationController?.pushViewController(t, animated: true) } }