В 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:) функция.

Другие вопросы по тегам