首页 > 解决方案 > 为什么我不能在 self 是一个类的协议扩展中更改变量?

问题描述

我很好奇为什么这不起作用:

public protocol MyProtocol {
    var i: Int { get set }
}

public protocol MyProtocol2: class, MyProtocol {}

public extension MyProtocol2 where Self: AnyObject {
    func a() {
        i = 0 <-- error
    }
}

错误:

无法分配给属性:“自我”是不可变的

为什么?只有类可以采用 MyProtocol2。如果我: class在 MyProtocol 后面添加声明,它就可以工作。我不明白为什么它不适用于子协议。

标签: swiftprotocols

解决方案


您的示例无法编译,因为MyProtocol它不是类绑定的,因此可以具有mutating要求和扩展成员。这包括属性设置器,默认情况下是mutating. 这些成员可以自由地为 重新分配一个全新的值self,这意味着编译器需要确保在可变变量上调用它们。

例如,考虑:

public protocol MyProtocol {
  init()
  var i: Int { get set } // implicitly `{ get mutating set }`
}

extension MyProtocol {
  var i: Int {
    get { return 0 }
    // implicitly `mutating set`
    set { self = type(of: self).init() } // assign a completely new instance to `self`.
  }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
  func a() {
    i = 0 // error: Cannot assign to property: 'self' is immutable
  }
}

final class C : MyProtocol2 {
  init() {}
}

let c = C()
c.a()

如果这是合法的,调用c.a()将重新分配一个全新的实例C给变量c。但是c是不可变的,因此代码格式不正确。

使MyProtocol类绑定(即protocol MyProtocol : AnyObject或不推荐的拼写protocol MyProtocol : class)有效,因为现在编译器知道只有类可以符合MyProtocol。因此,它通过禁止mutating需求和扩展成员来强加引用语义,从而防止self.

您可以使用的另一个选项是将需求的设置器标记i为存在nonmutating- 因此这意味着它只能由非变异设置器来满足。这使您的代码再次格式良好:

public protocol MyProtocol {
  init()
  var i: Int { get nonmutating set }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
  func a() {
    i = 0 // legal
  }
}

推荐阅读