简体   繁体   中英

Swift protocols: Why does the compiler complain that my class doesn't conform to a protocol?

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.)

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.

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

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, ie 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"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM