В Swift, как вы можете вызвать функцию сразу после создания объекта

У меня есть некоторые объекты, structs, которые я инициализирую из словарей JSON ([String : Any]) с помощью функции init, предоставленной из расширения протокола Decodable (см. Инициируйте объект, соответствующий 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 из протокола 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 сразу после инициализации объекта.


person Tim Fuqua    schedule 13.06.2018    source источник


Ответы (1)


Я нашел способ сделать это, используя фабричный метод. Как я сказал в исходном вопросе, инициализатор, который я хочу использовать, предоставляется расширением протокола на Decodable (см. Init объекта, соответствующего Codable с словарь/массив). Я пошел дальше и добавил статическую функцию createFrom внутри расширения Decodable следующим образом:

extension Decodable {
    init(any: Any) throws {
        // https://stackoverflow.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 factory для создания экземпляра моего типа, используя init из Decodable, а затем делаю все, что захочу после этого. Таким образом, я контролирую, какие объекты нуждаются в специальной инициализации, а какие нет, но в момент создания объекта ничего не меняется. Вы по-прежнему используете ту же функцию init(any:).

person Tim Fuqua    schedule 13.06.2018