Chapter 10. Cocoa Classes

When you program iOS through Foundation and UIKit, you’re programming Cocoa. The Cocoa API is written mostly in Objective-C, and Cocoa itself consists mostly of Objective-C classes, derived from the root class, NSObject.

This chapter introduces Cocoa’s class structure and explains how Cocoa is conceptually organized, in terms of its underlying Objective-C features, along with a survey of some of the most commonly encountered Cocoa utility classes. The chapter then discusses Objective-C instance properties and Cocoa key–value coding, and concludes with a description of the Cocoa root class and its features, which are inherited by all Cocoa classes.

Subclassing

Cocoa supplies a large repertory of objects that already know how to behave in certain desirable ways. A UIButton knows how to draw itself and how to respond when the user taps it; a UITextField knows how to display editable text, how to summon the keyboard, and how to accept keyboard input. When the default behavior or appearance of an object supplied by Cocoa isn’t quite what you’re after, you’ll want to customize it.

But that does not necessarily mean you need to subclass! In fact, subclassing is one of the rarer ways in which your code will relate to Cocoa. Most built-in Cocoa Touch classes will never need subclassing (and some, in their documentation, downright forbid it).

Instead, Cocoa classes are often heavily endowed with methods that you can call and properties that you can set precisely in order to customize an instance, and these will be your first resort. Always study the documentation for a Cocoa class to see whether instances can already be made to do what you want. The UIButton class documentation shows that you can set a button’s title, title color, internal image, background image, and many other features and behaviors, without subclassing.

In addition, many built-in classes use delegation (Chapter 11) as the preferred way of letting you customize their behavior. You wouldn’t subclass UITextField just in order to respond in some special way when the user types text, because the delegate mechanism and the UITextFieldDelegate protocol provide ways to do that.

Nevertheless, sometimes setting properties and calling methods and using delegation won’t suffice to customize an instance the way you want to. In such cases, a Cocoa class may provide methods that are called by the runtime at key moments in the life of the instance, allowing you to customize that class’s behavior by subclassing and overriding. In fact, certain Cocoa Touch classes are subclassed routinely, constituting the exception that proves the rule.

A case in point is UIViewController. Every Cocoa app uses view controllers, but a plain vanilla UIViewController, not subclassed, is very rare. An iOS app without at least one UIViewController subclass would be practically impossible. Your code is likely to consist primarily of UIViewController subclasses that you have written. Subclassing is how you inject your functionality into a view controller. With UIKit, most of your app’s functionality will likely live in subclassed view controllers.

Another case in point is UIView. Cocoa Touch is full of built-in UIView subclasses that behave and draw themselves as needed (UIButton, UITextField, and so on), and you will rarely need to subclass any of them. On the other hand, you might create your own UIView subclass, whose job would be to draw itself in some completely new way.

You don’t actually draw a UIView; rather, when a UIView needs drawing, its draw(_:) method is called so that the view can draw itself. So the way to draw a custom UIView is to subclass UIView and implement draw(_:) in the subclass. As the documentation says, “Subclasses that … draw their view’s content should override this method and implement their drawing code there.” The documentation is saying that you need to subclass UIView in order to draw custom content.

Suppose we want our window to contain a horizontal line. There is no horizontal line interface widget built into Cocoa, so we’ll just have to roll our own — a UIView that draws itself as a horizontal line. Let’s try it:

  1. In our Empty Window example project, choose File → New → File and specify iOS → Source → Cocoa Touch Class, and in particular a subclass of UIView. Call the class MyHorizLine. Xcode creates MyHorizLine.swift. Make sure it’s part of the app target.

  2. In MyHorizLine.swift, replace the contents of the class declaration with this (without further explanation):

    required init?(coder: NSCoder) {
        super.init(coder:coder)
        self.backgroundColor = .clear
    }
    override func draw(_ rect: CGRect) {
        let c = UIGraphicsGetCurrentContext()!
        c.move(to:CGPoint(x: 0, y: 0))
        c.addLine(to:CGPoint(x: self.bounds.size.width, y: 0))
        c.strokePath()
    }
  3. Edit the storyboard. Find UIView in the Library (it is called simply “View”) and drag it into the View object in the canvas. You may resize it to be less tall.

  4. Select the UIView that you just dragged into the canvas and use the Identity inspector to change its class to MyHorizLine.

Build and run the app in the Simulator. You’ll see a horizontal line corresponding to the location of the top of the MyHorizLine instance in the nib. Our view has drawn itself as a horizontal line, because we subclassed it to do so.

In that example, we started with a bare UIView that had no drawing functionality of its own. But you might also be able to subclass a built-in UIView subclass to modify the way it already draws itself. The UILabel documentation shows that two methods are present for exactly this purpose. Both drawText(in:) and textRect(forBounds:limitedToNumberOfLines:) explicitly tell us: “This method should only be overridden by subclasses that want to [modify how the label is drawn].” The implication is that these are methods that will be called for us, automatically, by Cocoa, as a label draws itself; we can subclass UILabel and implement these methods in our subclass to modify how a particular label draws itself.

In one of my own apps, I subclass UILabel and override drawText(in:) to make a label that draws its own rectangular border and has its content inset somewhat from that border. As the documentation tells us: “In your overridden method, you can configure the current context further and then invoke super to do the actual drawing [of the text].” Let’s try it:

  1. In the Empty Window project, make a new class file, a UILabel subclass; call the class MyBoundedLabel.

  2. In MyBoundedLabel.swift, insert this code into the body of the class declaration:

    override func drawText(in rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()!
        context.stroke(self.bounds.insetBy(dx: 1.0, dy: 1.0))
        super.drawText(in: rect.insetBy(dx: 5.0, dy: 5.0))
    }
  3. Edit the storyboard, add a UILabel to the interface, and change its class in the Identity inspector to MyBoundedLabel.

Build and run the app. As you can see, the rectangle is drawn and the label’s text is inset within it.

Categories and Extensions

A category is an Objective-C language feature that allows code to reach right into an existing class and inject additional methods. This is comparable to a Swift extension (Chapter 4), so I’ll start by reminding you how extensions are used, and then I’ll describe how Cocoa uses categories.

Tip

Objective-C categories have names, and you may see references to these names in the headers, the documentation, and so forth. However, the names are effectively meaningless, so don’t worry about them.

How Swift Uses Extensions

In the Swift standard library header, many native object type declarations consist of an initial declaration followed by a series of extensions. After declaring the generic struct Array<Element>, the header proceeds to declare some dozen extensions on the Array struct. Some of these add protocol adoptions; all of them add declarations of properties or methods. These extensions are not, of themselves, functionally significant. The header could have declared the Array struct with all of those properties and methods within the body of a single declaration. Instead, it breaks things up into multiple extensions as a way of clumping related functionality together, organizing this object type’s members so as to make them easier for human readers to understand.

In the Swift Core Graphics header, on the other hand, extensions are functionally significant — and just about everything is an extension — because Swift is adapting types that are already defined elsewhere. It adapts Swift numeric types for use with Core Graphics and CGFloat, and it adapts C structs such as CGPoint and CGRect for use as Swift object types.

How You Use Extensions

For the sake of object-oriented encapsulation, you will often want to write a function that you inject, as a method, into an existing object type. To do so, you’ll write an extension. Subclassing merely to add a method or two is heavy-handed — and besides, it often wouldn’t help you do what you need to do.

Suppose you wanted to add a method to Cocoa’s UIView class. You could subclass UIView and declare your method, but then it would be present only in your UIView subclass and in subclasses of that subclass. It would not be present in UIButton, UILabel, and all the other built-in UIView subclasses, because they are subclasses of UIView, not of your subclass. An extension solves the problem beautifully: you inject your method into UIView, and it is inherited by all built-in UIView subclasses as well.

For more fine-grained injection of functionality, you can use protocol extensions. Suppose I want UIButton and UIBarButtonItem — which is not a UIView, but does have button-like behavior — to share a certain method. I can declare a protocol with a method, implement that method in a protocol extension, and then use extensions to make UIButton and UIBarButtonItem adopt that protocol and acquire that method:

protocol ButtonLike {
    func behaveInButtonLikeWay()
}
extension ButtonLike {
    func behaveInButtonLikeWay() {
        // ...
    }
}
extension UIButton : ButtonLike {}
extension UIBarButtonItem : ButtonLike {}

How Cocoa Uses Categories

Cocoa uses categories as an organizational tool much as Swift uses extensions. The declaration of a class will often be divided by functionality into multiple categories; these can even appear in separate header files.

A good example is NSString. NSString is defined as part of the Foundation framework, and its basic methods are declared in NSString.h. Here we find that NSString itself, aside from its initializers, has just two members, length and character(at:), because these are regarded as the minimum functionality that a string needs in order to be a string.

Additional NSString methods — those that create a string, deal with a string’s encoding, split a string, search in a string, and so on — are clumped into categories. These are shown in the Swift translation of the header as extensions. After the declaration for the NSString class itself, we find this in the Swift translation of the header:

extension NSString {
    func substring(from: Int) -> String
    func substring(to: Int) -> String
    // ...
}

That is actually Swift’s translation of this Objective-C code:

@interface NSString (NSStringExtensionMethods)
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;
// ...
@end

That notation — the keyword @interface, followed by a class name, followed by another name in parentheses — is an Objective-C category.

Moreover, although the declarations for some of Cocoa’s NSString categories appear in this same file, NSString.h, many of them appear elsewhere:

  • A string may serve as a file pathname, so we also find a category on NSString in NSPathUtilities.h, where methods and properties such as pathComponents are declared for splitting a pathname string into its constituents and the like.

  • In NSURL.h, which is devoted primarily to declaring the NSURL class (and its categories), there is also another NSString category, declaring methods for dealing with percent encoding in a URL string, such as addingPercentEncoding(withAllowedCharacters:).

  • Off in a completely different framework (UIKit), NSStringDrawing.h adds two further NSString categories, with methods like draw(at:withAttributes:) having to do with drawing a string in a graphics context.

This organization means that the NSString methods are not gathered in a single header file. In general, fortunately, this won’t matter to you as a programmer, because an NSString is an NSString, no matter how it acquires its methods.

Protocols

