为何Swift中遵循协议的实例默认被当作值类型处理?
为什么遵循协议的类实例会表现得像值类型?
这其实是Swift里**协议存在容器(Protocol Existential Container)**的特性搞的鬼,咱们一步步给你掰明白:
核心原因:协议存在类型的“值语义默认行为”
你的代码里,controller.sampleValue的类型是SampleProtocol!——这不是具体的SampleObject类类型,而是一种协议存在类型。当你把类实例赋值给这种变量时,Swift会把实例包装进一个“协议容器”里,这个容器的默认行为是遵循值语义,不管底层实例是类(引用类型)还是结构体(值类型)。
当你执行controller.sampleValue.message = "New message"时,Swift的处理逻辑是这样的:
- 先从协议容器里拷贝出底层实例的“副本”(哪怕是类引用,也会把容器连同引用一起拷贝一份);
- 修改这个副本的
message属性; - 把修改后的副本重新赋值回
sampleValue变量。
这一连串操作就触发了sampleValue的didSet监听,看起来就跟修改值类型属性时的行为一模一样。
验证:用具体类类型替代协议类型
如果把sampleValue的类型改成具体的SampleObject!,你会发现行为立刻变回类的引用类型特性:
final class Controller { var sampleValue: SampleObject! { // 改成具体类类型 didSet { print("New value has been set") } } } let controller = Controller() controller.sampleValue = SampleObject() // 正常打印一次 controller.sampleValue.message = "New message" // 不会再打印,符合类的引用行为
解决方案:给协议添加AnyObject约束
如果你想让协议类型的变量也保留类的引用语义,可以给协议加上AnyObject约束,明确要求只有类才能遵循这个协议:
protocol SampleProtocol: AnyObject { var message: String? { get set } }
这样Swift就知道协议容器里的实例一定是引用类型,修改属性时就不会执行“拷贝-修改-赋值”的流程,自然也就不会触发didSet了。
内容的提问来源于stack exchange,提问作者nikhiltammanache
相关产品推荐
相关产品推荐

