I want to perform a background fetch and pass the result to closure. Currently I'm using performBackgroundTask method from NSPersistentContainer which is giving a NSManagedObjectContext as a closure. Then using that context I'm executing fetch request. When fetch is done I'm, passing the result to the completion handler.
func getAllWorkspacesAsync(completion: @escaping ([Workspace]) -> Void) {
CoreDataStack.shared.databaseContainer.performBackgroundTask { childContext in
let workspacesFetchRequest: NSFetchRequest<Workspace> = NSFetchRequest(entityName: "Workspace")
workspacesFetchRequest.predicate = NSPredicate(format: "remoteId == %@", "\(UserDefaults.lastSelectedWorkspaceId))")
do {
let workspaces: [Workspace] = try childContext.fetch(workspacesFetchRequest)
completion(workspaces)
} catch let error as NSError {
// Handle error
}
}
}
I'm going to call this method from ViewModel and use Combine PassthroughSubject to notify the ViewController about the event.
class WorkspaceViewModel {
private var cancellables: Set<AnyCancellable> = []
let resultPassthroughObject: PassthroughSubject<[Workspace], Error> = PassthroughSubject()
private let cacheManager = WorkspaceCacheProvider.shared
static public let shared: WorkspaceViewModel = {
let instance = WorkspaceViewModel()
return instance
}()
func fetchWorkspaces() {
cacheManager.getAllWorkspacesAsync { [weak self] workspaces in
guard let self = self else { return }
self.resultPassthroughObject.send(workspaces)
}
}
}
And the ViewController code:
class WorkspaceViewController: UIViewController {
private var cancellables: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
WorkspaceViewModel.shared.resultPassthroughObject
.receive(on: RunLoop.main)
.sink { _ in } receiveValue: { workspaces in
// update the UI
}
.store(in: &cancellables)
}
}
My question is: is it safe to pass NSManagedObject items?
No, it is not
You would want something like:
You will need to make corresponding changes to your publisher so that it publishes
[NSManagedObjectID].Once you have received this array in your view controller you will need to call
object(with:)on your view context to get theWorkspaceitself. This will perform a fetch in the view context for the object anyway.You may want to consider whether the time taken to fetch your workspaces warrants the use of a background context. By default Core Data objects are retrieved as faults and the actual attribute values are only fetched when the fault is triggered by accessing an object's attributes.
Alternatively, if the purpose of the view controller is to present a list of workspaces that the user can select from, you could return an array of tuples or structs containing the workspace name and the object id. However, this all sounds like pre-optimisation to me.