Swiftwater Logo

Swift Extensions -Part Deux

This entry is part 9 of 21 in the series Swiftwater

ABSTRACT

In the previous post, we learned about extensions, and how they apply to Structs, Classes, Enums and most data types.

In this post, we’ll cover Protocol extensions, and learn a little bit about some powerful, yet potentially confusing capabilities.

THE BASICS

The basic syntax for extending protocols is pretty much exactly the same as for the other types:

We start with a protocol. We’ll create one for communicating emoji characters:

protocol EmojiString {
    var poopEmoji: String { get }
    var waterEmoji: String { get }
    var smileyEmoji: String { get }
}

Default Extension Implementation

Now, as of Swift 2.0, we can define default implementations in protocol extensions, like so:

extension EmojiString {
    var poopEmoji: String { return "\u{1F4A9}" }
    var waterEmoji: String { return "\u{1F30A}" }
    var smileyEmoji: String { return "\u{1F600}" }
}

This protocol now, by default, gives the following emojis: ?, ?, ? (Poop, Wave and Smiley).

So when you declare a class to follow a protocol, you don’t actually need to implement anything:

class StandardEmojiClass: EmojiString {}

let someEmoji = StandardEmojiClass()
let poop = someEmoji.poopEmoji
let water = someEmoji.waterEmoji
let smiley = someEmoji.smileyEmoji

print("\(poop), \(water), \(smiley)")

Which gives us:

?, ?, ?

Is This Inheritance?

Note that we did not actually declare anything in the StandardEmojiClass. It all came from the protocol.

Doesn’t that smell like inheritance?

So let’s declare a new protocol, with another emoji:

protocol EmojiString2 {
    var frownyEmoji: String { get }
}

extension EmojiString2 {
    var frownyEmoji: String { return "\u{2639}" }
}

class FrownyEmojiClass: EmojiString2 {}
let someEmoji2 = FrownyEmojiClass()
let frown = someEmoji2.frownyEmoji

print(frown)

OK. we now have two classes, based on two protocols.

Is This MULTIPLE Inheritance?

However, can’t we follow multiple protocols?

Let’s give it a try:

class HybridEmoji: EmojiString, EmojiString2 {}
let someEmoji3 = HybridEmoji()
let poop2 = someEmoji3.poopEmoji
let water2 = someEmoji3.waterEmoji
let smiley2 = someEmoji3.smileyEmoji
let frown2 = someEmoji3.frownyEmoji

print("\(poop2), \(water2), \(smiley2), \(frown2)")
?, ?, ?, ☹

Oh…wow. That’s like multiple inheritance, isn’t it?

Kinda looks like it, yah?

We can also override the default in a class extension, like so:

extension HybridEmoji {
    var waterEmoji: String { return "\u{1F6BE}" }
}

As soon as we do that, the print we saw above changes to this:

?, ?, ?, ☹

Now, let’s go for broke. We’ll see if we can modify the “root” emoji:

extension EmojiString {
    var smileyEmoji: String { return "\u{1F60E}" }
}

No, that won’t work (thank God). You get “note: 'smileyEmoji' previously declared here

So it’s not quite the same as polymorphic inheritance, but it is very close.

Let’s look to see if we can create a “diamond of death”:

protocol EmojiString3: EmojiString {
    var frownyEmoji: String { get }
}

extension EmojiString3 {
    var frownyEmoji: String { return "\u{1F92F}" }
}
class Emoji2: EmojiString3 {}
let someEmoji4 = Emoji2()
let poop3 = someEmoji4.poopEmoji
let water3 = someEmoji4.waterEmoji
let smiley3 = someEmoji4.smileyEmoji
let frown3 = someEmoji4.frownyEmoji

print("\(poop3), \(water3), \(smiley3), \(frown3)")

This prints the following:

?, ?, ?, ?

That’s fairly straightforward. We don’t have a diamond, yet, though.

OK. Diamonds are a bug’s best friend:

class DiamondsAreABugsBestFriend: EmojiString2, EmojiString3 {}

Damn. That gave us a big ol’ mess, didn’t it?

error: ExtensionsExplorerPartDeux.playground:72:7: error: type 'DiamondsAreABugsBestFriend' does not conform to protocol 'EmojiString2'
class DiamondsAreABugsBestFriend: EmojiString2, EmojiString3 {}
      ^

ExtensionsExplorerPartDeux.playground:21:9: note: multiple matching properties named 'frownyEmoji' with type 'String'
    var frownyEmoji: String { get }
        ^

ExtensionsExplorerPartDeux.playground:60:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{1F92F}" }
        ^

