iOS private/public data management and storage in Swift

2020-02-15 ios swift networking core-data

Is it possible to display as one array objects retrieved from network and same model but retrieved from core data. Purpose is to have same data possible to be public (then retrieved from network) or private and then this data is stored locally in coredata model. Attributes/Properties will be the same for both.

I plan to display this as swiftUI view (if that matters)

After some search I came with idea to have one struct that based on its privacy property will be translated to core data class model or if public directly connected to networking layer?

for example (some pseudo swift ;) )

struct Note {
   let note: String
   let isPrivate: Bool

   func save(self) {
       if self.isPrivate { save to CoreData }
       else { send save request with use of networking }
   }
}

class coreDataModel: NSManagedObject {
   var note: String
   let isPrivate = true
}

struct networkingModel {
   var note: String
   let isPrivate = false
}

class modelManager {
   func joinData() {
       let joinedModel: Note = coreDataModel + networkingModel 
    // and so on to display that model

   }
}

Answers

I think you can try this, first load the ui with the existing local data. At the same time, make a call to your api on a background queue. Once the api results are available, filter for duplicates, then persist it locally. Then the last step is to notify the ui to reload.

This pseudo-code is a bit UIKit specific, however the same logic can be applied to when in SwiftUI. You won't need closure, you will compute directly to your publisher object, and ui will react based any new emits.

/// you can have one model for both api and core data 
struct Note: Hashable { let uuid = UUID.init() }

class ModelManager {
    private var _items: Array<Note> = []

    var onChanged: ((Array<Note>) -> Void)? = nil
    // you might also have an init, where you can have a timer running
    func start() {
        loadFromCoreData { [weak self] (items) in
            guard let `self` = self else { return }
            self._items.append(contentsOf: items)
            self.onChanged?(self._items)
            self.loadFromApi()
        }
    }

    private func loadFromCoreData(completion: @escaping (Array<Note>) -> Void) {
        // background queue
        // logic to load from coredata.
        let coreDataResults: Array<Note> = []
        completion(coreDataResults)
    }

    private func loadFromApi() {
        // background queue
        let apiResults: Array<Note> = []
        compute(contents: apiResults)
    }

    private func compute(contents: Array<Note>) {
        let combined = zip(_items, contents).flatMap{[$0.0, $0.1] }
        let newItems = Array(Set(combined)) // set doesn't allow duplicates 
        // save to db

        // insert to new to _items
        _items.append(contentsOf: newItems)
        // sort maybe to place new items on top
        self.onChanged?(self._items)

    }

}



// usage of this object 
    let manager = ModelManager()
    manager.start()
    manager.onChanged = { items in
        // [weak self] remember of retain cycles
        // make sure you are on main queue when reloading

    }

Related