Objective-C has protocols, and these are generally comparable to and compatible with Swift protocols (see Chapter 4). Since classes are the only Objective-C object type, all Objective-C protocols are seen by Swift as class protocols. Conversely, Swift protocols marked as @objc are implicitly class protocols and can be seen by Objective-C. Cocoa makes extensive use of protocols.

A case in point is how Cocoa objects are copied. Some objects can be copied; some can’t. This has nothing to do with an object’s class heritage. Yet we would like a uniform method to which any object that can be copied will respond. So Cocoa defines a protocol named NSCopying, which declares just one required method, copyWithZone:. Here’s how NSObject.h declares the NSCopying protocol:

@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end

That’s translated into Swift:

protocol NSCopying {
    func copy(with zone: NSZone? = nil) -> Any
}

The NSCopying protocol declaration in NSObject.h, however, is not a statement that NSObject itself conforms to NSCopying. Indeed, NSObject does not conform to NSCopying! This doesn’t compile:

let obj = NSObject().copy(with:nil) // compile error

But this does compile, because NSString does conform to NSCopying:

let s = ("hello" as NSString).copy(with: nil)

Far and away the most pervasive use of protocols in Cocoa is in connection with the delegation pattern. I’ll discuss this pattern in detail in Chapter 11, but you can readily see an example in our handy Empty Window project: the AppDelegate class provided by the project template is declared like this:

class AppDelegate: UIResponder, UIApplicationDelegate { // ...

AppDelegate’s chief purpose on earth is to serve as the shared application’s delegate. The shared application object is a UIApplication, and UIApplication’s delegate property is declared like this (I’ll explain the unsafe modifier in Chapter 12):

unowned(unsafe) var delegate: UIApplicationDelegate?

The UIApplicationDelegate type is a protocol. UIApplication is saying: “I don’t care what class my delegate belongs to, but whatever it is, it should conform to the UIApplicationDelegate protocol.” Such conformance constitutes a promise that the delegate will implement instance methods declared by the protocol, such as application(_:didFinishLaunchingWithOptions:). The AppDelegate class officially announces its role by explicitly adopting the UIApplicationDelegate protocol.

A Cocoa protocol has its own documentation page. When the UIApplication class documentation tells you that the delegate property is typed as UIApplicationDelegate, it’s implicitly telling you that if you want to know what messages the application’s delegate might receive, you need to look in the UIApplicationDelegate protocol documentation. application(_:didFinishLaunchingWithOptions:) isn’t mentioned anywhere in the UIApplication class documentation page; it’s in the UIApplicationDelegate protocol documentation page.

Optional Members

Objective-C protocols, and Swift protocols marked as @objc, can have optional members (see “Optional Protocol Members”). The UIApplicationDelegate protocol method application(_:didFinishLaunchingWithOptions:) is a case in point; it’s optional. But how, in practice, is an optional member feasible? We know that if a message is sent to an object and the object can’t handle that message, an exception is raised and your app will likely crash. How does Objective-C prevent that from happening?

The answer is that Objective-C is both dynamic and introspective. Objective-C can ask an object whether it can deal with a message without actually sending it that message. The key method here is NSObject’s responds(to:) method (Objective-C respondsToSelector:), which takes a selector parameter (see Chapter 2) and returns a Bool. Thus it is possible to send a message to an object conditionally — that is, only if it would be safe to do so.

Demonstrating responds(to:) in Swift is generally a little tricky, because Swift, with its strict type checking, doesn’t want to let us send an object a message to which it might not respond. In this artificial example, I start by defining, at top level, a class that derives from NSObject, because otherwise we can’t send responds(to:) to it, along with an @objc protocol to declare the message that I want to send conditionally:

class MyClass : NSObject {
}
@objc protocol Dummy {
    func woohoo()
}

Now I can say this:

let mc = MyClass()
if mc.responds(to: #selector(Dummy.woohoo)) {
    (mc as AnyObject).woohoo()
}

Note the cast of mc to AnyObject. This causes Swift to abandon its strict type checking (see “Suppressing type checking”); we can now send this object any message that Swift knows about, provided it is susceptible to Objective-C introspection — that’s why I marked my protocol declaration as @objc to start with. As you know, Swift provides a shorthand for sending a message conditionally: append a question mark to the name of the message. I could have written this:

let mc = MyClass()
(mc as AnyObject).woohoo?()

Behind the scenes, those two approaches are exactly the same; the latter is syntactic sugar for the former. In response to the question mark, Swift is calling responds(to:) for us, and will refrain from sending woohoo to this object if it doesn’t respond to this selector.

That explains how optional protocol members work. It is no coincidence that Swift treats optional protocol members like AnyObject members. Here’s the example I gave in Chapter 4:

@objc protocol Flier {
    @objc optional var song : String {get}
    @objc optional func sing()
}

When you call sing?() on an object typed as Flier, responds(to:) is called behind the scenes to determine whether this call is safe. That is also why optional protocol members work only on @objc protocols and classes derived from NSObject: Swift is relying here on a purely Objective-C feature.

You wouldn’t want to send a message optionally, or call responds(to:) explicitly, before sending just any old message, because it isn’t generally necessary except with optional methods, and it slows things down a little. But Cocoa does in fact call responds(to:) on your objects as a matter of course. To see that this is true, implement responds(to:) on the AppDelegate class in our Empty Window project and instrument it with logging:

override func responds(to aSelector: Selector) -> Bool {
    print(aSelector)
    return super.responds(to:aSelector)
}

The output on my machine, as the Empty Window app launches, includes the following:

application:handleOpenURL:
application:openURL:sourceApplication:annotation:
application:openURL:options:
applicationDidReceiveMemoryWarning:
applicationWillTerminate:
applicationSignificantTimeChange:
application:willChangeStatusBarOrientation:duration:
application:didChangeStatusBarOrientation:
application:willChangeStatusBarFrame:
application:didChangeStatusBarFrame:
application:deviceAccelerated:
application:deviceChangedOrientation:
applicationDidBecomeActive:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationWillSuspend:
application:didResumeWithOptions:
application:shouldSaveApplicationState:
application:supportedInterfaceOrientationsForWindow:
application:defaultWhitePointAdaptivityStyleForWindow:
application:configurationForConnectingSceneSession:options:
application:didDiscardSceneSessions:
application:performFetchWithCompletionHandler:
application:didReceiveRemoteNotification:fetchCompletionHandler:
application:didFinishLaunchingSuspendedWithOptions:
application:willFinishLaunchingWithOptions:
application:didFinishLaunchingWithOptions:

That’s Cocoa, checking to see which optional UIApplicationDelegate protocol methods are actually implemented by our AppDelegate instance. Cocoa checks all the optional protocol methods once, when it first meets the object in question, and presumably stores the results; the app is slowed a tiny bit by this one-time initial bombardment of responds(to:) calls, but now Cocoa knows all the answers and won’t have to perform any of these same checks on the same object later. The entire pattern of a delegate with optional delegate members depends upon this technique.

Informal Protocols

You may occasionally see, online or in the Cocoa documentation, a reference to an informal protocol. An informal protocol isn’t really a protocol at all; it’s just an Objective-C trick for providing the compiler with a knowledge of a method name so that it will allow a message to be sent without complaining.

There are two complementary ways to implement an informal protocol. One is to define a category on NSObject; this makes any object eligible to receive the messages listed in the category. The other is to define a protocol to which no class formally conforms; instead, messages listed in the protocol are sent only to objects typed as id (AnyObject), suppressing any objections from the compiler.

These techniques were widespread in Cocoa before Objective-C protocols could declare methods as optional; now they are largely unnecessary, and are also mildly dangerous. Nowadays, very few informal protocols remain, but they do exist. NSKeyValueCoding (discussed later in this chapter) is an informal protocol; you may see the term NSKeyValueCoding in the documentation and elsewhere, but there isn’t actually any such type — it’s a category on NSObject.

Some Foundation Classes

The Foundation classes of Cocoa provide basic data types and utilities that will form the basis of your communication with Cocoa. In this section, I’ll survey those that you’ll probably want to be aware of initially. For more information, start with Apple’s list of the Foundation classes in the Foundation framework documentation page.

In many situations, you can use Foundation classes implicitly by way of Swift classes. That’s because of Swift’s ability to bridge between its own classes and those of Foundation. String is bridged to NSString (Chapter 3), and Array is bridged to NSArray (Chapter 4), so a String and an NSString can be cast to one another, and an Array and an NSArray can be cast to one another. But in fact you’ll rarely need to cast, because wherever the Objective-C API expects you to pass an NSString or an NSArray, these will be typed in the Swift translation of that API as a String or an Array. And when you use String or Array in the presence of Foundation, many NSString and NSArray properties and methods spring to life.

The Swift Foundation “overlay” puts a native Swift interface in front of many other Foundation types. The Swift interface is distinguished by dropping the “NS” prefix that marks Foundation class names; Objective-C NSData is accessed through Swift Data, and Objective-C NSDate is accessed through Swift Date — though you can still use NSData and NSDate directly if you really want to. The Swift and Objective-C types are bridged to one another, and the API shows the Swift type, so casting and passing works as you would expect. The Swift types provide many conveniences that the Objective-C types do not: they may declare adoption of appropriate Swift protocols such as Equatable, Hashable, and Comparable, and, in some cases, they may be value types (structs) where the Objective-C types are reference types (classes).

Tip

There are two kinds of bridging to be distinguished here. String and Array are native Swift types, with an independent existence. Date and Data, on the other hand, aren’t native Swift types; they are façades for NSDate and NSData, meaning that you cannot use them except in the presence of Cocoa’s Foundation framework.

NSRange

NSRange is a C struct (see Appendix A). Its components are integers, location and length. An NSRange whose location is 1 starts at the second element of something (because element counting is always zero-based), and if its length is 2 it designates this element and the next.

A Swift Range and a Cocoa NSRange are constructed very differently from one another. A Swift Range is defined by two endpoints. A Cocoa NSRange is defined by a starting point and a length. Nevertheless, Swift goes to some lengths (in the Foundation overlay) to help you work with an NSRange:

  • NSRange is endowed with Range-like members such as lowerBound, upperBound, and contains(_:).

  • You can coerce a Swift Range whose Bound type (the type of its endpoints) is Int (or any other integer type) to an NSRange.

