You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Swift中遵循协议的实例默认被当作值类型处理?

为什么遵循协议的类实例会表现得像值类型?

这其实是Swift里**协议存在容器(Protocol Existential Container)**的特性搞的鬼,咱们一步步给你掰明白:

核心原因:协议存在类型的“值语义默认行为”

你的代码里,controller.sampleValue的类型是SampleProtocol!——这不是具体的SampleObject类类型,而是一种协议存在类型。当你把类实例赋值给这种变量时,Swift会把实例包装进一个“协议容器”里,这个容器的默认行为是遵循值语义,不管底层实例是类(引用类型)还是结构体(值类型)。

当你执行controller.sampleValue.message = "New message"时,Swift的处理逻辑是这样的:

  1. 先从协议容器里拷贝出底层实例的“副本”(哪怕是类引用,也会把容器连同引用一起拷贝一份);
  2. 修改这个副本的message属性;
  3. 把修改后的副本重新赋值回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 10:27:39