Safely Using Objective-C From Swift

A common source of bugs in languages that have reference types is the unintentional sharing and modifying of references by different parts of a program. Often this creates side effects in one part of a program caused by innocent changes to values elsewhere – definitely something you want to avoid.

“Defensive copying” is a technique that prevents these side effects by working on copies of references. This prevents the kind of sharing and mutation that causes problems. Defensive copy is a best practice in many languages like Objective-C and Java.

Swift makes and explicit distinction between reference and value types. Classes are references, while structs, enums, and tuples are value types. When a new value is assigned to a struct Swift creates a new instance with its own copy of the data. This copying takes defensive copy and automates it for value types.

Apple talks defensive copy and Swift in depth both in the Swift documentation and the WWDC session Building Better Apps with Value Types in Swift from a few years ago.

But that is only for value types. Chances are that much of your app isn't pure Swift – it's Swift talking to Objective-C. When you are using Apple frameworks like UIKit, CoreData, UserNotifications and many others your Swift code is talking to Objective-C behind the scenes.

When you are working with Objective-C objects it is still important to use defensive copy.

For example, this method is returning a UNNotificationContent object:

func makeContent() -> UNNotificationContent {
let content: UNMutableNotificationContent = UNMutableNotificationContent()
content.title = "Title"
return content
}

It's actually returning a subclass, UNMutableNotificationContent. That subclass is mutable and could very well pose a problem. The instance could be passed into the user notification center wrapped in a request. It may wait quite a while to be delivered – while your app then modifies that object. It may be intentional, it may be a mistake, either way it is asking for trouble.

The right way to write that method would return an immutable copy of that instance:

func makeContent() -> UNNotificationContent {
let content: UNMutableNotificationContent = UNMutableNotificationContent()
content.title = "Title"
return (content.copy() as! UNNotificationContent)
}

Let's break down that last line of code.
UNNotificationContent and UNMutableNotificationContent support NSCopying, which allows us to call copy() on it. copy() will return an UNNotificationContent with the same data as the original object – giving us an object that can't be changed later. In Swift the return type of copy() is interpreted as Any, so it must be cast to the correct type.

In your code look for places where you might be doing something like this – particularly where you are later passing objects to code you do not control. You might be surprised how many bugs doing this can fix.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.