Swift协议: 为什么编译器会抱怨我的类不符合协议要求?

3

我一直在尝试使用Swift协议,并且正在努力弄清楚为什么这段代码无法正常工作...

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

编译器一直提示我说BirdHouse不符合协议AnimalHouse。如果你继续查看,它会告诉你myAnimal需要类型Animal,而我提供的是Bird类型。显然,Bird确实符合Animal协议,但这并不足以让编译器满意。
我猜这是那种只需要一行代码就能解决的问题,关键在于知道那一行代码在哪里。有人有什么建议吗?
(是的,我可以将myAnimal作为Animal创建,然后在函数中将其强制转换为Bird,但这似乎过于混乱了。)

我相信答案可以在这里找到:https://dev59.com/NoLba4cB1Zd3GeqPh64W。不幸的是,这并不是很优雅。当然,编译器应该支持你输入的内容。 - BallpointBen
@Robert:不,编译器不应该支持它。编译器是正确的。请看我的答案。 - Luca Angeletti
3个回答

3
编译器是正确的。
当你写下以下代码时:
protocol AnimalHouse {
    var myAnimal: Animal! {get set}
}

您正在发表以下言论(其中之一):
如果一个类型符合AnimalHouse,那么就可以将Animal!放在myAnimal属性中。
现在让我们看看Birdhouse是如何定义的。
class Birdhouse: AnimalHouse {
    var myAnimal: Bird!

    ...
}
myAnimal的类型是Bird!。 你不能将一个类型为Bird!的属性中放置一个Animal!。 因此,Birdhouse没有遵守AnimalHouse协议中所承诺的内容。

2
正如您在问题中所说,您不能仅从Animal向下转换为Bird。我建议将变量更改为可选项,因为AnimalHouse有时可能没有居住者。在我的实现中,非Bird动物无法进入鸟屋。
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
    }
}

进一步发展AnimalHouse协议的可能是在setter中添加throwsSwift 2.0版本不支持),或者使AnimalHouse返回它可以容纳的动物类型。

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

class Birdhouse: AnimalHouse {
    func houses() -> Any {
        return Bird.self
    }
}

1
谢谢!在我的情况下,我意识到我实际上并不需要一个公共的setter来设置myAnimal - 只需要一个getter。所以将myAnimal作为计算属性,并像你一样获取myBird,基本上得到了我需要的东西。 - Toddarooski

1
也许您会满意于这种方式:
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
    }
}

但是“House”协议只能用作一般约束,即以下情况不可能:
let house: House = Birdhouse() // Compile-time error

但你可以做以下事情:
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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接