  • You can coerce from an NSRange to a Swift Range — resulting in an Optional wrapping a Range, for reasons I’ll explain in a moment.

For example:

// Range to NSRange
let r = 2..<4
let nsr = NSRange(r) // (2,2), an NSRange
// NSRange to Range
let nsr2 = NSRange(location: 2, length: 2)
let r2 = Range(nsr2) // Optional wrapping Range 2..<4

But what about strings? A Swift String’s range Bound type is not Int; it is String.Index. Meanwhile, on the Cocoa side, an NSString still uses an NSRange whose components are integers. Not only is there a type mismatch, there’s also a value mismatch, because (as I explained in Chapter 3) a String is indexed on its characters, meaning its graphemes, but an NSString is indexed on its Unicode codepoints.

Sometimes, Swift will solve the problem by crossing the bridge for you in both directions; here’s an example I’ve already used:

let s = "hello"
let range = s.range(of:"ell") // Optional wrapping Range 1..<4

The range(of:) method in that code is actually a Cocoa method. Swift has cast the String s to an NSString for us, called a Foundation method that returns an NSRange, and coerced the NSRange to a Swift Range (wrapped in an Optional), adjusting its value as needed, entirely behind the scenes.

On other occasions, you will want to perform that coercion explicitly. For this purpose, Range has an initializer init(_:in:), taking an NSRange and the String to which the resulting range is to apply:

let range = NSRange(location: 1, length: 3)
let r = Range(range, in:"hello") // Optional wrapping 1..<4 of String.Index

And NSRange has the converse initializer init(_:in:), taking a Range of String.Index and the String to which it applies:

let s = "hello"
let range = NSRange(s.range(of:"ell")!, in: s) // (1,3), an NSRange

Sometimes, however, you actively want to operate in the Cocoa Foundation world, without bridging back to Swift. You can do that by casting:

let s = "Hello"
let r = (s as NSString).range(of: "ell")
let mas = NSMutableAttributedString(string:s)
mas.addAttributes([.foregroundColor:UIColor.red], range: r)

In that code, we cast a String to an NSString so as to be able to call NSString’s range(of:) and get an NSRange, because that is what NSMutableAttributedString’s addAttributes(_:range:) wants as its second parameter. It would be wasteful to call range(of:) on a Swift String, which crosses into the Foundation world, gets the range, and brings it back to the Swift world, only to convert it back to an NSRange again.

NSNotFound

NSNotFound is a constant integer indicating that some requested element was not found. The true numeric value of NSNotFound is of no concern; you always compare against NSNotFound itself to learn whether a result is meaningful. If you ask for the index of a certain object in an NSArray and the object isn’t present, the result is NSNotFound:

let arr = ["hey"] as NSArray
let ix = arr.index(of:"ho")
if ix == NSNotFound {
    print("it wasn't found")
}

Why does Cocoa resort to an integer value with a special meaning in this way? Because it has to. The result could not be 0 to indicate the absence of the object, because 0 would indicate the first element of the array. Nor could it be -1, because an NSArray index value is always positive. Nor could it be nil, because Objective-C can’t return nil when an integer is expected (and even if it could, it would be seen as another way of saying 0). Contrast Swift, whose Array firstIndex(of:) method returns an Int wrapped in an Optional, so that it can return nil to indicate that the target object wasn’t found.

If a search returns an NSRange and the thing sought is not present, the location component of the result will be NSNotFound. This means that, when you turn an NSRange into a Swift Range, the NSRange’s location might be NSNotFound, and Swift needs to be able to express that as a nil Range. That’s why the initializers for coercing an NSRange to a Range are failable. It is also why, when you call NSString’s range(of:) method on a Swift String, the result is an Optional:

let s = "hello"
let r = s.range(of:"ha") // nil; an Optional wrapping a Swift Range

NSString and Friends

NSString is the Cocoa object version of a string. NSString and Swift String are bridged to one another, and you will often move between them without thinking, passing a Swift String to Cocoa, calling Cocoa NSString methods on a Swift String, and so forth:

let s = "hello"
let s2 = s.capitalized

In that code, s is a Swift String and s2 is a Swift String, but the capitalized property actually belongs to Cocoa. In the course of that code, a Swift String has been bridged to NSString and passed to Cocoa, which has processed it to get the capitalized string; the capitalized string is an NSString, but it has been bridged back to a Swift String. In all likelihood, you are not conscious of the bridging; capitalized feels like a native String property, but it isn’t — as you can readily prove by trying to use it in an environment where Foundation is not imported.

In some cases, Swift may fail to cross the bridge implicitly for you, and you will need to cast explicitly. If s is a Swift string, you can’t call appendingPathExtension on it directly:

let s = "MyFile"
let s2 = s.appendingPathExtension("txt") // compile error

You have to cast explicitly to NSString:

let s2 = (s as NSString).appendingPathExtension("txt")

Similarly, to use NSString’s substring(to:), you must cast the String to an NSString beforehand:

let s2 = (s as NSString).substring(to:4)

In this situation, however, we can stay entirely within the Swift world by calling prefix, which is a native Swift method, not a Foundation method; delightfully, it takes an Int, not a String.Index:

let s2 = s.prefix(4)

However, those two calls are not equivalent: they can give different answers! The reason is that String and NSString have fundamentally different notions of what constitutes an element of a string (see “The String–NSString Element Mismatch”). A String must resolve its elements into characters, which means that it must walk the string, coalescing any combining codepoints; an NSString behaves as if it were an array of UTF-16 codepoints. On the Swift side, each increment in a String.Index corresponds to a true character, but access by index or range requires walking the string; on the Cocoa side, access by index or range is extremely fast, but might not correspond to character boundaries. (See the “Characters and Grapheme Clusters” chapter of Apple’s String Programming Guide in the documentation archive.)

Another important difference between a Swift String and a Cocoa NSString is that an NSString is immutable. This means that, with NSString, you can do things such as obtain a new string based on the first — as capitalized and substring(to:) do — but you can’t change the string in place. To do that, you need another class, a subclass of NSString, NSMutableString. Swift String isn’t bridged to NSMutableString, so you can’t get from String to NSMutableString merely by casting. To obtain an NSMutableString, you’ll have to make one. The simplest way is with NSMutableString’s initializer init(string:), which expects an NSString — meaning that you can pass a Swift String. Coming back the other way, you can cast all the way from NSMutableString to a Swift String in one move, because an NSMutableString is an NSString:

let s = "hello"
let ms = NSMutableString(string:s)
ms.deleteCharacters(in:NSRange(location: ms.length-1, length:1))
let s2 = (ms as String) + "ion" // now s2 is a Swift String, "hellion"

As I said in Chapter 3, native Swift String methods are thin on the ground. All the real string-processing power lives over on the Cocoa side of the bridge. So you’re going to be crossing that bridge a lot! And this will not be only for the power of the NSString and NSMutableString classes. Many other useful classes are associated with them. Suppose you want to search a string for some substring; all the best ways come from Cocoa:

  • An NSString can be searched using various range methods, with numerous options such as ignoring diacriticals, ignoring case, and searching backward.

  • Perhaps you don’t know exactly what you’re looking for: you need to describe it structurally. A Scanner (Objective-C NSScanner) lets you walk through a string looking for pieces that fit certain criteria; for example, with Scanner (and CharacterSet, Objective-C NSCharacterSet) you can skip past everything in a string that precedes a number and then extract the number.

  • By specifying the .regularExpression search option, you can search using a regular expression. Regular expressions are also supported as a separate class, NSRegularExpression, which in turn uses NSTextCheckingResult to describe match results.

