【问题标题】:Can I use `inout` with protocol extensions?我可以在协议扩展中使用“inout”吗?
【发布时间】:2016-05-27 14:39:18
【问题描述】:

我有一个协议及其扩展,以及一个符合该协议的类。

protocol WarAbilities {
    var strength: Int { get set }
    func attack(inout opponent: WarAbilities)
}

extension WarAbilities {
    func attack(inout opponent: WarAbilities) {
        opponent.strength -= 1
    }
}

class Warrior: WarAbilities {
    var strength: Int

    init(strength: Int) {
        self.strength = strength
    }
}

现在如果我想让两个战士战斗:

let thug1 = Warrior(strength: 10)
let thug2 = Warrior(strength: 30)

thug1.attack(&thug2)

我收到此错误消息:

错误:不能将“WarAbilities”类型的不可变值作为 inout 参数传递

添加mutating 看起来很有希望:

protocol WarAbilities {
    var strength: Int { get set }
    mutating func attack(inout opponent: WarAbilities)
}

extension WarAbilities {
    mutating func attack(inout opponent: WarAbilities) {
        opponent.strength -= 1
    }
}

但编译器也不满意,我无法理解新错误消息的含义:

错误:不能将不可变值作为 inout 参数传递:从“Warrior”到“WarAbilities”的隐式转换需要临时

由于Warrior 符合WarAbilities,我认为其中一个可以工作 - 但看起来 Swift 没有这种......协方差?我什至不确定我在说什么。

我的错误是什么?

【问题讨论】:

    标签: swift


    【解决方案1】:

    将其设为class 协议并去掉(当时)不必要的inout 东西:

    protocol WarAbilities : class {
        var strength: Int { get set }
        func attack(opponent: WarAbilities)
    }
    
    extension WarAbilities {
        func attack(opponent: WarAbilities) {
            opponent.strength -= 1
        }
    }
    
    class Warrior: WarAbilities {
        var strength: Int
    
        init(strength: Int) {
            self.strength = strength
        }
    }
    
    let thug1 = Warrior(strength: 10)
    let thug2 = Warrior(strength: 30)
    
    thug1.attack(thug2)
    
    thug2.strength // 29
    

    (事实上,我不太清楚为什么你需要一个协议;因为 Warrior 是一个类,你可以将 WarAbilities 设为它的超类。)

    【讨论】:

      【解决方案2】:

      我认为有两点是错误的:

      1. let-常量传递给inout
      2. 将子类型传递为inout

      请注意,在您的示例中,您甚至 需要 inout,因为您没有覆盖参数,而是覆盖该参数的 some 实例成员。简单地删除 inout 可能会适用于您的情况,因为您根本不需要它。(请参阅 matt 的答案以正确处理此问题)

      其次请注意,在这里使用mutating 可能对您没有任何好处,因为这只是在谈论改变它自己的成员,而不是改变参数的成员。


      问题说明:
      让我们考虑一个更简单的问题,它产生完全相同的问题输出:

      protocol A {}
      class B : A {}
      
      func c(inout d : A) {}
      
      let a = B()
      c(&a)
      

      这首先不起作用,因为a 是一个常数。因此将let 更改为var
      然后编译器抱怨需要一个临时的。这是一个非常令人困惑的错误消息,但问题实际上是有道理的。

      您不能将某个 Type 的子类型传递给 inout 或将某个协议的实现传递给期望该协议的 inout

      说明 - 考虑以下示例:

      class A {}
      class B : A {}
      
      func c(inout a : A) { a = A() }
      

      如果你通过调用该方法会发生什么

      var b = B()
      c(&b)
      

      b 将被更改,现在它将与 a 相同 - 但 a 不是 B 类型,据说 b 是。

      请注意,以下内容将再次起作用:

      var b : A = B()
      c(&b)
      

      我们传入B,但实际上只担心它是A。协议也是如此:

      protocol P {}
      class K : P {}
      class X : P {}
      
      func f(inout p : P) { p = X() }
      
      var k = K()
      f(&k) // does not work
      
      var p : P = K()
      f(&p) // works
      

      【讨论】:

      • "请注意,在您的示例中,您甚至不需要 inout,因为您没有覆盖参数"您错了。他试图更改作为参数传递的实例的属性。如果没有inout,如果这是一个结构参数,他就不能这样做。这就是为什么在我的回答中我将其设为class 协议——所以编译器知道这不是一个结构参数。
      • @matt inout 不关心参数是结构还是类,它关心的是常量与否。传入结构的工作方式完全相同。使用纯类协议只会消除改变结构的潜在问题。但这与inout 无关——你当然可以覆盖一个作为inout 传递的结构。
      • @matt 再次阅读您的评论后,我不得不同意您对inout 的必要性部分正确。所以我的答案的第一个小部分是不正确的,是的。幸运的是,它并没有改变剩下的论点和推理。
      【解决方案3】:

      带有协议支持的输入输出:

      protocol IPositional{
          func setPosition(position:CGPoint)
      }
      extension IPositional{
          var positional:IPositional {get{return self as IPositional}set{}}
      }
      class A:IPositional{
          var position:CGPoint = CGPoint()
          func setPosition(position:CGPoint){
              self.position = position
          }
      }
      func test(inout positional:IPositional){
          positional.setPosition(CGPointMake(10,10))
      }
      var a = A()
      test(&a.positional)
      a.position//(10.0, 10.0)
      

      结论:
      这样做的好处是:您现在可以为所有实现 IPositional 的类拥有一个“inout 方法”

      【讨论】:

        猜你喜欢
        • 2018-03-04
        • 1970-01-01
        • 1970-01-01
        • 2014-10-03
        • 2021-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多