Little Swift Tricks: Boxing Multiple Types

Here’s a little Swift language trick I sometimes use — more often than you might suppose, actually.

As I’m sure you know, Swift is very strict about the types of its objects. You have to declare clearly what type a reference is, and from then on, you have to stick to that. That’s one of Swift’s great strengths; thanks to strict typing, writing correct code in Swift is usually pretty easy, because the compiler stops you from making a mistake about what type something is. Each thing has just its one particular type, and no other.

But sometimes you want to say that an object can be either of two (or more) particular types. How can you do that? That’s what I’m going to talk about here.

But first, some historical background.

The Bad Old Days

Maybe you’re not as old as I am, in computer years, so perhaps you don’t remember Objective-C. But back when I was first writing iOS apps with Cocoa Touch, the language of choice was Objective-C.

Objective-C is a dynamic language. Unlike Swift, it doesn’t have strict typing; you can declare that a variable has a certain type, but just as often, you’ll probably say that your variable is an id, meaning simply: “Some object.” And Cocoa itself is quite likely to hand you an object typed as an id; knowing its real type is then up to you. You can cast an id down to its real type, but frequently you won’t bother, because you can call any method on an id, regardless of whether the underlying object implements that method!

Sometimes, this vagueness as to what kind of thing a reference refers to can get you into trouble. If you do call a method on an id to which the underlying object doesn’t know how to respond, the compiler will happily let you do it, but you’ll probably crash when the app runs.

On the other hand, vagueness has its advantages. An Objective-C collection, such as an array (NSArray), is typed by default as having id elements; this means that an array can happily consist of different kinds of object. And being able to say that a method takes any sort of object as its parameter can be mighty convenient.

There Can Be Only Two

Here’s a case in point. In one of my apps, I use the ImageIO framework to convert an image to a smaller “thumbnail” of the same image. To get started, you have to ask the ImageIO framework to convert the image to an “image source.” There are actually two functions for doing that, depending on how the image is provided to start with:

  • CGImageSourceCreateWithURL: We’re starting with a file URL containing the image data.
  • CGImageSourceCreateWithData: We’re starting with the image data in memory.

So I wanted to write a method that would operate the same way on the image, regardless of whether it was provided as a file URL (NSURL) or as data in memory (NSData). How could I say, in declaring this method, that it was okay for its parameter to be either an NSURL or an NSData? Well, I really couldn’t; but I could declare that this method would accept any object as its parameter, and worry later about what that object really was.

So here’s my Objective-C method declaration:

