skip to Main Content

I’m pretty new in the iOS development world and I’m trying to understand protocols in Swift. Here’s my code:

protocol Animal {
    func printFood()
}

struct Rabbit: Animal {
    func printFood() {
        print("carrot")
    }
}

struct Sheep: Animal {
    func printFood() {
        print("grass")
    }
}

I have this very simple protocol with these two structs conforming to it. I wrote these two functions:

func printAnimalFood<A: Animal>(_ animal: A) {
    animal.printFood()
}

func printAnimalFood(_ animal: Animal) {
    animal.printFood()
}

Is this the same way to write the same thing? Or not? In many examples I see a lot of functions written in the first way but I don’t understand why since I can use the second way and obtain the same result. Why should I introduce a generic type parameter since I can directly use the protocol as parameter? Is there any subtle difference? Are there some situations in which I should use the first "syntax" over the second one and vice versa?

I tried to execute both the functions and, as expected, the printed result is the same, with no difference.

2

Answers


  1. Protocols as Parameters:
    When you define a function or method that takes a protocol as a parameter, it means that any value passed to that parameter must conform to the specified protocol. This allows you to write more flexible and reusable code.

    Protocols as Generic Types:
    When you use a protocol as a generic type, it means that the generic type placeholder can be any type that conforms to the specified protocol. This allows you to create more generic and flexible code that can work with different types that share common behaviors.

    Login or Signup to reply.
  2. In the code that you wrote there is no noticeable difference from usage point of view. The two will probably be complied differently though.

    You in most cases don’t need to restrict it with generic. But doing so can allow you to do some additional things.

    For instance if you wish to compare two animals you can not do this

    func isAnimal(_ animalA: Animal & Equatable, sameAs animalB: Animal & Equatable) -> Bool {
        return animalA == animalB
    }
    

    mostly because you can not compare your sheep to your rabbits. So you need to restrict it with generic to ensure that both animas are actually of the same type:

    func isAnimal<AnimalType: Animal & Equatable>(_ animalA: AnimalType, sameAs animalB: AnimalType) -> Bool {
        return animalA == animalB
    }
    

    Also when using return type things can get pretty dicey. Imagine you want to do something like this:

    let sheep = Sheep()
    let fedSheep = feed(animal: sheep)
    

    if you define a feed method without a generic you have no relation between input animal and an output animal. So the method itself does not restrict you from returning a Rabbit instead of a sheep. So examine the two methods:

    func feed<AnimalType: Animal & Feedable>(animal: AnimalType) -> AnimalType {
        var feedingAnimal = animal
        feedingAnimal.fedPercentage = 100
        return feedingAnimal
    }
    
    func naturalFriend(ofAnimal animal: Animal) -> Animal? {
        if let sheep = animal as? Sheep {
            return Rabbit()
        } else {
            return nil
        }
    }
    

    The first one will ensure that if you are feeding a Rabbit you will get back a Rabbit, and if you are feeding a Sheep you are getting back a Sheep. The second method will have no such restrictions and the returned value can be any type of Animal, regardless of the input.

    But these are just basics. Most of the problems where you need a generic restriction actually comes with introducing associated value to a protocol. You may find some interesting cases down that road. But as a beginner I suggest that you always try to solve it without a generic. If you get some strange errors related to it then maybe try using some Animal. And if that fails try with generic restriction.

    And for better understanding of errors try this (absurd) method.

    func areAllTheseItemsSame(_ items: [Comparable]) -> Bool {
        guard items.count > 1 else { return true }
        let firstItem = items[0]
        for item in items {
            if item == firstItem {
                continue
            } else {
                return false
            }
        }
        return true
    }
    

    You should first get two errors where first will tell you to add any before Comparable. The second then becomes tricky saying "Binary operator ‘==’ cannot be applied to two ‘any Comparable’ operands" which basically means "I can’t compare apples to oranges". So you need to use some instead of any.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search