Why can't I change variables in a protocol extension where self is a class?

天大地大妈咪最大 提交于 2019-12-21 09:14:19

问题


I am curious why this doesn't work:

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

public protocol MyProtocol2: class, MyProtocol {}

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

Error:

Cannot assign to property: 'self' is immutable

Why? Only classes can adopt MyProtocol2. If I add : class declaration behind MyProtocol it works. I do not understand why it doesn't work on a subprotocol.


回答1:


Your example doesn't compile because MyProtocol isn't class-bound, and as such can have mutating requirements and extension members. This includes property setters, which are by default mutating. Such members are free to re-assign a completely new value to self, meaning that the compiler needs to ensure they're called on mutable variables.

For example, consider:

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

If this were legal, calling c.a() would re-assign a completely new instance of C to the variable c. But c is immutable, therefore the code is not well formed.

Making MyProtocol class bound (i.e protocol MyProtocol : AnyObject or the deprecated spelling protocol MyProtocol : class) works because now the compiler knows that only classes can conform to MyProtocol. Therefore it imposes reference semantics by forbidding mutating requirements and extension members and therefore prevents any mutations of self.

Another option at your disposal is to mark the setter for the requirement i as being nonmutating – therefore meaning that it can only be satisfied by a non-mutating setter. This makes your code once again well-formed:

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



回答2:


Property setters trigger mutation on the callee, which means that any method that operates on the property needs to be declared as mutating:

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

This will allow any writes on self within the method.



来源:https://stackoverflow.com/questions/51678708/why-cant-i-change-variables-in-a-protocol-extension-where-self-is-a-class

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!