ExtensionsExplorerPartDeux.playground:25:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{2639}" }
        ^

error: ExtensionsExplorerPartDeux.playground:72:7: error: type 'DiamondsAreABugsBestFriend' does not conform to protocol 'EmojiString3'
class DiamondsAreABugsBestFriend: EmojiString2, EmojiString3 {}
      ^

ExtensionsExplorerPartDeux.playground:56:9: note: multiple matching properties named 'frownyEmoji' with type 'String'
    var frownyEmoji: String { get }
        ^

ExtensionsExplorerPartDeux.playground:60:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{1F92F}" }
        ^

ExtensionsExplorerPartDeux.playground:25:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{2639}" }
        ^

So we won’t be mining diamonds that way. How about trying to sneak in the back door?

class Emoji3: Emoji2, EmojiString2 {}

No dice. We’ll have to settle for cubic zirconia:

error: ExtensionsExplorerPartDeux.playground:75:7: error: type 'Emoji3' does not conform to protocol 'EmojiString2'
class Emoji3: Emoji2, EmojiString2 {}
      ^

ExtensionsExplorerPartDeux.playground:21:9: note: multiple matching properties named 'frownyEmoji' with type 'String'
    var frownyEmoji: String { get }
        ^

ExtensionsExplorerPartDeux.playground:60:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{1F92F}" }
        ^

ExtensionsExplorerPartDeux.playground:25:9: note: candidate exactly matches
    var frownyEmoji: String { return "\u{2639}" }
        ^

CONCLUSION

Protocols can be extended with default behavior. This allows you to declare classes, based on protocols, without having to implement the protocol declaration completely, yet still get some of the behavior.

This is very cool, but it looks a lot like inheritance. Worse, it looks a lot like multiple inheritance.

Fortunately, Swift doesn’t allow you to get into “diamond of death” situations. It isn’t exactly like multiple inheritance.

Also, you can’t “rewrite” protocol-extension-defined defaults with a subsequent extension (although you can do it in a class derived from the protocol).


SAMPLE PLAYGROUND

// Now, as of Swift 2, you can add "default vars and funcs" to protocol extensions:
protocol EmojiString {
    var poopEmoji: String { get }
    var waterEmoji: String { get }
    var smileyEmoji: String { get }
}

extension EmojiString {
    var poopEmoji: String { return "\u{1F4A9}" }
    var waterEmoji: String { return "\u{1F30A}" }
    var smileyEmoji: String { return "\u{1F600}" }
}

class StandardEmojiClass: EmojiString {}

let someEmoji = StandardEmojiClass()
let poop = someEmoji.poopEmoji
let water = someEmoji.waterEmoji
let smiley = someEmoji.smileyEmoji

print("\(poop), \(water), \(smiley)")

// Let's specify another protocol, with a new emoji:
protocol EmojiString2 {
    var frownyEmoji: String { get }
}

extension EmojiString2 {
    var frownyEmoji: String { return "\u{2639}" }
}

class FrownyEmojiClass: EmojiString2 {}
let someEmoji2 = FrownyEmojiClass()
let frown = someEmoji2.frownyEmoji

print(frown)

// Now, we follow multiple protocols:
class HybridEmoji: EmojiString, EmojiString2 {}
let someEmoji3 = HybridEmoji()
let poop2 = someEmoji3.poopEmoji
let water2 = someEmoji3.waterEmoji
let smiley2 = someEmoji3.smileyEmoji
let frown2 = someEmoji3.frownyEmoji

print("\(poop2), \(water2), \(smiley2), \(frown2)")

extension HybridEmoji {
    var waterEmoji: String { return "\u{1F6BE}" }
}

// This won't work:
//extension EmojiString {
//    var smileyEmoji: String { return "\u{1F60E}" }
//}

// Let's see if we can create a "diamond of death":

protocol EmojiString3: EmojiString {
    var frownyEmoji: String { get }
}

extension EmojiString3 {
    var frownyEmoji: String { return "\u{1F92F}" }
}

class Emoji2: EmojiString3 {}
let someEmoji4 = Emoji2()
let poop3 = someEmoji4.poopEmoji
let water3 = someEmoji4.waterEmoji
let smiley3 = someEmoji4.smileyEmoji
let frown3 = someEmoji4.frownyEmoji

print("\(poop3), \(water3), \(smiley3), \(frown3)")

// This is a bug. It won't work.
//class DiamondsAreABugsBestFriend: EmojiString2, EmojiString3 {}
// This is also a bug.
//class Emoji3: Emoji2, EmojiString2 {}