3

I've been playing around with Swift protocols and I'm trying to figure out why this code isn't working...

protocol Animal {
  var name: String {get}
  var breed: String {get}
}

struct Bird: Animal {
  var name: String
  var breed: String
  var wingspan: Double
}

protocol AnimalHouse {
  var myAnimal: Animal! {get set}
}

class Birdhouse: AnimalHouse {
  var myAnimal: Bird!

  func isOpeningBigEnough() -> Bool {
    return myAnimal.wingspan <= 5.0
  }
}

The problem the compiler keeps giving me is that that BirdHouse doesn't conform to protocol AnimalHouse. If you follow up, it'll tell you that myAnimal requires type Animal, and I'm supplying type Bird. Obviously, Bird does conform to the Animal protocol, but that's not enough to make the compiler happy.

I'm assuming this is one of those one-line fixes where the trick is knowing where the one line is. Anybody have any suggestions?

(And, yes, I could make myAnimal an Animal and then cast it as a Bird later in the function, but that seems unnecessarily messy.)

Toddarooski
  • 2,645
  • 2
  • 16
  • 12
  • I believe the answer can be found here: http://stackoverflow.com/questions/25419529/cant-use-concrete-subclass-to-implement-a-property-in-protocol-in-swift. Unfortunately, it is not all that elegant. Certainly, what you typed should be supported by the compiler. – BallpointBen Nov 19 '15 at 05:29
  • @Robert: No, it should not be supported by the compiler. The compiler is right. Please see my answer. – Luca Angeletti Nov 20 '15 at 20:30

3 Answers3

3

The compiler is right.

When you write

protocol AnimalHouse {
    var myAnimal: Animal! {get set}
}

you are making (among the others) the following statement:

If a type does conform to AnimalHouse, then it is possible to put an Animal! inside the myAnimal property.

Now let's look at how Birdhouse is defined

class Birdhouse: AnimalHouse {
    var myAnimal: Bird!

    ...
}

The type on myAnimal is Bird!. And you cannot put an Animal! inside a property of type Bird!.

So Birdhouse does not respect what promised in the AnimalHouse protocol.

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
2

As you said yourself in the question, you can't just downcast to Bird from Animal. I propose changing the var to be optional, as an AnimalHouse is likely to be without inhabitant some of the time.

In my implementation below non Bird animals can't enter the birdhouse.

protocol AnimalHouse {
    var myAnimal: Animal? {get set}
}

class Birdhouse: AnimalHouse {
    var myAnimal: Animal? {
        get{
            return myBird
        }
        set(newanimal){
            if let bird = newanimal as? Bird {
                myBird = bird
            }
        }
    }

    private var myBird: Bird?

    func isOpeningBigEnough() -> Bool {
        return myBird?.wingspan <= 5.0
    }
}

A further development of the AnimalHouse protocol might be to add throws to the setter (not possible as of Swift 2.0) or that an AnimalHouse returns the type of animal it can house.

protocol AnimalHouse {
    var myAnimal: Animal? {get set}
    func houses() -> Any
}

class Birdhouse: AnimalHouse {
    func houses() -> Any {
        return Bird.self
    }
}
Community
  • 1
  • 1
orkoden
  • 18,946
  • 4
  • 59
  • 50
  • 1
    Thanks! In my situation, I realized that I didn't actually need a public setter for myAnimal -- only a getter. So making myAnimal a computed property that grabbed myBird, as you did, basically got my what I needed. – Toddarooski Nov 24 '15 at 01:03
1

Maybe you will be satisfied with such an approach:

protocol Animal {
    var name: String {get}
    var breed: String {get}
}

struct Bird: Animal {
    var name: String
    var breed: String
    var wingspan: Double
}

// Read from here

protocol House {
    typealias Inhabitant
    var inhabitant: Inhabitant! {get set}
}

class Birdhouse: House {
    typealias Inhabitant = Bird
    var inhabitant: Inhabitant!

    func isOpeningBigEnough() -> Bool {
        return inhabitant.wingspan <= 5.0
    }
}

But then the 'House' protocol can only be used as a generic constraint, i.e. the following is impossible:

let house: House = Birdhouse() // Compile-time error

But you can do the following:

func printHouseInhabitant<T: House>(house: T) {
    print(house.inhabitant)
}

let house = Birdhouse()
house.inhabitant = Bird(name: "Unnamed", breed: "general bird", wingspan: 4.5)
printHouseInhabitant(house) // "Bird(name: "1", breed: "2", wingspan: 3.0)\n"
werediver
  • 4,667
  • 1
  • 29
  • 49