Combining Protocols

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Combining Protocols

You know the basics of defining and implementing protocols, and you’ve seen some examples of extending protocols or combining protocols with inheritance or composition. Along with class inheritance, you now have a wealth of options for designing your app’s data model. But how do you decide which to use?

Extending a Protocol

Sometimes, it turns out that most types that conform to a protocol have the same implementation of a method, initializer, subscript, or computed property. Instead of duplicating code, you can provide a default implementation in an extension of the protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one the extension provides.

Protocol Inheritance vs. Class Inheritance

Classes have inheritance; structs and enums don’t. But all types can implement protocols. So when you’re trying to model objects that have some properties or functionality in common, do you change your structs to classes or use protocol inheritance? Or something else? Here are some differences you might consider:

Class-Only Protocols

Every class implicitly conforms to AnyObject. If a protocol inherits from AnyObject, only classes can implement it.

protocol MutableLocalizable: Localizable {
  mutating func change(to language: Language)
}

protocol UIKitLocalizable: AnyObject, Localizable {
  func change(to language: Language)
}
protocol LocalizableViewController where Self: UIViewController {
  func showLocalizedAlert(text: String)
}

Inheritance vs. Composition

Use protocol inheritance to refine capability. Protocol inheritance is a very strong relationship that can reduce the reusability of the inheriting protocol, if you try to inherit a capability that isn’t closely related. Some standard library examples of protocol inheritance are:

protocol DiskWritable {
  func writeToDisk(at url: URL) throws
}
protocol DiskWritable: Encodable {
  func writeToDisk(at url: URL) throws
}

extension DiskWritable {
  func writeToDisk(at url: URL) throws {
    let encoder = JSONEncoder()
    let data = try encoder.encode(self)
    try data.write(to: url)
  }
}
protocol DiskWritable {
  func writeToDisk(at url: URL) throws
}

extension DiskWritable where Self: Encodable {
  func writeToDisk(at url: URL) throws {
    let encoder = JSONEncoder()
    let data = try encoder.encode(self)
    try data.write(to: url)
  }
}
struct TodoList: DiskWritable, Encodable {
  var name: String
  var items: [String]  // simplified Item
}
typealias DiskWritableByEncoding = DiskWritable & Encodable
struct TodoList: DiskWritableByEncoding {
  var name: String
  var items: [String]
}

Extending a Protocol

And now, back to protocol extensions and the surprising results you can get when you implement methods that aren’t required in the protocol. The following example is from Expert Swift.

protocol Greetable {
  func greet() -> String
}

extension Greetable {
  func greet() -> String {
    return "Hello"
  }

  func leave() -> String {  // not a requirement of Greetable
    return "Bye"
  }
}
struct GermanGreeter: Greetable { }
let greeter = GermanGreeter()
print(greeter.greet())  // Hello
print(greeter.leave())  // Bye
struct GermanGreeter: Greetable {
  func greet() -> String {
    return "Hallo"  // one letter different from Hello
  }

  func leave() -> String {
    return "Tschüss"
  }
}
let greeter = GermanGreeter()
greeter.greet()  // Hallo
greeter.leave()  // Tschüss
let greeterX: Greetable = GermanGreeter()
greeterX.greet()  // Hallo
greeterX.leave()  // Bye

Static & Dynamic Dispatch

Like everything in your code, your functions have an address in your app’s memory. When your code calls a function, Swift jumps to that address to dispatch (start executing) the function. But there are two ways to dispatch a function, depending on how it’s stored: static dispatch and dynamic dispatch.

See forum comments
Download course materials from Github
Previous: Protocols - Introduction Next: Type Erasure