Swift’s Protocol Extension

I have been working on porting over my ObjC platform layer from

Handmade Hero and I have a bit of code that looks like this:

self.aboutMenuItem.title = [self.aboutMenuItem.title
                            stringByReplacingOccurrencesOfString:DEADZONE_TITLE
                            withString:APP_NAME];
self.hideMenuItem.title = [self.hideMenuItem.title
                           stringByReplacingOccurrencesOfString:DEADZONE_TITLE
                           withString:APP_NAME];
self.quitMenuItem.title = [self.quitMenuItem.title
                           stringByReplacingOccurrencesOfString:DEADZONE_TITLE
                           withString:APP_NAME];
self.helpMenuItem.title = [self.helpMenuItem.title
                           stringByReplacingOccurrencesOfString:DEADZONE_TITLE
                           withString:APP_NAME];
self.window.title = [self.window.title
                     stringByReplacingOccurrencesOfString:DEADZONE_TITLE
                     withString:APP_NAME];

The point of this code is to go through and replace all of the text with the

target name in it to a string defined by a config file. But you can easily

imagine that this is any code that is starting to look fairly redundant across

a set of various differently typed items.

This is really just a simple case of iterating over the items in the collection and calling the title setting.

for (var item) in items {
    item.title = item.title.replace(placeholder, withString: title)
}

The astute readers out there may have caught an issue with this approach though. For one, a window is of type NSWindow and the other items are of type

NSMenuItem. That's not going to work as they share no base class that contains a title property.

We have two choices here:

  1. Handle window on it's own separate line, or 2. Create a protocol extension so they do have a common interface with a title

property.

I want my code to capture the intent as much as possible with as little redundancies as possible, so option #1 is out.

So, let's think about option #2. What I really want to say is that both

NSWindow and NSMenuItem share a common set of behaviors, or more specifically in this case, a single behavior: title.

Now, it seems that they actually already do share this behavior and that it is just not codified. However, if you look at the declaration of each, you'll notice that NSWindow actually returns a String? for title and NSMenuItem returns a String (note the optional vs. non-optional difference).

Ok… that's annoying, but we can work around that!

protocol HasTitle {
    var ht_title: String { get set }
}

extension NSMenuItem: HasTitle {
    var ht_title: String {
        get { return self.title }
        set { self.title = newValue }
    }
}

extension NSWindow : HasTitle {
    var ht_title: String {
        get { return self.title ?? "" }
        set { self.title = newValue }
    }
}

With the protocol extension HasTitle, we have now codified what the behavior semantics are. It's then trivial to implement that appropriately for each type that you want to share this HasTitle codification.

The final code to make use of this now looks like:

let items: [HasTitle] = [aboutItem, hideItem, quitItem, helpItem, window]
for (var item) in items {
    item.ht_title = item.ht_title.replace(placeholder, withString: title)
}

I almost forgot… you'll probably paste this code in and wonder why it is not

working for you. I've created an extension on String that adds the replace

function. It simply forwards the parameters into

stringByReplacingOccurrencesOfString.

Now, unfortunately, Swift needed a little bit of type annotation help to make it all work 100%, but we've done it. With one simple protocol and two easy class extensions, our usage code has become significantly cleaner, easier to update, and less error prone.

Could we bring this technique back to ObjC? Of course. Just write the simple for-loop to begin with. However, you'll probably stop there because that is all that is needed to make the ObjC side happy as we can send messages to any object instance; we can completely forgo the HasTitle protocol.

However, that is the part that I think it vastly more important as it's that protocol that leads us into thinking about better composition models. I hope to do a longer blog post about that at another time.

Happy Swifting!

Update, February 16th: I removed the usage1 of map as it was an abuse of the function. I used it because I didn't want to use the ugly version of the for-loop and I was having issues with the iterative version. Turns out I just need (var item) instead of var item. Seems like a compiler bug to me…

  1. Ok… it was really a very bad abuse of the map() function.
Swift’s Protocol Extension