在方便的初始值设定项中使用 `self =` 委托给 Swift 中的 JSONDecoder 或工厂方法以避免 `Cannot assign to value: 'self' is immutable`

Use `self =` in convenience initializers to delegate to JSONDecoder or factory methods in Swift to avoid `Cannot assign to value: 'self' is immutable`

有时在 Swift 中,为委托给 JSONDecoder 或工厂方法的 class 编写初始化程序可能会很方便。例如,一个人可能想写

final class Test: Codable {
    let foo: Int
    
    init(foo: Int) {
        self.foo = foo
    }
    
    func jsonData() throws -> Data {
        try JSONEncoder().encode(self)
    }
    
    convenience init(fromJSON data: Data) throws {
        self = try JSONDecoder().decode(Self.self, from: data)
    }
}

let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)

但是编译失败

Cannot assign to value: 'self' is immutable.

解决此问题的解决方案是什么?

首先,请注意此限制仅适用于 classes,因此示例初始化程序将按原样适用于 structs 和 enums,但并非所有情况允许将 class 更改为这些类型之一。

class 初始化程序的这种限制是一个常见的痛点,经常出现在这个网站上(). There is a thread 在 Swift 论坛上讨论这个问题,并且工作已经开始添加必要的语言功能以使上面的示例代码可以编译,但截至 Swift 5.4,这还不完整。 来自线程:

Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.

使用这个想法修复示例代码产量

final class Test: Codable {
    let foo: Int
    
    init(foo: Int) {
        self.foo = foo
    }
    
    func jsonData() throws -> Data {
        try JSONEncoder().encode(self)
    }
}

protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
    init(fromJSON data: Data) throws {
        self = try JSONDecoder().decode(Self.self, from: data)
    }
}

let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)

效果很好。如果 Test 是唯一符合 TestProtocol 的类型,那么只有 Test 会得到这个初始化器。

另一种方法是简单地扩展 Decodable 或您的 class 符合的其他协议,但如果您不希望符合该协议的其他类型也获得您的初始化器,这可能是不可取的.