Programming in Swift: Functions & Types

Jan 4 2022 · Swift 5.5, iOS 15, Xcode 13

Part 5: Protocols & Inheritance

42. Protocols

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 41. Challenge: Initializers Next episode: 43. Protocols & Extensions

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 42. Protocols

Update Notes: This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.

Transcript: 42. Protocols

So far, you’ve learned about three named types: enumerations, structures, and classes. There’s just one more to learn about: Protocols. You’ve already used protocols! CaseIterable was a protocol we used a few times on enumerations. And if you worked through Your First and Second SwiftUI app courses, you definitely used the “View” protocol.

But you haven’t written your own protocols yet. We’ll fix that in this episode! Now, this is actually a deep topic, and we can’t cover everything about protocols in this course. But, now that you know about class inheritance, we can go over the similarity between that concept, and protocols.

Then, if you’re interested, you can do more research into advanced usage of protocols. Our book, Swift Apprentice, has two chapters on protocols which cover some topics not covered in this course. Now, let’s compare class inheritance, and protocols!

This starter code begins with what we just wrote in the challenge on inheritance.

Animal is what you’d call an “abstract class”. It’s not meant to be instantiated directly. you can tell that because the speak method doesn’t do anything; it just requires that all subclasses can speak. It’s assumed you’ll override that method to provide something appropriate, for each concrete animal type.

…Like Dog

…and Cat.

Let’s turn Animal into a protocol, and we’ll see that abstract classes and protocols have a lot in common. The easiest way to begin with that will be to comment out everything within Animal. You’ll get errors, but we’ll deal with them.

32 class Animal {
//  let name: String
//
//  required init(name: String) {
//    self.name = name
//  }
//
//  func speak() { }
}

Then, change the keyword class, to protocol.

32 protocol Animal {

Now’s let’s deal with how to use name, with Animal being a protocol. Change the let to var, and after the type of name (String), in curly braces, put the keyword get.

33 var name: String { get }

//  required init(name: String) {

Protocols define a common set of properties and behaviors. But unlike with classes, in the definition of a protocol, you’re only listing requirements, not implementations.

So here, we’re saying that for every Animal, you can get its name. But we’re not saying anything about how that’s done. name could be a computed property, or stored. That’s up to the animal types themselves.

You could also add set, within the braces, if you wanted to enforce mutability, for the animals’ names, but we don’t.

Dog and Cat need stored properties for their names. So unfortunately, unlike with a superclass, we have to define that storage individually for both Dog and Cat.

class Dog: Animal {
43  let name: String
  var tricksLearnedCount: Int
class Cat: Animal {
61 let name: String
  
  override func speak() {

Notice how we used let, as Animal originally did, even though the protocol uses the keyword var. let doesn’t show up in protocols, but a constant counts as a get-only property, as far a protocol is concerned.

35 required init(name: String) {
    self.name = name
  }

//  func speak() { }

For the initializer requirement, it’s going to be the same sort of situation. We won’t be able use to an initializer body, from Animal.

But we can still enforce the requirement of having a certain initializer, by putting the signature of that initializer, in the protocol.

35   init(name: String)
//  required init(name: String) {

This required init body is just what we need, for Cat, so cut and paste it into Cat.

  var name: String { get }

  init(name: String)

//  func speak() { }
61 required init(name: String) {
    self.name = name
  }
  
  override func speak() {

And for dog, we have to assign directly to name, as well, because there is no such thing as “super”, anymore – protocols are not superclasses. Dog and Cat are actually both base classes now.

45 self.tricksLearnedCount = tricksLearnedCount
    self.name = name
  }

For the last requirement, that an animal can speak, the necessary change is the smallest so far.

Uncommenting what we had, we see that protocol methods don’t –and can’t– have bodies. So we delete it.

37 func speak()

And now, we’re even more clearly expressing that an animal has to be able to speak, than we were before. What we don’t do, is use the override keyword anymore. That’s only for subclassing.

53 func speak() {
65 func speak() {

For a moment, let’s comment out Cat’s speak method.

65 //  func speak() {
//    print("My name is \(name). Please leave me alone. I must look at this wall.")
//  }

And you’ll see that “Cat does not conform to” Animal.

There are two parts to using protocols with your types.

The first part is “adopting” a protocol: where you declare that a type “conforms” to a protocol. That uses the same syntax as class inheritance: you follow the name of the type with a colon and the name of the protocol you want to conform to.

In our case, because Animal used to be Dog and Cat’s superclass, we didn’t need to make any changes here.

Unlike with class inheritance, you can adopt protocols using extensions. You’ll see that technique used a lot, for organizational purposes.

To actually “conform” to a protocol, means satisfying all of its requirements. According to our definition, if a type doesn’t have… a name property, an initializer that accepts a name, and a speak method, it can’t be an Animal.

…but otherwise, it can!

65   func speak() {
    print("My name is \(name). Please leave me alone. I must look at this wall.")
  }

We’re almost done getting rid of the errors! What the last ones are telling us, is that, because Animal is no longer a superclass, of Dog and Cat, Swift doesn’t know what the elements in the array have in common. But we can help out, with explicit typing.

let animals: [Animal] = [Dog(name: "Fang"), Cat(name: "Mr. Midnight")]

And now, we can iterate through our animals, just like when Animal was a superclass, and each one of them speaks in their own way! What you can’t do, is instantiate an Animal directly.

77 Animal(name: "Animal")

But when Animal was a class, it wasn’t meant to be instantiated. So this is just fine!