В Swift, как вы можете вызвать функцию сразу после создания объекта
У меня есть несколько объектов, которые structs
, что я инициализирую из словарей JSON ([String : Any]
) через init
функция предоставляется из расширения на Decodable
протокол (см. Init объект, соответствующий Codable со словарем / массивом).
В общем, у меня есть объекты, которые выглядят так:
struct ObjectModelA: Codable {
var stringVal: String
var intVal: Int
var boolVal: Bool
// Coding keys omitted for brevity
}
struct ObjectModelB: Codable {
var doubleVal: Double
var arrayOfObjectModelAVal: [ObjectModelA]
// Coding keys omitted for brevity
var complicatedComputedVar: String {
// expensive operations using the values in arrayOfObjectModelAVal
}
}
ObjectModelB
содержит массив ObjectModelA
и он также имеет свойство, которое я действительно хочу установить, только если arrayOfObjectModelAVal
изменения.
Я могу использовать didSet
на arrayOfObjectModelAVal
, но это только улавливает будущие изменения в arrayOfObjectModelAVal
имущество. Проблема в том, что я использую веб-сервис для извлечения данных JSON для создания массива ObjectModelB ([[String : Any]]
), и я строю это так:
guard let objectDictArray = responseObject as? [[String : Any]] else { return }
let objects = objectDictArray.compactMap({ try? ObjectModelB(any: $0) })
Мои объекты создаются внутри compactMap
закрытие и init
не вызывает didSet
,
Я также не могу "переопределить" init, предоставленный init
от Decodable
протокол (тот, что в закрытии: try? ObjectModelB(any: $0)
) потому что мой объект struct
и это не наследование, это просто инициализатор, предоставляемый протоколом. В противном случае я бы "переопределил" init
в моем объекте, а затем просто сделать super.init
сопровождаемый некоторой мутирующей функцией, которая обновляет мое сложное свойство, и я делаю свое сложное свойство private(set)
,
Единственный другой вариант, о котором я могу подумать, - это создание той мутантной функции, которую я только что упомянул, и вызов ее в didSet
когда arrayOfObjectModelAVal
изменения, а затем обновите мой вызов инициализации объекта примерно так:
guard let objectDictArray = responseObject as? [[String : Any]] else { return }
let objects = objectDictArray
.compactMap({ try? ObjectModelB(any: $0) })
.forEach({ $0.updateProperties() })
Но сейчас updateProperties
может быть вызван в любое время любым пользователем (что плохо, потому что это действительно обременительно), и нет никакой гарантии, что его даже вызовут при создании массива объектов, потому что разработчик мог забыть сделать forEach
часть. Поэтому я хочу, чтобы способ автоматически вызывать updateProperties
Функция сразу после инициализации объекта.
1 ответ
Я нашел способ сделать это, используя фабричный метод. Как я уже говорил в исходном вопросе, инициализатор, который я хочу использовать, предоставляется расширением протокола на Decodable
(см. Init объект, соответствующий Codable со словарем / массивом). Я пошел дальше и добавил createFrom
статический функционал внутри Decodable
расширение как это:
extension Decodable {
init(any: Any) throws {
// https://stackru.com/questions/46327302
}
static func createFrom(any: Any) throws -> Self {
return try Self.init(any: any)
}
}
Теперь, если я определю init
на ObjectModelB
с той же сигнатурой функции, что и init
предусмотрено в Decodable
Расширение, вот так:
struct ObjectModelB: Codable {
var doubleVal: Double {
didSet {
computeComplicatedVar()
}
}
var arrayOfObjectModelAVal: [ObjectModelA] {
didSet {
computeComplicatedVar()
}
}
// Coding keys omitted for brevity
private(set) var complicatedVar: String = ""
mutating private func computeComplicatedVar() {
// complicated stuff here
}
init() {
doubleVal = 0.0
arrayOfObjectModelAVal = []
}
init(any: Any) throws {
self.init()
self = try ObjectModelB.createFrom(any: any)
computeComplicatedVar()
}
}
Это похоже на работу. Мне это нравится, потому что если я не добавлю init
который точно соответствует указанному в Decodable
расширение, то мой объект все еще может использовать тот, который указан в Decodable
расширение. Но если я предоставлю свой, я просто использую createFrom
фабричный метод для создания экземпляра моего типа с использованием init
от Decodable
и потом делай все, что захочу после этого. Таким образом, я контролирую, какие объекты требуют специальной инициализации, а какие нет, но в момент создания объекта ничего не меняется. Вы все еще используете то же самое init(any:)
функция.