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 {}