Можете ли вы “расширить” (т. е. добавить дополнительную логику инициализации) автоматически сгенерированный конструктор для кодируемого объекта?

#ios #swift #decoding #codable

Вопрос:

Когда вы реализуете Codable объект, компилятор может автоматически сгенерировать для вас конструктор. Однако это делается только в том случае, если вы сами не написали инициализатор, который принимает декодер.

Тем не менее, у нас есть объект с ~50 let свойствами, которые задаются декодером, но у нас также есть пять вычисленных свойств, которые основаны на этих let свойствах.

Технически, если бы мы могли вычислить их в инициализаторе, после того как декодер установит остальные 50, мы могли бы просто хранить результаты в let их собственных vars, полностью устраняя необходимость в вычисляемых свойствах.

Проблема, как уже упоминалось, заключается в том, что если вы реализуете свой собственный инициализатор, компилятор не создает его автоматически для вас, поэтому вам остается не только инициализировать свои «вычисленные» значения, но и все значения.

Итак, есть ли способ, которым вы можете вставить себя в процесс инициализации/декодирования без необходимости полностью переписывать инициализатор самостоятельно?

Ответ №1:

То, что вы ищете, похоже на шаблон делегата, в котором декодер сообщает своему делегату, что он закончил декодирование. К сожалению, это еще не добавлено в Swift. Самое близкое, что я могу придумать, — это использовать наследование, чтобы Swift мог автоматически генерировать декодер для тех 50 let в базовом классе, и вы можете инициализировать свои вычисленные свойства в подклассе. Например:

 class A: Decodable {
    let firstName: String
    let lastName: String
}

class B: A {
    private var _fullName: String! = nil
    var fullName: String { return _fullName }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        _fullName = firstName   " "   lastName
    }
}
 

Определите свои 50 свойств в классе A и оставьте все вычисленные свойства в классе B.


Или по вашему предложению вы также можете использовать lazy var :

 struct Model: Decodable {
    let firstName: String
    let lastName: String

    // private(set) so users cannot change value of the
    // pre-computed property
    lazy private(set) var fullName = self.firstName   " "   self.lastName
}

// But you can't use a let here, since calling fullName
// for the first time will mutate the struct
var model = try JSONDecoder().decode(Model.self, from: json)
 

Комментарии:

1. Я даже не думал об этом подходе! Довольно круто, хотя разве вам не нужно было бы также сделать класс V декодируемым?

2. Кроме того, я на самом деле совершенно забыл о lazy var возможности Swift, которая также позволяет вычислять значения только один раз, что было моей основной целью. Поскольку этот сайт посвящен тому, чтобы помогать другим, если вы добавите часть о lazy var себе в качестве альтернативы, чтобы ваш ответ показывал и то, и другое, я отмечу это как принятое. 🙂