Swift中能否通过KeyPath/WritableKeyPath在初始化方法中设置属性?
问题原因
第一类初始化未完成错误
Swift采用双阶段初始化安全规则,要求初始化第一阶段必须为所有存储属性完成赋值,之后才能访问self的任何成员、调用self的任何方法。通过self[keyPath:]赋值的本质是调用self的下标方法,属于访问self的行为,在所有存储属性未完成赋值前触发该操作,就会抛出'self' used before all stored properties are initialized错误。
第二类KeyPath只读错误
let声明的存储属性只允许在初始化阶段直接赋值,不允许初始化完成后修改,因此let属性对应的KeyPath为只读的KeyPath类型,而非支持修改的WritableKeyPath,自然无法通过KeyPath下标赋值,会抛出Cannot assign through subscript: key path is read-only错误。
可行解决方案
- 给属性设置默认值
如果属性可声明为var且存在合理默认值,直接给属性赋默认值即可,此时进入初始化方法时所有属性已完成初始化,可正常使用KeyPath赋值:
struct SomeStruct { var number: Int = 0 init(viaKeyPathTo number: Int) { self[keyPath: \.number] = number // 运行正常 } }
- 先完成初始化再修改
如果不想对外暴露属性默认值,可以在初始化方法内部先构造临时实例,用KeyPath修改完成后再赋值给self:
struct SomeStruct { var number: Int init(viaKeyPathTo number: Int) { var temp = SomeStruct(number: 0) temp[keyPath: \.number] = number self = temp } }
- 不安全内存操作(不推荐生产环境使用)
如果必须给let属性通过KeyPath赋值,可以使用不安全内存操作绕过Swift的安全检查,但会破坏类型安全,存在极高的稳定性风险:
struct SomeStruct { let number: Int init(viaKeyPathTo number: Int) { self = SomeStruct(number: 0) withUnsafeMutableBytes(of: &self) { buffer in buffer.storeBytes(of: number, toByteOffset: MemoryLayout<Self>.offset(of: \.number)!, as: Int.self) } } }
Swift Evolution相关进展
该需求已经在Swift Evolution社区被多次讨论,核心思路是新增专门适配初始化阶段的InitializableKeyPath类型,在不破坏现有双阶段初始化安全规则的前提下,支持通过KeyPath完成初始化赋值。截至目前还没有正式通过的相关提案,讨论仍处于草案阶段。
内容的提问来源于stack exchange,提问作者pkamb
相关产品推荐
相关产品推荐