  • More sophisticated automated textual analysis is supported by some additional classes, such as NSDataDetector, an NSRegularExpression subclass that efficiently finds certain types of string expression such as a URL or a phone number.

In this example, our goal is to replace all occurrences of the word “hell” with the word “heaven.” We don’t want to replace mere occurrences of the substring “hell” — the word “hello” should be left intact. Clearly our search needs some intelligence as to what constitutes a word boundary. That sounds like a job for a regular expression. Swift doesn’t have regular expressions, so the work has to be done by Cocoa:

var s = "hello world, go to hell"
let r = try! NSRegularExpression(
    pattern: #"\bhell\b"#,
    options: .caseInsensitive)
s = r.stringByReplacingMatches(
    in: s,
    range: NSRange(s.startIndex..., in:s),
    withTemplate: "heaven")
// s is "hello world, go to heaven"

NSString also has convenience utilities for working with a file path string, and is often used in conjunction with URL (Objective-C NSURL), which is another Foundation type worth looking into, along with its companion types, URLComponents (Objective-C NSURLComponents) and URLQueryItem (Objective-C NSURLQueryItem). In addition, NSString — like some other classes discussed in this section — provides methods for writing out to a file’s contents or reading in a file’s contents; the file can be specified either as a string file path or as a URL.

An NSString carries no font and size information. Interface objects that display strings (such as UILabel) have a font property that is a UIFont; but this determines the single font and size in which the string will display. If you want styled text — where different runs of text have different style attributes (size, font, color, and so forth) — you need to use NSAttributedString, along with its supporting classes NSMutableAttributedString, NSParagraphStyle, and NSMutableParagraphStyle. These allow you to style text and paragraphs easily in sophisticated ways. The built-in interface objects that display text can display an attributed string.

String drawing in a graphics context can be performed with methods provided through the NSStringDrawing category on NSString and on NSAttributedString.

NSDate and Friends

A Date (Objective-C NSDate) is a date and time, represented internally as a number of seconds since some reference date. Calling Date’s initializer init() — that is, saying Date() — gives you a Date object for the current date and time. Many date operations will also involve the use of DateComponents (Objective-C NSDateComponents), and conversions between Date and DateComponents require use of a Calendar (Objective-C NSCalendar). Here’s an example of constructing a date based on its calendrical values:

let greg = Calendar(identifier:.gregorian)
let comp = DateComponents(calendar: greg,
    year: 2019, month: 8, day: 10, hour: 15)
let d = comp.date // Optional wrapping Date

Similarly, DateComponents provides the correct way to do date arithmetic. Here’s how to add one month to a given date:

let d = Date() // or whatever
let comp = DateComponents(month:1)
let greg = Calendar(identifier:.gregorian)
let d2 = greg.date(byAdding: comp, to:d) // Optional wrapping Date

Because a Date is essentially a wrapper for a TimeInterval (a Double), Swift can overload the arithmetic operators so that you can do arithmetic directly on a Date:

let d = Date()
let d2 = d + 4 // 4 seconds later

You can express the range between two dates as a DateInterval (Objective-C NSDateInterval). DateIntervals can be compared, intersected, and checked for containment:

let greg = Calendar(identifier:.gregorian)
let d1 = DateComponents(calendar: greg,
    year: 2019, month: 1, day: 1, hour: 0).date!
let d2 = DateComponents(calendar: greg,
    year: 2019, month: 8, day: 10, hour: 15).date!
let di = DateInterval(start: d1, end: d2)
if di.contains(Date()) { // are we currently between those two dates?

You will also likely be concerned with dates represented as strings. If you don’t take explicit charge of a date’s string representation, it is represented by a string whose format may surprise you. If you simply print a Date, you are shown the date in the GMT timezone, which can be confusing if that isn’t where you live. A simple solution when you’re just logging to the console is to call description(with:), whose parameter is a Locale (Objective-C NSLocale) comprising the user’s current time zone, language, region format, and calendar settings:

print(d)
// 2019-08-10 22:00:00 +0000
print(d.description(with:Locale.current))
// Saturday, August 10, 2019 at 3:00:00 PM Pacific Daylight Time

For full control over date strings, especially when presenting them to the user, use DateFormatter (Objective-C NSDateFormatter), which takes a format string describing how the date string is laid out:

let df = DateFormatter()
df.dateFormat = "M/d/y"
let s = df.string(from: Date())
// 7/9/2019

DateFormatter knows how to make a date string that conforms to the user’s local conventions. In this example, we call the class method dateFormat(fromTemplate:options:locale:) with the current locale as configured on the user’s device. The template: is a string listing the date components to be used, but their order, punctuation, and language are left up to the locale:

let df = DateFormatter()
let format = DateFormatter.dateFormat(
    fromTemplate:"dMMMMyyyyhmmaz", options:0,
    locale:Locale.current)
df.dateFormat = format
let s = df.string(from:Date())

The result is that the date is shown in the user’s time zone and language, using the correct linguistic conventions. That involves a combination of region format and language, which are two separate settings:

  • On my device, the result might be “July 9, 2019, 12:34 PM PDT.”

  • If I change my device’s region to France, it might be “9 July 2019 at 12:34 pm GMT-7.”

  • If I also change my device’s language to French, and if my app is localized for French, it might be “9 juillet 2019 à 12:34 PM UTC−7.”

DateFormatter can also parse a date string into a Date — but be sure that the date format is correct. This attempt to parse a string will fail, because the date format doesn’t match the way the string is constructed:

let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "M/d/y"
let d = df.date(from: "31/7/2019") // nil; should have been "d/M/y"
Tip

Setting the Locale to "en_US_POSIX" guarantees that we will override the device’s settings. Forgetting to do this, and then wondering why parsing a string into a Date fails on some devices, is a common beginner error, particularly when you were expecting 12-hour vs. 24-hour time formatting.

NSNumber

An NSNumber is an object that wraps a numeric value. The wrapped value can be any standard Objective-C numeric type (including BOOL, the Objective-C equivalent of Swift Bool). In Swift, everything is an object — a number is a Struct instance — so it comes as a surprise to Swift users that NSNumber is needed. But an ordinary number in Objective-C is a scalar, not an object, so it cannot be used where an object is expected; and an object cannot be used where a number is expected. Thus, NSNumber solves an important problem for Objective-C, converting a number into an object and back again.

Swift does its best to shield you from having to deal directly with NSNumber. It bridges Swift numeric types to Objective-C in two different ways:

As a scalar

If Objective-C expects an ordinary number, a Swift number is bridged to an ordinary number (a scalar):

UIView.animate(withDuration: 1,
    animations: whatToAnimate, completion: whatToDoLater)

Objective-C animateWithDuration:animations:completion: takes a C double as its first parameter. The Swift numeric object that you supply as the first argument to animate(withDuration:animations:completion:) becomes a C double.

As an NSNumber

If Objective-C expects an object, a Swift numeric type is bridged to an NSNumber (including Bool, because NSNumber can wrap an Objective-C BOOL):

UserDefaults.standard.set(1, forKey:"Score")

Objective-C setObject:forKey: takes an Objective-C object as its first parameter. The Swift numeric object that you supply as the first argument to set(_:forKey:) becomes an NSNumber.

Naturally, if you need to cross the bridge explicitly, you can. You can cast a Swift number to an NSNumber:

let n = 1 as NSNumber

Coming back from Objective-C to Swift, an NSNumber (or an Any that is actually an NSNumber) can be unwrapped by casting it down to a numeric type — provided the wrapped numeric value matches the type. To illustrate, I’ll fetch the NSNumber that I created in UserDefaults by bridging a moment ago:

let n = UserDefaults.standard.value(forKey:"Score")
// n is an Optional<Any> containing an NSNumber
let i = n as! Int // legal
let d = n as! Double // legal

An NSNumber object is just a wrapper and no more. It can’t be used directly for numeric calculations; it isn’t a number. It wraps a number. One way or another, if you want a number, you have to extract it from the NSNumber.

An NSNumber subclass, NSDecimalNumber, on the other hand, can be used in calculations, thanks to a bunch of arithmetic methods:

let dec1 = 4.0 as NSDecimalNumber
let dec2 = 5.0 as NSDecimalNumber
let sum = dec1.adding(dec2) // 9.0

Underlying NSDecimalNumber is the Decimal struct (Objective-C NSDecimal); it is an NSDecimalNumber’s decimalValue. In Objective-C, NSDecimal comes with C functions that are faster than NSDecimalNumber methods. In Swift, things are even better, because the arithmetic operators are overloaded to allow you to do Decimal arithmetic; you are likely to prefer working with Decimal rather than NSDecimalNumber:

let dec1 = Decimal(4.0)
let dec2 = Decimal(5.0)
let sum = dec1 + dec2

NSValue

NSValue is NSNumber’s superclass. It is used for wrapping nonnumeric C values, such as C structs, where an object is expected. The problem being solved here is parallel to the problem solved by NSNumber: a Swift struct is an object, but a C struct is not, so a struct cannot be used in Objective-C where an object is expected, and vice versa.

Convenience methods provided through the NSValueUIGeometryExtensions category on NSValue allow easy wrapping and unwrapping of such common structs as CGPoint, CGSize, CGRect, CGAffineTransform, UIEdgeInsets, and UIOffset:

let pt = self.oldButtonCenter // a CGPoint
let val = NSValue(cgPoint:pt)

Additional categories allow easy wrapping and unwrapping of NSRange, CATransform3D, CMTime, CMTimeMapping, CMTimeRange, MKCoordinate, and MKCoordinateSpan (and you are unlikely to need to store any other kind of C value in an NSValue, but if you do need to, you can).

But you will rarely need to deal with NSValue explicitly, because Swift will wrap any of those common structs in an NSValue for you as it crosses the bridge from Swift to Objective-C. Here’s an example from my own real-life code:

let pt = CGPoint(
    x: screenbounds.midX + r * cos(rads),
    y: screenbounds.midY + r * sin(rads)
)
// apply an animation of ourself to that point
let anim = CABasicAnimation(keyPath:"position")
anim.fromValue = self.position
anim.toValue = pt

In that code, self.position and pt are both CGPoints. The CABasicAnimation properties fromValue and toValue need to be Objective-C objects (that is, class instances) so that Cocoa can obey them to perform the animation. It is therefore necessary to wrap self.position and pt as NSValue objects. But you don’t have to do that; Swift wraps those CGPoints as NSValue objects for you, Cocoa is able to interpret and obey them, and the animation works correctly.

The same thing is true of an array of common structs. Again, animation is a case in point. If you assign an array of CGPoint to a CAKeyframeAnimation’s values property, the animation will work properly, without your having to map the CGPoints to NSValues first. That’s because Swift maps them for you as the array crosses the bridge.

NSData

Data (Objective-C NSData) is a general sequence of bytes (UInt8); basically, it’s just a buffer, a chunk of memory. In Objective-C, NSData is immutable; the mutable version is its subclass NSMutableData. In Swift, however, where Data is a bridged value type imposed in front of NSData, a Data object is mutable if it was declared with var, just like any other value type. Moreover, because a Data object represents a byte sequence, Swift makes it a Collection (and therefore a Sequence), causing Swift features such as enumeration with for...in, subscripting, and append(_:) to spring to life. Thus, although you can work with NSData and NSMutableData if you want to (by casting to cross the bridge), you are much more likely to prefer Data.

In practice, Data tends to arise in two main ways:

When downloading from the internet

URLSession (Objective-C NSURLSession) supplies whatever it retrieves from the internet as Data. Transforming it from there into (let’s say) a string, specifying the correct encoding, would then be up to you.

When serializing an object

A typical use case is that you’re storing an object as a file or in user preferences (UserDefaults). You can’t store a UIColor value directly into user preferences. So if the user has made a color choice and you need to save it, you transform the UIColor into a Data object (using NSKeyedArchiver) and save that:

let ud = UserDefaults.standard
let c = UIColor.blue
let cdata = try! NSKeyedArchiver.archivedData(
    withRootObject: c, requiringSecureCoding: true)
ud.set(cdata, forKey: "myColor")

NSMeasurement and Friends

The Measurement type (Objective-C NSMeasurement) embodies the notion of a measurement by some unit (Unit, Objective-C NSUnit). A unit may be along some dimension that can be expressed in different units convertible to one another; by reducing values in different units of the same dimension to a base unit, a Measurement permits you to perform arithmetic operations and conversions.

The dimensions, which are all subclasses of the (abstract) Dimension class (Objective-C NSDimension, an NSUnit subclass), have names like UnitAngle and UnitLength (Objective-C NSUnitAngle, NSUnitLength), and have class properties vending an instance corresponding to a particular unit type; UnitAngle has class properties degrees and radians and others, UnitLength has class properties miles and kilometers, and so on.

To illustrate, I’ll add 5 miles to 6 kilometers:

let m1 = Measurement(value:5, unit: UnitLength.miles)
let m2 = Measurement(value:6, unit: UnitLength.kilometers)
let total = m1 + m2

The answer, total, is 14046.7 meters under the hood, because meters are the base unit of length. But it can be converted to any length unit:

let totalFeet = total.converted(to: .feet).value // 46084.9737532808

If your goal is to output a measurement as a user-facing string, use a MeasurementFormatter (Objective-C NSMeasurementFormatter). Its behavior is locale-dependent by default, expressing the value and the units as the user would expect:

let mf = MeasurementFormatter()
let s = mf.string(from:total) // "8.728 mi"

My code says nothing about miles, but the MeasurementFormatter outputs "8.728 mi" because my device is set to United States (region) and English (language). If my device is set to France (region) and French (language), the very same code outputs "14,047 km" — using the French decimal point notation and the French preferred unit of distance measurement.

Equality, Hashability, and Comparison

In Swift, the equality and comparison operators can be overridden for an object type that adopts Equatable and Comparable (“Operators”). But Objective-C operators are applicable only to scalars. Objective-C therefore performs comparison of object instances in a special way, and it can be useful to know about this when working with Cocoa classes.

To permit determination of whether two objects are “equal” — whatever that may mean for this object type — an Objective-C class must implement isEqual(_:), which is inherited from NSObject. Swift will help out by treating NSObject as Equatable and by permitting the use of the == operator, implicitly converting it to an isEqual(_:) call. Thus, if a class derived from NSObject implements isEqual(_:), ordinary Swift comparison will work. If an NSObject subclass doesn’t implement isEqual(_:), it inherits NSObject’s implementation, which compares the two objects for identity (like Swift’s === operator).

These two Dog objects can be compared with the == operator, even though Dog does not adopt Equatable, because they derive from NSObject. Dog doesn’t implement isEqual(_:), so == defaults to using NSObject’s identity comparison:

class Dog : NSObject {
    var name : String
    var license : Int
    init(name:String, license:Int) {
        self.name = name
        self.license = license
    }
}
let d1 = Dog(name:"Fido", license:1)
let d2 = Dog(name:"Fido", license:1)
let ok = d1 == d2 // false

If we wanted two Dogs with the same name and license to be considered equal, we’d need to implement isEqual(_:), like this:

class Dog : NSObject {
    var name : String
    var license : Int
    init(name:String, license:Int) {
        self.name = name
        self.license = license
    }
    override func isEqual(_ object: Any?) -> Bool {
        if let otherdog = object as? Dog {
            return (otherdog.name == self.name &&
                otherdog.license == self.license)
        }
        return false
    }
}
let d1 = Dog(name:"Fido", license:1)
let d2 = Dog(name:"Fido", license:1)
let ok = d1 == d2 // true

At this point, you might be saying (thinking of “Synthesized Protocol Implementations”): “But wait, why don’t you just declare Dog to adopt Equatable and get autosynthesis of equatability based on its properties?” But you can’t do that. Autosynthesis of Equatable conformance doesn’t work for classes, and in any case Dog is already Equatable by virtue of being an NSObject subclass. Besides, Equatable is about how to implement ==, whereas what we need to implement here is isEqual:.

Foundation types implement isEqual(_:) in a sensible way, so Swift equatability works as you would expect. NSNumber implements isEqual(_:) by comparing the underlying numbers; thus, you can use NSNumber where a Swift Equatable is expected, and, because a Swift number will be cast automatically to an NSNumber if needed, you can even compare an NSNumber to a Swift number:

let n1 = 1 as NSNumber
let n2 = 2 as NSNumber
let n3 = 3 as NSNumber
let ok = n2 == 2 // true
let ix = [n1,n2,n3].firstIndex(of:2) // Optional wrapping 1

By the same token, for an NSObject subclass to work properly where hashability is required — as a dictionary key or a set member, even if this is a Swift Dictionary or Set — it must conform to the NSObject notion of hashability, namely, an implementation of isEqual(_:) plus a corresponding override of the NSObject hash property, meaning that two equal objects should have equal hash values. If we wanted our Dog from the previous code to be usable in a Set, we’d need to override hash; in the past, that was tricky to do correctly, but nowadays the Hasher struct (introduced in Swift 4.2) makes it easy:

class Dog : NSObject {
    var name : String
    var license : Int
    init(name:String, license:Int) {
        self.name = name
        self.license = license
    }
    override func isEqual(_ object: Any?) -> Bool {
        if let otherdog = object as? Dog {
            return (otherdog.name == self.name &&
                otherdog.license == self.license)
        }
        return false
    }
    override var hash: Int {
        var h = Hasher()
        h.combine(self.name)
        h.combine(self.license)
        return h.finalize()
    }
}
var set = Set<Dog>()
set.insert(Dog(name:"Fido", license:1))
set.insert(Dog(name:"Fido", license:1))
print(set.count) // 1

Foundation types come with a built-in hash implementation (and the Swift overlay types are all both Equatable and Hashable as well).

In Objective-C it is also up to individual classes to supply ordered comparison methods. The standard method is compare(_:), which returns one of three cases of ComparisonResult (Objective-C NSComparisonResult):

.orderedAscending

The receiver is less than the argument.

.orderedSame

The receiver is equal to the argument.

.orderedDescending

The receiver is greater than the argument.

Swift comparison operators (< and so forth) do not magically call compare(_:) for you. You can’t compare two NSNumber values directly:

let n1 = 1 as NSNumber
let n2 = 2 as NSNumber
let ok = n1 < n2 // compile error

You will typically fall back on calling compare(_:) yourself:

let n1 = 1 as NSNumber
let n2 = 2 as NSNumber
let ok = n1.compare(n2) == .orderedAscending // true

On the other hand, a Swift Foundation overlay type can adopt Comparable, and now comparison operators do work. You can’t compare two NSDate values with <, but you can compare two Date values.

NSArray and NSMutableArray

NSArray is Objective-C’s array object type. It is fundamentally similar to Swift Array, and they are bridged to one another; but NSArray elements must be objects (classes and class instances), and they don’t have to be of a single type. For a full discussion of how to bridge back and forth between Swift Array and Objective-C NSArray, implicitly and by casting, see “Swift Array and Objective-C NSArray”.

An NSArray’s length is its count, and an element can be obtained by index number using object(at:). The index of the first element, as with a Swift Array, is zero, so the index of the last element is count minus one.

Instead of calling object(at:), you can use subscripting with an NSArray. This is not because NSArray is bridged to Swift Array, but because NSArray implements an Objective-C method, objectAtIndexedSubscript:, which is the Objective-C equivalent of a Swift subscript getter. In fact, when you examine the NSArray header file translated into Swift, that method is shown as a subscript declaration!

You can seek an object within an array with index(of:) or indexOfObjectIdentical(to:); the former’s idea of equality is to call isEqual(_:), whereas the latter uses object identity (like Swift’s ===). If the object is not found in the array, the result is NSNotFound.

Like an Objective-C NSString, an NSArray is immutable. This doesn’t mean you can’t mutate any of the objects it contains; it means that once the NSArray is formed you can’t remove an object from it, insert an object into it, or replace an object at a given index. To do those things while staying in the Objective-C world, you can derive a new array consisting of the original array plus or minus some objects, or use NSArray’s subclass, NSMutableArray.

Swift Array is not bridged to NSMutableArray; if you want an NSMutableArray, you must create it. The simplest way is with the NSMutableArray initializers, init() or init(array:). Once you have an NSMutableArray, you can call methods such as insert(_:at:) and replaceObject(at:with:). You can also assign into an NSMutableArray using subscripting. Again, this is because NSMutableArray implements a special Objective-C method, setObject:atIndexedSubscript:; Swift recognizes this as equivalent to a subscript setter.

Coming back the other way, you can cast an NSMutableArray down to a Swift array:

let marr = NSMutableArray()
marr.add(1) // an NSNumber
marr.add(2) // an NSNumber
let arr = marr as NSArray as! [Int]

Cocoa provides ways to sort an array, as well as to search or filter an array by passing a function. You might prefer to perform those kinds of operation in the Swift Array world, but it can be useful to know how to do them the Cocoa way:

let pep = ["Manny", "Moe", "Jack"] as NSArray
let ems = pep.objects(
    at: pep.indexesOfObjects { (obj, idx, stop) -> Bool in
        return (obj as! NSString).range(
            of: "m", options:.caseInsensitive
            ).location == 0
    }
) // ["Manny", "Moe"]

NSDictionary and NSMutableDictionary

NSDictionary is Objective-C’s dictionary object type. It is fundamentally similar to Swift Dictionary, and they are bridged to one another. But NSDictionary keys and values must be objects (classes and class instances), and they don’t have to be of a single type; the keys must conform to NSCopying and must be hashable. See “Swift Dictionary and Objective-C NSDictionary” for a full discussion of how to bridge back and forth between Swift Dictionary and Objective-C NSDictionary, including casting.

An NSDictionary is immutable; its mutable subclass is NSMutableDictionary. Swift Dictionary is not bridged to NSMutableDictionary; you can most easily make an NSMutableDictionary with an initializer, init() or init(dictionary:), and you can cast an NSMutableDictionary down to a Swift Dictionary type.

The keys of an NSDictionary are distinct (using isEqual(_:) for comparison). If you add a key–value pair to an NSMutableDictionary, then if that key is not already present, the pair is simply added, but if the key is already present, then the corresponding value is replaced. This is parallel to the behavior of Swift Dictionary.

The fundamental use of an NSDictionary is to request an entry’s value by key (using object(forKey:)); if no such key exists, the result is nil. In Objective-C, nil is not an object and cannot be a value in an NSDictionary, so the meaning of this response is unambiguous. Swift handles this by treating the result of object(forKey:) as an Optional wrapping an Any.

Subscripting is possible on an NSDictionary or an NSMutableDictionary, for similar reasons to an NSArray or an NSMutableArray. NSDictionary implements objectForKeyedSubscript:, and Swift understands this as equivalent to a subscript getter. In addition, NSMutableDictionary implements setObject:forKeyedSubscript:, and Swift understands this as equivalent to a subscript setter.

Like a Swift Dictionary, an NSDictionary is unordered. You can get from an NSDictionary a list of keys (allKeys), a list of values (allValues), or a list of keys sorted by value. You can also walk through the key–value pairs, and you can even filter an NSDictionary by a test against its values.

NSSet and Friends

An NSSet is an unordered collection of distinct objects. Swift Set is bridged to NSSet, and the Swift Foundation overlay even allows you to initialize an NSSet from a Swift array literal. But NSSet elements must be objects (classes and class instances), and they don’t have to be of a single type. For details, see “Swift Set and Objective-C NSSet”.

“Distinct” for an NSSet means that no two objects in a set can return true when they are compared using isEqual(_:). Learning whether an object is present in a set is much more efficient than seeking it in an array (because a set’s elements are hashable), and you can ask whether one set is a subset of, or intersects, another set. You can walk through (enumerate) a set with the for...in construct, though the order is of course undefined. You can filter a set, as you can an NSArray. Indeed, much of what you can do with a set is parallel to what you can do with an array, except that you can’t do anything with a set that involves the notion of ordering.

To transcend that restriction, you can use an ordered set. An ordered set (NSOrderedSet) is very like an array, and the methods for working with it are similar to the methods for working with an array — you can even fetch an element by subscripting (because it implements objectAtIndexedSubscript:). But an ordered set’s elements must be distinct. An ordered set provides many of the advantages of sets: as with an NSSet, learning whether an object is present in an ordered set is much more efficient than for an array, and you can readily take the union, intersection, or difference with another set. Since the distinctness restriction will often prove no restriction at all (because the elements were going to be distinct anyway), it can be worthwhile to use NSOrderedSet instead of NSArray where possible.

An NSSet is immutable. You can derive one NSSet from another by adding or removing elements, or you can use its subclass, NSMutableSet. Similarly, NSOrderedSet has its mutable counterpart, NSMutableOrderedSet (which you can insert into by subscripting, because it implements setObject:atIndexedSubscript:). There is no penalty for adding to a set an object that the set already contains; nothing is added (and so the distinctness rule is enforced), but there’s no error.

NSCountedSet, a subclass of NSMutableSet, is a mutable unordered collection of objects that are not necessarily distinct (this concept is often referred to as a bag). It is implemented as a set plus a count of how many times each element has been added.

NSMutableSet, NSCountedSet, NSOrderedSet, and NSMutableOrderedSet are easily formed from a set or an array using an initializer. Coming back the other way, you can cast an NSMutableSet or NSCountedSet down to a Swift Set. Because of their special behaviors, however, you are much more likely to leave an NSCountedSet or NSOrderedSet in its Objective-C form for as long as you’re working with it.

Warning

Classes are reference types, so they can be referred to from multiple places and can be mutated in place. Mutating an object while it is inside a Set will effectively break the Set (lookup will cease to operate correctly).

NSIndexSet

IndexSet (Objective-C NSIndexSet) represents a collection of unique whole numbers; its purpose is to express element numbers of an ordered collection, such as an array. For instance, to retrieve multiple elements simultaneously from an NSArray, you specify the desired indexes as an IndexSet. It is also used with other things that are array-like; for example, you pass an IndexSet to a UITableView to indicate what sections to insert or delete.

NSIndexSet is immutable; it has a mutable subclass, NSMutableIndexSet. But IndexSet is a value type, so it is mutable if the declaration uses var. And, as with other Swift types imposed in front of Foundation types, IndexSet gets to do all sorts of convenient Swift magic. Comparison and arithmetic operators work directly with IndexSet values. Even more important, an IndexSet acts like a Set: it adopts the SetAlgebra protocol, and methods like contains(_:) and intersection(_:) spring to life. You probably won’t need NSMutableIndexSet at all.

To take a specific example, let’s say you want to speak of the elements at indexes 1, 2, 3, 4, 8, 9, and 10 of an array. IndexSet expresses this notion in some compact implementation that can be readily queried. The actual implementation is opaque, but you can imagine that this IndexSet might consist of two Ranges, 1...4 and 8...10, and IndexSet’s methods actually invite you to think of it as a Set of Ranges:

let arr = ["zero", "one", "two", "three", "four", "five",
    "six", "seven", "eight", "nine", "ten"]
var ixs = IndexSet()
ixs.insert(integersIn: Range(1...4))
ixs.insert(integersIn: Range(8...10))
let arr2 = (arr as NSArray).objects(at:ixs)
// ["one", "two", "three", "four", "eight", "nine", "ten"]

To walk through (enumerate) the index values specified by an IndexSet, you can use for...in; alternatively, you can walk through an IndexSet’s indexes or ranges by calling various enumerate methods that let you pass a function returning a Bool.

NSNull

The NSNull class does nothing but supply a pointer to a singleton object, NSNull(). This singleton object is used to stand for nil in situations where an actual Objective-C object is required and nil is not permitted. You can’t use nil as the value of an element of an Objective-C collection (such as NSArray, NSDictionary, or NSSet), so you’d use NSNull() instead.

NSNull() makes it possible for a Swift Array of Optional to be handed to Objective-C. The Swift Array might contain nil, which is illegal in Objective-C. But Swift will bridge the Array of Optional for you, as it crosses into Objective-C, by substituting NSNull() for any nil elements. And, coming back the other way, Swift will perform the inverse operation when you cast an NSArray down to an Array of Optional, substituting nil for any NSNull() elements.

You can test an object for equality against NSNull() using the ordinary equality operator (==), because it falls back on NSObject’s isEqual(_:), which is identity comparison. This is a singleton instance, and therefore identity comparison works.

Immutable and Mutable

Cocoa Foundation has a pattern of class pairs where the superclass is immutable and the subclass is mutable; I’ve given many examples already, such as NSString and NSMutableString, or NSArray and NSMutableArray. This is similar to the Swift distinction between a constant (let) and a true variable (var). An NSArray being immutable means that you can’t append or insert into this array, or replace or delete an element of this array; but if its elements are reference types — and of course, for an NSArray, they are reference types — you can mutate an element in place. That’s just like the behavior of a Swift Array referred to with let.

The reason why Cocoa needs these immutable/mutable pairs is to prevent unauthorized mutation. An NSString object, say, is an ordinary class instance — a reference type. If NSString were mutable, an NSString property of a class could be mutated by some other object, behind this class’s back. To prevent that from happening, a class will work internally and temporarily with a mutable instance, but then store and vend to other classes an immutable instance, protecting the value from being changed by anyone else. Swift doesn’t face the same issue, because its fundamental built-in object types such as String, Array, and Dictionary are structs, and therefore are value types, which cannot be mutated in place; they can be changed only by being replaced, and that is something that can be guarded against, or detected through a setter observer. NSString isn’t a value type, but as far as mutability is concerned, it displays value semantics (“Value Types and Reference Types”).

The documentation may not make it completely obvious that the mutable classes obey and, if appropriate, override the methods of their immutable superclasses. Dozens of NSMutableArray methods are not listed on NSMutableArray’s class documentation page, because they are inherited from NSArray. And when such methods are inherited by the mutable subclass, they may be overridden to fit the mutable subclass. NSArray’s init(array:) generates an immutable array, but NSMutableArray’s init(array:) — which isn’t even listed on the NSMutableArray documentation page, because it is inherited from NSArray — generates a mutable array.

That fact also answers the question of how to make an immutable array mutable, and vice versa. This single method, init(array:), can transform an array between immutable and mutable in either direction. You can also use copy (produces an immutable copy) and mutableCopy (produces a mutable copy), both inherited from NSObject; but these are not as convenient because they yield an Any which must then be cast.

Warning

These immutable/mutable class pairs are all implemented as class clusters, which means that Cocoa uses a secret class, different from the documented class you work with. You may discover this by peeking under the hood; an NSString, for instance, might be characterized as an NSTaggedPointerString or an NSCFString. You should not spend any time wondering about this secret class. It’s a mere implementation detail, and is subject to change without notice; you should never have looked at it in the first place.

Property Lists

A property list is a string (XML) representation of data. The Foundation classes NSString, NSData, NSArray, and NSDictionary are the only Cocoa classes that can be expressed directly in a property list. Moreover, an NSArray or NSDictionary can be expressed in a property list only if its elements are instances of those classes, along with NSDate and NSNumber. Those are the property list types.

(That is why, as I mentioned earlier, you must convert a UIColor into a Data object in order to store it in user defaults; the user defaults storage is a property list, and UIColor is not a property list type. But Data is a property list type, because it is bridged to NSData.)

The primary use of a property list is as a way of serializing a value — saving it to disk in a form from which it can be reconstructed. NSArray and NSDictionary provide write methods that generate property list files; conversely, they also provide initializers that create an NSArray object or an NSDictionary object based on the property list contents of a given file. (The NSString and NSData write methods just write the data out as a file directly, not as a property list.)

Here I’ll create an array of strings and write it out to disk as a property list file:

let arr = ["Manny", "Moe", "Jack"]
let fm = FileManager.default
let temp = fm.temporaryDirectory
let f = temp.appendingPathComponent("pep.plist")
try! (arr as NSArray).write(to: f)

The result is a file that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <string>Manny</string>
    <string>Moe</string>
    <string>Jack</string>
</array>
</plist>

When you reconstruct an NSArray or NSDictionary object from a property list file in this way, the collections, string objects, and data objects in the collection are all immutable. If you want them to be mutable, or if you want to convert an instance of one of the other property list classes to a property list, you’ll use the PropertyListSerialization class (Objective-C NSPropertyListSerialization; see the Property List Programming Guide in the documentation archive).

Codable

Property lists are a Cocoa Objective-C construct, useful for serializing objects. But in Swift you can serialize an object without crossing the bridge into the Objective-C world, provided it adopts the Codable protocol. In effect, every native Swift type and every Foundation overlay type does adopt the Codable protocol! This means, among other things, that enums and structs can easily be serialized.

There are three main use cases, involving three pairs of classes to serialize the object and extract it again later; what you’re encoding to and decoding from is a Data object:

Property lists

Use PropertyListEncoder and PropertyListDecoder.

JSON

Use JSONEncoder and JSONDecoder.

NSCoder

Use NSKeyedArchiver and NSKeyedUnarchiver.

To illustrate, let’s rewrite the previous example, serializing an array of strings to a property list, without casting it to an NSArray. This works because both Swift Array and Swift String adopt Codable; indeed, thanks to conditional conformance (Chapter 4), an Array is Codable only just in case its element type is Codable:

let arr = ["Manny", "Moe", "Jack"]
let fm = FileManager.default
let temp = fm.temporaryDirectory
let f = temp.appendingPathComponent("pep.plist")
let penc = PropertyListEncoder()
penc.outputFormat = .xml
let d = try! penc.encode(arr)
try! d.write(to: f)

The resulting file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <string>Manny</string>
    <string>Moe</string>
    <string>Jack</string>
</array>
</plist>

That example doesn’t do anything that we couldn’t have done with NSArray. But now consider, for instance, an index set. You can’t write an NSIndexSet directly into a property list using Objective-C, because NSIndexSet is not a property list type. But the Swift Foundation overlay type, IndexSet, is Codable:

let penc = PropertyListEncoder()
penc.outputFormat = .xml
let d = try! penc.encode(IndexSet([1,2,3]))

And here’s the result:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>indexes</key>
    <array>
        <dict>
            <key>length</key>
            <integer>3</integer>
            <key>location</key>
            <integer>1</integer>
        </dict>
    </array>
</dict>
</plist>

Notice how cleverly Swift has encoded this object. You can’t put an IndexSet into a property list — but this property list doesn’t contain any IndexSet! It is composed entirely of legal property list types — a dictionary containing an array of dictionaries whose values are numbers. And Swift can extract the encoded object from the property list:

let ix = try! PropertyListDecoder().decode(IndexSet.self, from: d)
// [1,2,3]

Your own custom types can adopt Codable and make themselves encodable in the same way. In fact, in the simplest case, adopting Codable is all you have to do! If the type’s properties are themselves Codable, the right thing will happen automatically. The Codable protocol has two required methods, but we don’t have to implement them because default implementations are synthesized (see “Synthesized Protocol Implementations”) — though we could implement them if we wanted to customize the details of encoding and decoding.

Here’s a simple Person struct:

struct Person : Codable {
    let firstName : String
    let lastName : String
}

Person adopts Codable, so with no further effort we can turn a Person into a property list:

let p = Person(firstName: "Matt", lastName: "Neuburg")
let penc = PropertyListEncoder()
penc.outputFormat = .xml
let d = try! penc.encode(p)

Here’s our encoded Person:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>firstName</key>
    <string>Matt</string>
    <key>lastName</key>
    <string>Neuburg</string>
</dict>
</plist>

Observe that this would work just as well for, say, an array of Person, or a dictionary with Person values, or any Codable struct with a Person property.

UserDefaults is a property list, so an object that isn’t a property list type must be archived to a Data object in order to store it in UserDefaults. A PropertyListEncoder creates a Data object, so we can use it to store a Person object in UserDefaults:

let ud = UserDefaults.standard
let p = Person(firstName: "Matt", lastName: "Neuburg")
let pdata = try! PropertyListEncoder().encode(p)
ud.set(pdata, forKey: "person")

Encoding as JSON is similar to encoding as a property list:

let p = Person(firstName: "Matt", lastName: "Neuburg")
let jenc = JSONEncoder()
jenc.outputFormatting = .prettyPrinted
let d = try! jenc.encode(p)
print(String(data:d, encoding:.utf8)!)
/*
{
  "firstName" : "Matt",
  "lastName" : "Neuburg"
}
*/

The final use case is encoding or decoding through an NSCoder. There are various situations where Cocoa lends you an NSCoder object and invites you to put some data into it or pull some data out of it. The NSCoder in question will be either an NSKeyedArchiver, when you’re encoding, or an NSKeyedUnarchiver, when you’re decoding. These subclasses, respectively, provide methods encodeEncodable(_:forKey:), which takes a Codable object, and decodeDecodable(_:forKey:), which produces a Codable object. Thus, your Codable adopters can pass into and out of an archive by way of NSCoder.

As I mentioned earlier, your Codable adopter can take more control of the encoding and decoding process. You can map between your object’s property names and the encoded key names by adding a CodingKeys enum, and you can provide an explicit implementation of the encode(to:) and decode(from:) methods instead of letting them be synthesized for you. For more information, consult the help document “Encoding and Decoding Custom Types.”

Accessors, Properties, and Key–Value Coding

An Objective-C instance variable is structurally similar to a Swift instance property: it’s a variable that accompanies each instance of a class, with a lifetime and value associated with that particular instance. An Objective-C instance variable, however, is usually private, in the sense that instances of other classes can’t see it (and Swift can’t see it). If an instance variable is to be made public, an Objective-C class will typically implement accessor methods: a getter method and (if this instance variable is to be publicly writable) a setter method. This is such a common thing to do that there are naming conventions:

The getter method

A getter should have the same name as the instance variable (without an initial underscore if the instance variable has one). If the instance variable is named myVar (or _myVar), the getter method should be named myVar.

The setter method

A setter method’s name should start with set, followed by a capitalized version of the instance variable’s name (without an initial underscore if the instance variable has one). The setter should take one parameter — the new value to be assigned to the instance variable. If the instance variable is named myVar (or _myVar), the setter should be named setMyVar:.

This pattern — a getter method, possibly accompanied by an appropriately named setter method — is so common that the Objective-C language provides a shorthand: a class can declare a property, using the keyword @property and a name. Here’s a line from the UIView class declaration in Objective-C (ignore the material in the parentheses):

@property(nonatomic) CGRect frame;

This declaration constitutes a promise that there is a getter accessor method frame returning a CGRect, along with a setter accessor method setFrame: that takes a CGRect parameter.

When Objective-C formally declares a @property in this way, Swift sees it as a Swift property. UIView’s frame property declaration is translated directly into a Swift declaration of an instance property frame of type CGRect:

var frame: CGRect

An Objective-C property name, however, is mere syntactic sugar; Objective-C objects do not really “have” properties. When you apparently set a UIView’s frame property, you are actually calling its setFrame: setter method, and when you apparently get a UIView’s frame property, you are actually calling its frame getter method. In Objective-C, use of the property is optional; Objective-C code can, and often does, call the setFrame: and frame methods directly. But you can’t do that in Swift! If an Objective-C class has a formal @property declaration, the accessor methods are hidden from Swift.

An Objective-C property declaration can include the word readonly in the parentheses. This indicates that there is a getter but no setter:

@property(nonatomic,readonly,strong) CALayer *layer;

(Ignore the other material in the parentheses.) Swift will reflect this restriction with {get} after the declaration, as if this were a computed read-only property; the compiler will not permit you to assign to such a property:

var layer: CALayer { get }

Although Objective-C accessor methods may literally be ways of accessing an invisible instance variable, they don’t have to be. When you set a UIView’s frame property and the setFrame: accessor method is called, you have no way of knowing what that method is really doing: it might be setting an instance variable called frame or _frame, but who knows? In this sense, accessors and properties are a façade, hiding the underlying implementation. This is similar to how, within Swift, you can set a variable without knowing or caring whether it is a stored variable or a computed variable (and, if it is a computed variable, without knowing what its getter and setter functions really do).

Swift Accessors

Just as Objective-C properties are actually a shorthand for accessor methods, so Objective-C treats Swift properties as a shorthand for accessor methods — even though no such methods are formally present. If you, in Swift, declare that a class has a property prop, Objective-C can call a prop method to get its value or a setProp: method to set its value, even though you have not implemented such methods. Those calls are routed to your property through implicit accessor methods.

In Swift, you should not write explicit accessor methods for a property; the compiler will stop you if you attempt to do so. If you need to implement an accessor method explicitly and formally, use a computed property. Here I’ll add to my UIViewController subclass a computed color property with a getter and a setter:

class ViewController: UIViewController {
    @objc var color : UIColor {
        get {
            print("someone called the getter")
            return .red
        }
        set {
            print("someone called the setter")
        }
    }
}

Objective-C code can now call explicitly the implicit setColor: and color accessor methods — and when it does, the computed property’s setter and getter methods are in fact called:

ViewController* vc = [ViewController new];
[vc setColor:[UIColor redColor]]; // "someone called the setter"
UIColor* c = [vc color]; // "someone called the getter"

This proves that, in Objective-C’s mind, you have provided setColor: and color accessor methods.

You can even change the Objective-C names of accessor methods! To do so, follow the @objc attribute with the Objective-C name in parentheses. You can add it to a computed property’s setter and getter methods, or you can add it to a property itself:

@objc(hue) var color : UIColor?

Objective-C code can now call hue and setHue: accessor methods directly.

If, in speaking to Objective-C, you need to pass a selector for an accessor method, precede the contents of the #selector expression with getter: or setter:. For example, #selector(setter:color) is "setHue:" if we have modified our color property’s Objective-C name with @objc(hue) (or "setColor:" if we have not).

If all you want to do is add functionality to the setter, use a setter observer. To add functionality to the Objective-C setFrame: method in your UIView subclass, you can override the frame property and write a didSet observer:

class MyView: UIView {
    override var frame : CGRect {
        didSet {
            print("the frame setter was called: \(super.frame)")
        }
    }
}

Key–Value Coding

Cocoa can dynamically call an accessor method, or access an instance variable, based on a string name specified at runtime, through a mechanism called key–value coding (KVC). The string name is the key; what is passed or returned is the value. The basis for key–value coding is the NSKeyValueCoding protocol, an informal protocol; it is actually a category injected into NSObject. A Swift class, to be susceptible to key–value coding, must therefore be derived from NSObject.

The fundamental Cocoa key–value coding methods are setValue(_:forKey:) and value(forKey:). When one of these methods is called on an object, the object is introspected. In simplified terms, first the appropriate accessor method is sought; if it doesn’t exist, the instance variable is accessed directly. The value can be an Objective-C object of any type, so its Objective-C type is id; therefore it is typed in Swift as Any. Whatever you pass into setValue(_:forKey:) will cross the bridge from Swift to Objective-C. Coming back the other way, when calling value(forKey:), you’ll receive an Optional wrapping an Any; you’ll want to cast this down safely to its expected type.

A class is key–value coding compliant (or KVC compliant) on a given key if it provides the accessor methods, or possesses the instance variable, required for access through that key. An attempt to access a key for which a class is not key–value coding compliant will likely cause a crash at runtime. It is useful to be familiar with the message you’ll get when such a crash occurs, so let’s cause it deliberately:

let obj = NSObject()
obj.setValue("hello", forKey:"keyName") // crash

The console says: “This class is not key value coding-compliant for the key keyName.” The last word in that error message is the key string that caused the trouble.

What would it take for that method call not to crash? The class of the object to which it is sent would need to have a setKeyName: setter method, or a keyName or _keyName instance variable. In Swift, as I demonstrated in the previous section, an instance property implies the existence of accessor methods. So we can use Cocoa key–value coding on an instance of any NSObject subclass that has a declared property, provided the key string is the string name of that property. Let’s try it! Here is such a class:

class Dog : NSObject {
    @objc var name : String = ""
}

And here’s our test:

let d = Dog()
d.setValue("Fido", forKey:"name") // no crash!
print(d.name) // "Fido" - it worked!

How Outlets Work

Key–value coding lies at the heart of how outlet connections work (Chapter 7). Suppose that you have a class Dog with an @IBOutlet property master typed as Person, and you’ve drawn a "master" outlet from a Dog object in the nib to a Person object in the nib. The name of that outlet, "master", is just a string. When the nib loads, the outlet name "master" is translated through key–value coding to the accessor method name setMaster:, and your Dog instance’s setMaster: implicit accessor method is called with the Person instance as its parameter, setting the value of your Dog instance’s master property to the Person instance (Figure 7-8).

If something goes wrong with the match between the outlet name in the nib and the name of the property in the class, then at runtime, when the nib loads, Cocoa’s attempt to use key–value coding to set a value in your object based on the name of the outlet will fail, and your app will crash — with an error message complaining (you guessed it) that the class is not key–value coding compliant for the key. (The key here is the outlet name.) A likely way for this to happen is that you formed the outlet correctly but then later changed the name of (or deleted) the property in the class; see “Misconfigured Outlets”.

Cocoa Key Paths

A Cocoa key path allows you to chain keys in a single expression. If an object is key–value coding compliant for a certain key, and if the value of that key is itself an object that is key–value coding compliant for another key, you can chain those keys by calling value(forKeyPath:) and setValue(_:forKeyPath:).

A key path string looks like a succession of key names joined using dot-notation. valueForKeyPath("key1.key2") effectively calls value(forKey:) on the message receiver, with "key1" as the key, and then takes the object returned from that call and calls value(forKey:) on that object, with "key2" as the key.

To illustrate, here are two classes that form a chain of properties — a DogOwner that has a dog property which is a Dog that has a name property:

class Dog : NSObject {
    @objc var name : String = ""
}
class DogOwner : NSObject {
    @objc var dog : Dog?
}

Now let’s configure an actual chain:

let owner = DogOwner()
let dog = Dog()
dog.name = "Fido"
owner.dog = dog

Now we can use key–value coding with a key path to work our way down the chain:

if let name = owner.value(forKeyPath:"dog.name") as? String {

We retrieve the value as an Optional wrapping an Any which is actually a string, and we cast down safely to retrieve the real value, "Fido".

Uses of Key–Value Coding

Cocoa key–value coding allows you, in effect, to decide at runtime, based on a string, what accessor to call. In the simplest case, you’re using a string to access a dynamically specified property. That’s useful in Objective-C code; but such unfettered introspective dynamism is contrary to the spirit of Swift, and in translating my own Objective-C code into Swift I have generally found myself accomplishing the same ends by other means.

Nevertheless, key–value coding remains useful in programming iOS, especially because a number of built-in Cocoa classes permit you to use it in special ways:

  • If you send value(forKey:) to an NSArray, it sends value(forKey:) to each of its elements and returns a new array consisting of the results, an elegant shorthand. NSSet behaves similarly.

  • NSDictionary implements value(forKey:) as an alternative to object(forKey:) (useful particularly if you have an NSArray of dictionaries). Similarly, NSMutableDictionary treats setValue(_:forKey:) as a synonym for set(_:forKey:), except that the first parameter can be nil, in which case removeObject(forKey:) is called.

  • NSSortDescriptor sorts an NSArray by sending value(forKey:) to each of its elements. This makes it easy to sort an array of dictionaries on the value of a particular dictionary key, or an array of objects on the value of a particular property.

  • NSManagedObject, used in conjunction with Core Data, is guaranteed to be key–value coding compliant for attributes you’ve configured in the entity model. It’s common to access those attributes with value(forKey:) and setValue(_:forKey:).

  • CALayer and CAAnimation permit you to use key–value coding to define and retrieve the values for arbitrary keys, as if they were a kind of dictionary; they are, in effect, key–value coding compliant for every key. This is extremely helpful for attaching extra information to an instance of one of these classes.

Also, many Cocoa APIs use key–value coding indirectly: you supply a key string, and Cocoa applies it for you. For example, a CABasicAnimation must be initialized with a keyPath string parameter:

let anim = CABasicAnimation(keyPath:"transform")

What you’re really doing here is telling the animation that you’re going to want to animate a CALayer’s transform property. Similarly, the AV Foundation framework, used in conjunction with videos, takes string keys to specify properties whose value you’re going to be interested in:

let url = Bundle.main.url(forResource:"ElMirage", withExtension:"mp4")!
let asset = AVURLAsset(url:url)
asset.loadValuesAsynchronously(forKeys:["tracks"]) {

That works because an AVURLAsset has a tracks property.

KeyPath Notation

Using key–value coding can be dangerous, because you risk using a key for which the target object is not key–value coding compliant. But Swift can often provide some measure of safety. Instead of forming the key string yourself, you ask the Swift compiler to form it for you. To do so, use #keyPath notation.

#keyPath notation is similar to #selector syntax (Chapter 2): you’re asking the Swift compiler to form the key string for you, and it will refuse if it can’t confirm that the key in question is legal. We crashed by saying this:

let obj = NSObject()
obj.setValue("hello", forKey:"keyName") // crash

But if we had used #keyPath notation, our code wouldn’t have crashed — because it wouldn’t even have compiled:

let obj = NSObject()
obj.setValue("howdy", forKey: #keyPath(NSObject.keyName)) // compile error

Now return to our Dog with a name property:

class Dog : NSObject {
    @objc var name : String = ""
}

This compiles, because Swift knows that Dog has a name property:

let d = Dog()
d.setValue("Fido", forKey:#keyPath(Dog.name))

But that code will not compile if Dog is not an NSObject subclass, or if its name property is not exposed to Objective-C. Thus the Swift compiler can often help to save us from ourselves. Some of my earlier examples can be rewritten more safely using #keyPath notation, and in real life, this is how I would write them:

let anim = CABasicAnimation(keyPath: #keyPath(CALayer.transform))

And:

let url = Bundle.main.url(forResource:"ElMirage", withExtension:"mp4")!
let asset = AVURLAsset(url:url)
let tracks = #keyPath(AVURLAsset.tracks)
asset.loadValuesAsynchronously(forKeys:[tracks]) {

But the compiler can’t always save us from ourselves. There are situations where you can’t form a string indirectly using #keyPath notation, and you’ll just have to hand Cocoa a string that you form yourself. For example (self is a CALayer):

self.rotationLayer.setValue(.pi/4.0, forKeyPath:"transform.rotation.y")

You can’t rewrite that using #keyPath(CALayer.transform.rotation.y), because the compiler won’t let you form that key path. The problem is that the compiler is unaware of any rotation property of a CALayer transform — because there is no such property. That sort of key path works by a special dispensation within Cocoa: CATransform3D (the type of a CALayer’s transform) is key–value coding compliant for a repertoire of keys and key paths that don’t correspond to any actual properties, and Swift has no way of knowing that.

You may be wondering how all of this relates to Swift’s own key path mechanism (Chapter 5). If a Dog has a name property, you can say:

let d = Dog()
d[keyPath:\.name] = "Rover"

That is a completely different mechanism! You’ll surely prefer to use the Swift mechanism where possible. It provides complete safety, along with type information; a Swift KeyPath object is strongly typed, because it is a generic, specified to the type of the corresponding property. But that won’t help you when you’re talking to Cocoa. Objective-C key–value coding uses string keys, and a Swift KeyPath object cannot be magically transformed into a string key.

Tip

Cocoa key–value coding is a powerful technology with many ramifications beyond what I’ve described here; see Apple’s Key-Value Coding Programming Guide in the documentation archive for full information.

The Secret Life of NSObject

Every Objective-C class inherits from NSObject, which is constructed in a rather elaborate way:

  • It defines some native class methods and instance methods having mostly to do with the basics of instantiation and of method sending and resolution.

  • It adopts the NSObject protocol. This protocol declares instance methods having mostly to do with memory management, the relationship between an instance and its class, and introspection. Because all the NSObject protocol methods are required, the NSObject class implements them all. In Swift, the NSObject protocol is called NSObjectProtocol, to avoid name clash.

  • It implements convenience methods related to the NSCopying, NSMutableCopying, and NSCoding protocols, without formally adopting those protocols. NSObject intentionally doesn’t adopt these protocols because this would cause all other classes to adopt them, which would be wrong. But thanks to this architecture, if a class does adopt one of these protocols, you can call the corresponding convenience method. For instance, NSObject implements the copy instance method, so you can call copy on any instance, but you’ll crash unless the instance’s class also adopts the NSCopying protocol and implements copy(with:).

  • A large number of methods are injected into NSObject by more than two dozen categories on NSObject, scattered among various header files. For example, awakeFromNib (see Chapter 7) comes from the UINibLoadingAdditions category on NSObject, declared in UINibLoading.h.

  • A class object is an object. Therefore all Objective-C classes, which are objects of type Class, inherit from NSObject. Therefore, any instance method of NSObject can be called on a class object as a class method! For example, responds(to:) is defined as an instance method by the NSObject protocol, but it can (therefore) be treated also as a class method and sent to a class object.

Taken as a whole, the NSObject methods may be considered under the following rough classification:

Creation, destruction, and memory management

Methods for creating an instance, such as alloc and copy, along with methods for learning when something is happening in the lifetime of an object, such as initialize and dealloc, plus methods that manage memory.

Class relationships

Methods for learning an object’s class and inheritance, such as superclass, isKind(of:), and isMember(of:).

Object introspection and comparison

Methods for asking what would happen if an object were sent a certain message, such as responds(to:), for representing an object as a string (description), and for comparing objects (isEqual(_:)).

Message response

Methods for meddling with what does happen when an object is sent a certain message, such as doesNotRecognizeSelector(_:). If you’re curious, see the Objective-C Runtime Programming Guide in the documentation archive.

Message sending

Methods for sending a message dynamically. For example, perform(_:) takes a selector as parameter, and sending it to an object tells that object to perform that selector. This might seem identical to just sending that message to that object, but what if you don’t know what message to send until runtime? Moreover, variants on perform allow you to send a message on a specified thread, or to send a message after a certain amount of time has passed (perform(_:with:afterDelay:) and similar).