-(void) newGameWithImage:(id)imageSource {

As you can see, I’ve typed imageSource as simply an id, meaning, “I don’t know or care what kind of object this is.” But of course, I do care. So in the method implementation, I look to see what kind of object imageSource is, and call the corresponding ImageIO function accordingly:

CGImageSourceRef src = NULL;
if ([imageSource isKindOfClass:[NSURL class]])
    src = CGImageSourceCreateWithURL((__bridge CFURLRef)imageSource, nil);
else if ([imageSource isKindOfClass:[NSData class]])
    src = CGImageSourceCreateWithData((__bridge CFDataRef)imageSource, nil);
else
    return; // should never happen!

Ignoring the CFTypeRef memory management (ah, those were the good old days), you can see what the strategy is here. One way or another, we’re going to end up with a CGImageSourceRef, so I start by declaring that as an empty reference. Then I test the type of imageSource. If it’s an NSURL, I call CGImageSourceCreateWithURL; if it’s an NSData, I call CGImageSourceCreateWithData. All very nice and neat.

Of course, there’s nothing to prevent some other kind of object from being handed to us as the imageSource parameter. So now we’ve got a new problem: what to do if imageSource is some other kind of object. My solution is simply to bail out, which makes a certain sense.

You can see from this example that Objective-C is both pleasant and dangerous. On the one hand, the original problem is simple to solve. This object might be an NSURL or an NSData, so we just wave away all typing and call it an id. That’s easy, but it’s also lazy, not to mention potentially dangerous.

The problem is that all the onus is then upon the callee (our method) to deal with whatever kind of parameter it receives. Instead of letting only an NSURL or an NSData in the door, we are letting anything in the door. We are leaving it up to the caller to know — in some magical way, since there is no information about this in the method declaration! — that it should pass us either an NSURL or an NSData and nothing else. And then we just have to hope that we are called correctly, because we can’t proceed otherwise.

Enter Swift

When Swift came along, I translated the code for this app, like all my existing apps, into Swift. I wanted to get the job done quickly, so I worked in a pretty simple line-by-line way. Therefore, my Swift translation of this method was word-for-word and type-for-type. This was back in the very earliest days of Swift, when the Swift equivalent of id was AnyObject:

func newGameWithImage(imageSource: AnyObject) {

The Swift Foundation overlay types (such as URL and Data) hadn’t been invented yet, so we were still using Objective-C types:

let src : CGImageSource = {
    switch imageSource {
    case let url as NSURL:
        return CGImageSourceCreateWithURL(url, nil)
    case let data as NSData:
        return CGImageSourceCreateWithData(data, nil)
    default:
        fatalError("image source was neither url nor data")
    }
}()

Almost the same as the Objective-C original, isn’t it? To be sure, the Swift code is a little neater than the Objective-C original. The use of a Swift define-and-call assignment containing a switch is definitely an improvement, as is the deliberate fatalError crash if we get something that isn’t an NSURL or an NSData. Nevertheless, basically this is exactly the same as what I was doing in Objective-C. But hey, the code worked, so I stopped worrying about it and went on to other things.

Several years passed, and the structure of my code still hadn’t changed. By the time of Swift 4.2, the preferred equivalent of id was Any instead of AnyObject, and I changed the name of my method to look more like Swift and less like Objective-C:

func newGame(imageSource: Any) {

Also, the Foundation overlay was now present, so the names of the types no longer started with the old Cocoa NS prefix; either we’d get an URL or we’d get a Data. On the other hand, implicit bridging to the corresponding CFTypeRef had gone away, so I had to cast explicitly to CFURL and CFData, to satisfy the ImageIO framework:

let src : CGImageSource = {
    switch imageSource {
    case let url as URL:
        return CGImageSourceCreateWithURL(url as CFURL, nil)!
    case let data as Data:
        return CGImageSourceCreateWithData(data as CFData, nil)!
    default:
        fatalError("image source was neither url nor data")
    }
}()

On the whole, then, I was still behaving in this code as if it were written in Objective-C. I was still letting any old object potentially come in the door, and then testing to see what type it was (and bailing out if I couldn’t deal with it). It hadn’t occurred to me to wonder whether there was a way to say: “There can be only two types of parameter; give me one or the other.”

Swift to the Rescue

But there is a way to say that. Actually, I can think of two ways.

First try: A protocol

What we’d like to do here is to declare a single type that means: “URL or Data, but nothing else.” One way to do that is declare a supertype that embraces URL and Data and no other types. But how? We can’t intervene in the Cocoa class hierarchy, and anyway, in Swift these are structs, not classes. But we can do the same thing easily enough by declaring a protocol to which only URL and Data happen to conform:

protocol ImageSource {}
extension URL: ImageSource {}
extension Data: ImageSource {}

As you can see, the protocol is empty; it’s just a way of bring both URL and Data under a single umbrella. And sure enough, when we now declare our method, we’ve got a type that limits its subtypes:

func newGame(imageSource: ImageSource) {

Inside the method, we still have to examine imageSource to see what type it really is, so that code hasn’t really changed at all:

let src : CGImageSource = {
    switch imageSource {
    case let url as URL:
        return CGImageSourceCreateWithURL(url as CFURL, nil)!
    case let data as Data:
        return CGImageSourceCreateWithData(data as CFData, nil)!
    default:
        fatalError("image source was neither url nor data")
    }
}()

Our method declaration now does a much better job of barring to the door to unwanted types. Granted, it isn’t foolproof. Someone could conceivably make some other type adopt the ImageSource protocol, and the compiler doesn’t know that that won’t happen, so we’ve still got our fatalError bringing up the rear of the switch with a default case. But we’re definitely a lot closer to saying what we mean: there are a limited range of ImageSource types, and the parameter needs to be one of those. And saying what you mean is a major virtue when you’re writing a computer program.

Second try: An enum

An alternative trick — and, in my opinion, a much better way in this situation — is to use an enum with associated types. The reason this is so much better is that it forms a closed system: an enum has a fixed set of cases, and a case’s associated type is a single fixed type:

enum ImageSource {
    case data(Data)
    case fileURL(URL)
}

Now that is beautifully neat. The enum is exhaustive: it can be a .data or a .fileURL, and that’s all it can be. And a .data is guaranteed to wrap a Data, and a .fileURL is guaranteed to wrap a URL. The door to any other type of parameter is now locked!

Our method declaration is unchanged:

func newGame(imageSource: ImageSource) {

But now this declaration has a new meaning! It quite literally means: this parameter must be a Data or a URL, and nothing else. Actually, as I’ve already said, it means that the parameter must wrap a Data or a URL. This wrapping may be called boxing; this enum is the “box” that I was hinting at in the title of this article.

Whoever calls this method now has a little extra work to do. The caller must put the real value into the box! But that is easily done, and the code is very compact. We simply need to construct the ImageSource argument, which we can do inline as part of the method call. Here’s how we would do it with a URL:

let url = // ...
self.newGame(imageSource: .fileURL(url))

The outcome, delightfully, is that it is now impossible to behave incorrectly. If the caller passes a .fileURL, the associated value must actually be a URL. If the caller passes a .data, the associated value must actually be a Data. And those are the only possibilities — and the compiler enforces this requirement!

Not only that, but the compiler knows that it has enforced this requirement, so when the time comes to examine what was actually passed to our method, there is no need to cast. Instead, our switch now looks to see which case we’ve been handed, and extracts the associated value from that case, in full knowledge of what type that associated value will be.

The syntax for extracting an enum case’s associated value is just a little obscure; there’s more than one way to do it, actually, but I like to say let inside the parentheses accompanying the case, like this:

let src : CGImageSource = {
    switch imageSource {
    case .fileURL(let url):
        return CGImageSourceCreateWithURL(url as CFURL, nil)!
    case .data(let data):
        return CGImageSourceCreateWithData(data as CFData, nil)!
    }
}()

Notice that there is no longer a default case, because the switch over an enum is guaranteed to be exhaustive. It’s clean; it’s simple; it says exactly what we mean to say. Our enum is indeed a single type that lets one of just two specific types in the door and then permits us to separate it out later.

So it turns out that an enum can function as a Useful Box. Our enum boxes up either of two specific types — in my example, either a Data or a URL, and no other — and now we have a single type (the box) that passes through a gateway (the method call), after which the contents can be unboxed once again.

A Useful Box

Once you know about the Useful Box trick, you’ll probably see opportunities to employ it in many other situations in your code.

For instance, a common situation that I often hear people asking about is this: I’m receiving some JSON from a server, and the value of a certain key in a dictionary might be a number or a string. How can I decode this successfully?

Here’s a simple example of the kind of JSON we might be talking about:

{
  "array":
  [
    {
      "val": 1
    },
    {
      "val": "hello"
    }
  ]
}

You can see the problem. The inner dictionaries both have a "val" key, but the first one has 1 (a number) as the value for that key, and the second one has "hello" (a string) as the value for that key. How are we going to decode both dictionaries into the same struct? To put it the other way round, what would a struct have to look like in order to accommodate a property that might be an Int or a String?

(You could argue that this isn’t very well-written JSON, but that’s neither here nor there; what the server serves is not up to us. Our job here is to cope with it, not to criticize it.)

One obvious naïve solution is for our struct to have two properties — an Int property and a String property. These can be Optional. So whichever type of value we get, we populate the corresponding property, leaving the other at nil:

struct Inner : Decodable {
    let val1: Int?
    let val2: String?
    enum CodingKeys : String, CodingKey {
        case val
    }
    init(from decoder: Decoder) throws {
        let container = try? decoder.container(keyedBy: CodingKeys.self)
        let intMaybe = try? container?.decode(Int.self, forKey: .val)
        let stringMaybe = try? container?.decode(String.self, forKey: .val)
        if let intReally = intMaybe {
            self.val1 = intReally
            self.val2 = nil
        } else if let stringReally = stringMaybe {
            self.val1 = nil
            self.val2 = stringReally
        } else {
            self.val1 = nil
            self.val2 = nil
        }
    }
}
struct Outer : Decodable {
    let array: [Inner]
}

That works, but now that we know about boxing with an enum, we can see how to do better. Let’s posit an enum which can be — meaning it can box — an Int or a String:

enum IntOrString {
    case int(Int)
    case string(String)
}

How would we make that enum Decodable? Well, we could basically do in the enum the same kind of work we were doing in the previous example — we look to see what kind of data we’ve got, and initialize the enum accordingly:

enum IntOrString : Decodable {
    case int(Int)
    case string(String)
    init(from decoder: Decoder) throws {
        let container = try? decoder.singleValueContainer()
        let intMaybe = try? container?.decode(Int.self)
        let stringMaybe = try? container?.decode(String.self)
        if let intReally = intMaybe {
            self = .int(intReally)
        } else if let stringReally = stringMaybe {
            self = .string(stringReally)
        } else {
            fatalError("what on earth?")
        }
    }
}

(Extra points if you already knew that it’s legal for an enum initializer to set self as a way of specifying which enum case the initializer creates.)

The delightful thing is that our structs are now as simple as can be. IntOrString simply is the type of the inner struct’s val property:

struct Inner : Decodable {
    let val: IntOrString
}
struct Outer : Decodable {
    let array: [Inner]
}

So when we receive our JSON, we just decode it as an Outer, and we’re done! Of course, whoever has to deal later with an Inner struct instance must still examine its val property to see which case of the IntOrString enum it is, either .int or .string, and extract the associated value in order to use it. But we already know how to do that!

You Might Also Like…

Swift 5.5: Replacing GCD With Async/Await

Multithreading! The mere word sends shivers up one’s spine. And if it doesn’t, it should. Main thread and background threads. Code that runs asynchronously. Code that can run simultaneously with other code. Code that can run simultaneously with itself. Code that can share data across threads — possibly with disastrous consequences. Concurrency. Multithreaded code is …

Swift 5.5: Replacing GCD With Async/Await Read More »

    Sign Up

    Get more articles and exclusive content that takes your iOS skills to the next level.