【问题标题】:Swift protocols: Why does the compiler complain that my class doesn't conform to a protocol?Swift 协议:为什么编译器会抱怨我的类不符合协议?
【发布时间】:2025-12-18 19:40:01
【问题描述】:

我一直在使用 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,但这似乎不必要地混乱。)

【问题讨论】:

  • 相信可以在这里找到答案:*.com/questions/25419529/…。不幸的是,它并不是那么优雅。当然,您键入的内容应该得到编译器的支持。
  • @Robert:不,编译器不应该支持它。编译器是对的。请看我的回答。

标签: swift protocols


【解决方案1】:

编译器是对的。

当你写作时

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

您正在(除其他外)以下声明:

如果一个类型确实符合AnimalHouse,那么可以放置一个 Animal!myAnimal 属性中。

现在让我们看看Birdhouse是如何定义的

class Birdhouse: AnimalHouse {
    var myAnimal: Bird!

    ...
}

myAnimal 上的类型是 Bird!。 而且您不能将Animal! 放在Bird! 类型的属性中。

所以Birdhouse 确实尊重AnimalHouse 协议中的承诺。

【讨论】:

    【解决方案2】:

    正如您在问题中所说的那样,您不能只是从Animal 向下转换为Bird。我建议将 var 更改为可选的,因为 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 协议的进一步发展可能是将 throws 添加到 setter (not possible as of Swift 2.0) 或 AnimalHouse 返回它可以容纳的动物类型。

    protocol AnimalHouse {
        var myAnimal: Animal? {get set}
        func houses() -> Any
    }
    
    class Birdhouse: AnimalHouse {
        func houses() -> Any {
            return Bird.self
        }
    }
    

    【讨论】:

    • 谢谢!在我的情况下,我意识到我实际上并不需要 myAnimal 的公共设置器——只需要一个获取器。因此,像你所做的那样,让 myAnimal 成为一个抓取 myBird 的计算属性,基本上得到了我需要的东西。
    【解决方案3】:

    也许你会对这样的做法感到满意:

    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"
    

    【讨论】: