Swift类绑定协议细化场景下属性赋值编译错误原因及实现方法
问题原因
Swift 中协议属性的赋值语义是在协议声明属性时就确定的,和后续子协议是否追加类绑定约束无关:
- 第一个案例中,属性
i是声明在非类绑定的协议P里的,默认的{ get set }语义是mutating set:对于值类型的遵循者来说,修改属性会触发整个值实例的变更,因此要求承载实例的变量必须是用var声明的可写变量。虽然你后续给P扩展了类绑定的子协议ClassP,但编译器处理i的赋值时仍然会沿用P中声明的mutating set语义,因此会判定let声明的classP不可修改。 - 第二个案例中,属性
i声明时的宿主协议P本身就是类绑定的,类绑定协议的所有set默认都是nonmutating语义:修改属性只会修改实例的内存内容,不会变更变量本身的指向,因此不需要变量是var,let声明的常量也可以正常修改属性。
解决方案
你要实现「P 不做类绑定、但类绑定的子协议 ClassP 的 let 实例可以修改属性」的需求,有两种常用方案:
方案1:在类绑定子协议中重新声明属性
在 ClassP 中重新声明一次 i 的属性要求,此时属性的宿主是类绑定的 ClassP,会自动采用 nonmutating set 语义:
protocol P { var i: Int { get set } } protocol ClassP: P, AnyObject { var i: Int { get set } } // 追加属性声明 class C: ClassP { var i: Int = 0 } let classP: ClassP = C() classP.i = 1 // 编译正常
这种方案不需要修改顶层协议 P 的定义,值类型遵循 P 时的行为也不会受到影响,是最推荐的实现方式。
方案2:在顶层协议中声明 nonmutating set
如果你允许所有遵循 P 的类型(包括值类型)的 let 实例都可以修改 i,可以直接在 P 的属性声明中指定 set 为非变异:
protocol P { var i: Int { get nonmutating set } } protocol ClassP: P, AnyObject {} class C: ClassP { var i: Int = 0 } let classP: ClassP = C() classP.i = 1 // 编译正常
注意这种方案下,值类型实现 P 的 i 时必须使用 nonmutating set,一般只能通过将属性存储在外部(比如静态存储、全局存储或者闭包捕获)实现,适合特定的业务场景。
内容的提问来源于stack exchange,提问作者imre
相关产品推荐
相关产品推荐

