Упаковка базовых данных в кодируемые структуры - PullRequest
0 голосов
/ 17 ноября 2018

Я обертываю объекты Core Data в структуры, чтобы сделать их Codable.

[NB: Прежде чем вы направите меня к написанию файла swift для каждого класса Core Data, я бы хотел сказать, что перенос дочерних объектов NSManagedObject является результатом сознательного выбора в пользу удобства сопровождения кода, поскольку модель данных можетразвиваться в будущем.]

У меня есть несколько таких классов, вот пример:

struct CodableNeed : Codable {
    enum CodingKeys: String, CodingKey {
        ...
    }

    var need:Need

    init (_ need:Need) {
        self.need = need
    }

    init(from decoder: Decoder) throws {
        ....
    }

    func encode(to encoder: Encoder) throws {
        ....
    }
}

На самом деле это работает довольно хорошо, как любое обновление в init (from: decoder)) структуры фактически хранится в ManagedObjectContext.

Чтобы каждый экземпляр класса NSManagedObject мог возвращать свою собственную структуру, я определил протокол, в котором каждый экземпляр класса может возвращать свою собственную структуру Codable:

protocol CodableWhenWrapped {
    func wrapToCodable() -> Codable
}

extension Need : CodableWhenWrapped {

    func wrapToCodable() -> Codable {
        return CodableNeed(self)
    }
}

Затем я использую это в функции кодирования:

func jsonDataOfCodable<T:Encodable>(_ object:T) throws -> Data {
    let encoder = JSONEncoder()
    let data = try encoder.encode(object)
    return data
}

и вызываю эту функцию для генерации URLSessionUploadTask:

func updateTaskFor<T: NSManagedObject> (_ object:T, withSession session:URLSession) throws -> URLSessionUploadTask
    where T: CodableWhenWrapped
{
    let encoder = JSONEncoder() 

    // Here is the compile error:  
    // " Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)' "
    let jsonData = try jsonDataOfCodable(object.wrapToCodable())

    // then continue with generating the uploadTask        
    let url = "https://myurl.com/"
    let request = URLRequest(url: url)
    let updateTask = session.uploadTask(with: request, from: jsonData) { (data, response, error) in
        ....
    }
}

Вот проблема: кодне компилируется при вызове jsonDataOfCodable: Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)'.

Есть идеи, почему компилятору это не нравится?

Обратите внимание, что у меня возникает та же проблема, когда я задаю <T:Codable> вместо <T:Encodable> в прототипе jsonDataOfCodable.

1 Ответ

0 голосов
/ 17 ноября 2018

Я отвечаю на свой вопрос после размышления над комментарием vadian .

Я решил проблему, обновив свой протокол:

protocol CodableWhenWrapped : Encodable {
    func wrapToCodable<T>() -> CodableWrapper<T>
    func update(from decoder:Decoder) throws
}

Затем я добавил универсальную оболочку для своих объектов (все наследуются от класса Synchronizable):

struct CodableWrapper<T> : Codable
where T:CodableWhenWrapped, T: Synchronizable
{
    enum SynchronizableCodingKeys: String, CodingKey {
        case pk = "idOnServer"
        case lastModificationDate = "modified_on"
    }

    var object:T

    init(_ obj:T){
        self.object = obj
    }

    init(from decoder: Decoder) throws {
        // This is how the NSManagedObjectContext is passed through the decoder
        guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
            let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
            else {
                fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
            }

        // check if there is a an existing object with the same idOnServer. If not, create a new one        
        let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
        let pk = try container.decode(UUID.self, forKey: .pk)

        object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
        try object.update(from: decoder)
    }

    func encode(to encoder: Encoder) throws {
        try object.encode(to: encoder)
    }
}

Затем я могу иметь универсальную функцию для сериализации в JSON:

func jsonDataFor<T>(_ object:T) throws -> Data
    where T:CodableWhenWrapped, T:Synchronizable {
    let encoder = JSONEncoder()
    let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
    let jsonData = try encoder.encode(wrappedObject)
    return jsonData
}

Расшифровка просто:

let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)

Спасибо @vadian за вашу помощь, я надеюсь, что это полезно для других! Кстати, протокол CodableWhenWrapped можно легко использовать для других типов объектов.

...