Having to call fetch twice from CoreData - ios

Both on simulator and my real device, an array of strings is saved upon app termination. When I restart the app and fetchRequest for my persisted data (either from a viewDidLoad or a manual button action), I get an empty array on the first try. It isn't until the second time I fetchRequest that I finally get my data.
The funny thing is that there doesn't seem to be a time discrepancy involved in this issue. I tried setting various timeouts before trying to fetch the second time. It doesn't matter whether I wait 10 seconds to a minute -- or even immediately after the first fetch; the data is only fetched on the second try.
I'm having to use this code to fetch my data:
var results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
while (results.isEmpty) {
results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
}
return results
For my sanity's sake, here's a checklist:
I'm initializing the Core Data Stack using boilerplate code from Apple: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
I'm putting my single DataController instance in a static variable at the top of my class private static let context: NSManagedObjectContext = DataController().managedObjectContext
I'm successfully saving my context and can retrieve the items without any issue in a single session; but upon trying to fetch on the first try in a subsequent session, I get back an empty array (and there lies the issue).
Note** I forgot to mention that I'm building a framework. I am using CoreData with the framework's bundle identifier and using the model contained in the framework, so I want to avoid having to use logic outside of the framework (other than initalizing the framework in the appDelegate).

The Core Data stack should be initialized in applicationDidFinishLaunchingWithOptions located in appDelegate.swift because the psc is added after you're trying to fetch your data.

That boilerplate code from Apple includes:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
/* ... */
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
The saved data isn't available until the addPersistentStoreWithType call finishes, and that's happening asynchronously on a different queue. It'll finish at some point but your code above is executing before that happens. What you're seeing isn't surprising-- you're basically looping until the async call finishes.
You need to somehow delay your fetch until the persistent store has been loaded. There are a couple of possibilities:
Do something sort of like what you're already doing. I'd prefer to look at the persistent store coordinator's persistentStores property to see if any stores have been loaded rather than repeatedly trying to fetch.
Post a notification after the persistent store is loaded, and do your fetch when the notification happens.

Related

Firestore & SwiftUI - is my snapshot reader reading the db needlessly?

I have a project with firebase set up, and I have a function set up similarly to the sample from firebase, but the firebase document updates #Published vars in my observable object:
func getDataFromSession(env: GlobalEnvironmentObject) {
db.collection("sessions").document("firstSession")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(data)")
env.data1 = data[data1]
// env.data1 is the #Published bar data1
}
}
and I'm calling this function in my swiftui view :
var body: some View {
let fbConnection = FirebaseConnectionHandler(env: env)
fbConnection.getDataFromSession()
When I run my app, my debug window is executing the print("Current data: (data)") line at lightning speed despite the fact that no values in the observable object/global environment have change. I was under the impression that the snapshot listener will only perform a read when data changes, and the swiftui views will only update when #Published properties change and so fare neither are happening (so it shouldn't be reading like crazy). And I'm not sure if it's performing a ton of reads in a row or if that output is normal (obv I only want it to read when there is a change in my document)...
What the heck am I doing wrong? How do I prevent the mega reads and have it so the data only reads when it is updated?
Welcome to Stackoverflow. Relax, you and the SDK are doing it nothing wrong. The very first read you are experiencing is due to the initial state of your data.
See the doc View changes between snapshots :
https://firebase.google.com/docs/firestore/query-data/listen#view_changes_between_snapshots
Important: The first query snapshot contains added events for all
existing documents that match the query. This is because you're
getting a set of changes that bring your query snapshot current with
the initial state of the query. This allows you, for instance, to
directly populate your UI from the changes you receive in the first
query snapshot, without needing to add special logic for handling the
initial state.
The initial state can come from the server directly, or from a local
cache. If there is state available in a local cache, the query
snapshot will be initially populated with the cached data, then
updated with the server's data when the client has caught up with the
server's state.
Ok, so I figured it out...if you are updating observable objects from firebase, put the listener object in your view (under "var body: some View {")...
Then, take any of the views in your struct (HStack, ZStack, etc) and add a .onAppear modifier, and call the function the updates your firebase from there...
Turns out the view keeps updating, but .onAppear will kick off the refreshing once...
So, it turns out when I was writing my data to Firestore, all my data was being written to the same document. And, after searching the web, i learned that, when it comes to listeners in firestore, it is an all or nothing thing. So, my entire document was being called and updated, which triggered updates elsewhere, that would call and trigger, etc etc etc.
To fix, I took the document and split it into two documents, where one app can read to one but only write to the other, and the other app did vice versa (read to the other, write to the other). This way, the listener calls changes on a per document level and avoids the looping...

Can we generate Realm results in background queue and use it on main thread

I'm starting using Realm recently, I'm not sure if my use case is valid:
Normally, when reading a lot of data from DB, I want to put it in a background queue so it will async get the data and later use it on main thread.
For example, I want to fetch several results based on city:
private var results: [Results<SomeObject>?] = []
autoreleasepool {
DispatchQueue(label: "background").async {
[unowned self] in
do
{
let realm = try Realm()
for i in 1...City.count
{
self.results.append(realm.objects(SomeObject.self).filter("city=\(i)"))
}
}
catch
{
NSLog("Failed to open Realm instance on background qeueue")
}
}
}
And later use results to update my chart:
cell.setChartData(ChartDataFactory.createCombinedData(from: results[0]))
However if I apply this model for Realm, I'm getting error like
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.
I understand I must use realm for each thread, and I can do this by reading realm on main thread, but I don't want the realm query block my main thread.
is there any way I can achieve my goal? e.g. reading realm in a background queue and access the results from another thread, while keeping the auto-refresh feature.
Thanks.
Realm has built-in functionality for running a query on a background thread and delivering the results to the main thread by using Results.observe().
If you specifically need to perform expensive filtering logic that can't be expressed as a Realm query, you can manually pass an array of objects between threads using ThreadSafeReference.
As of 5.0, you can now construct the query on a background thread and receive notifications on the main thread using the on: parameter to observe():
DispatchQueue.global().async {
let realm = try! Realm()
let results = realm.objects(ObjectType.self).filter("property in %#", expensiveFunction(realm))
self.token = results.observe(on: .main) { change in
// do stuff with the results on the main thread
}
}
Realm objects are only accessible through the realm from which they are fetched or created. Realm instances cannot be shared between threads (which you are aware of), and sharing an object from a specific realm instance to another thread, implicitly has the same effects as sharing a realm instance between threads. This is due to the tight coupling between the objects and the realm instance.
As mentioned in this GitHub issue https://github.com/realm/realm-cocoa/issues/946, the recommended practice is to share the primary keys (if your realm object overrides the primaryKey method of RealmObject (Objective-C) / Object (Swift)).
You're trying to directly access 'results' property from a different queue and that will crash. You should instead use ThreadSafeReference as indicated on the answer of Thomas.
Make sure to create a ThreadSafeReference for results and call realm.resolve() on your background queue before fetching from your Realm database.
I solved it like this. I see an overall performance improvement, but I couldn't find any implementation example for querying on a background thread. Might be there is a solution with even better performance.
self.results = self.realm.objects(Object.self).filter(predicate).sorted(by: sortProperties)
self.notificationToken = self.results.observe({ (notification) in
self.tableview.reloadData()
})
This is the results on an iPhone X with a database of ~171k items. Durations are in seconds.
Search on UI tread:
UI thread blocked 0.730504035949707
Search with the code from above:
UI thread blocked 0.28138411045074463
background search duration 0.5073530673980713

Best practice manipulating Core Data Context on UI interaction while sync in background at the same time

I've already done research in lots of articles and discussions about how to use NSManagedObjectContext, but still could't find a satisfying architecture for my project.
In my app, there are three sources where the data might be modified from, ordered by their priorities when there is conflict simultaneously (ex. the cloud has the least priority):
User interface,
BLE message,
HTTP response from the cloud
Since I'm still not an expert on iOS development, I tried to avoid using multiple context for each source. However, after weeks of trial and error, I am reluctant but start to think if I really need to go for multi-context approach.
At the very beginning, I tried to use context.perform { } on the main context to do all the data change operations (add/update/delete, except fetch). I kept fetch to be a sync function because I want the data fetch to be instant so that can responsive with the UI. However, under this approach, I occasionally receive "Collection <__NSCFSet: 0x000000000> was mutated while being enumerated" exception (which I suppose might happen in the forEach or map function for batch data processing). I also found that this approach still block the UI when there are bunch of records to update in the background queue.
Therefore, I created a background context and use parent-child model to manipulate data. Basically the main context(parent) is only responsible for fetching data, while the background context(child) manipulates all the data change (add/update/delete) via backgroundContext.perform { }. This approach solves the UI block problem, however the collection mutated error still happen occasionally, and another issue occurs under this structure: for instance, the app would crash when I add a data record in ViewController A, and move to View Controller B, which fetch the same data immediately even if the background context haven't finished adding the data record.
Therefore I would like to ask some suggestions for the Core Data usage on my project. Is there something I did wrong under my parent-child data context model? Or, should I inevitably go for a real multi-context model without parent-child? How to do it?
My main context(parent) and background context(child) are initiated like this:
lazy var _context: NSManagedObjectContext = {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}()
lazy var _backgroundContext: NSManagedObjectContext = {
let ctx = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
ctx.parent = self._context
return ctx
}()
And the merge function is as follow:
#objc func contextDidSaveContext(notification: NSNotification) {
let sender = notification.object as! NSManagedObjectContext
if sender === self._context {
NSLog("******** Saved main Context in this thread")
self._backgroundContext.perform {
self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
}
} else if sender === self._backgroundContext {
NSLog("******** Saved background Context in this thread")
self._context.perform {
self._context.mergeChanges(fromContextDidSave: notification as Notification)
}
}
else {
NSLog("******** Saved Context in other thread")
self._context.perform {
self._context.mergeChanges(fromContextDidSave: notification as Notification)
}
self._backgroundContext.perform {
self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
}
Any advice of the core data structure would be appreciated.
Have you explored to use NSFetchedResultsController to fetch the data?
NSFetchedResultsController provides a thread safe way to observe for changes in the data due to Create, update or delete operations in CoreData.
Use NSFetchedResultsController's delegate methods to update the UI.
Ideally, controllerDidChangeContent delegate would give you an indication to update the UI. Documentation for reference

Swift Realm Write method is Sync or Async Thread

I am using Realm in My project, and I want to know whether the realm.write() method is synchronous or not.
My example is here:
let realm = try! Realm()
try! realm.write {
realm.delete(message)
}
realm.invalidate()
In the above example, I am deleting a realm object and outside braces I am writing invalidate()
Here is my confusion:
If write() is synchronous, then invalidate() is ok
And if Async than before write invalidate will call, and realm will release but operation is running in background
Thanks
Realm.write is synchronous. It just calls realm.beginWrite()/realm.commitWrite() with some error handling:
public func write(_ block: (() throws -> Void)) throws {
beginWrite()
do {
try block()
} catch let error {
if isInWriteTransaction { cancelWrite() }
throw error
}
if isInWriteTransaction { try commitWrite() }
}
The method you write is synchronous method as you did not specify the background queue for it.
Purpose of Invalidate() method
func invalidate()
Description
Invalidates all Objects, Results, LinkingObjects, and Lists managed by the Realm.
A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need.
All Object, Results and List instances obtained from this Realm instance on the current thread are invalidated. Objects and Arrays cannot be used. Results will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm.
Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. This method may not be called on a read-only Realm.

Multi-threaded progress bar concurrency issue outside the main view controller

I've found so many solutions for progress bar update within the same thread and view controller, however they seemed to be not similar cases as mine.
In my application, the main view controller calls loadIntoCoreData()(implemented in class MyLoadingService) which asynchronously loads data into core data by another thread. This function has to continuously update the loading percentage (which is written in NSUserDefaults.standardUserDefaults()) to the main thread so that it could be shown on the progress bar in main view controller. I had ever used a while loop in MainViewController to continuously fetch the current percentage value, like below:
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData() { result in
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "isLoading")
// do something to update the view
}
self.performSelectorInBackground("updateLoadingProgress", withObject: nil)
}
func updatingLoadingProgress() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setBool(true, forKey: "isLoading")
// here I use a while loop to listen to the progress value
while(prefs.boolForKey("isLoading")) {
// update progress bar on main thread
self.performSelectorOnMainThread("showLoadingProcess", withObject: nil, waitUntilDone: true)
}
prefs.setValue(Float(0), forKey: "loadingProcess")
}
func showLoadingProcess() {
let prefs = NSUserDefaults.standardUserDefaults()
if let percentage = prefs.valueForKey("loadingProcess") {
self.progressView.setProgress(percentage.floatValue, animated: true)
}
}
}
And in the class of function loadIntoCoreData:
class MyLoadingService {
let context = (UIApplication.sharedApplication()delegate as! AppDelegate).managedObjectContext!
func loadIntoCoreData(source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
counter++
}
}
}
The above code can successfully run the progress bar, however it often encounter BAD_ACCESS or some other exceptions(like "Cannot update object that was never inserted") due to the conflicts on core data context (thought it seems that managedObjectContext isn't touched by the main thread). Therefore, instead of using a while loop listening on the main thread, I consider using NSOperationQueue.performSelectorOnMainThread to acknowledge the main thread after each entry. Therefore I put my view controller as an argument sender into loadCoreData and call performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true) but failed with error "unrecognized selector sent to class 'XXXXXXXX'". So I would like to ask if is it possible to update an UI object between threads? Or, how to modify my previous solution so that the core data context conflicts could be solved? Any solutions are appreciated.
class MyLoadingService {
func loadIntoCoreData(sender: MainViewController, source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
NSOperationQueue.performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true)
counter++
}
}
func updateProgressBar(sender: MainViewController) {
sender.progressView.setProgress(percentage, animated: true)
}
}
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData(self) { result in
// do something to update the view
}
}
}
First, you are abusing NSUserDefaults in horrible ways. The documentation describes it as this...
The NSUserDefaults class provides a programmatic interface for
interacting with the defaults system. The defaults system allows an
application to customize its behavior to match a user’s preferences.
For example, you can allow users to determine what units of
measurement your application displays or how often documents are
automatically saved. Applications record such preferences by assigning
values to a set of parameters in a user’s defaults database. The
parameters are referred to as defaults since they’re commonly used to
determine an application’s default state at startup or the way it acts
by default.
You are using it to store a global variable.
Furthermore, you are completely abusing the user's CPU in your loop where you continuously are checking the value in the user defaults, and clipping off a selector to the main thread. "Abuse of the CPU" doesn't even come close to describing what this code is doing.
You should use NSProgress for reporting progress. There is a WWDC 2015 presentation dedicated exclusively to using NSProgress.
On to your core data usage.
Unfortunately, since you intentionally redacted all of the core data code, it's impossible to say what is going wrong.
However, based on what I see, you are probably trying to use that managed object context from your app delegate (which is probably still created with the deprecated confinement policy) from a background thread, which is a cardinal sin of the highest order as far as core data is concerned.
If you want to import data as a long running operation, use a private context, and execute the operations in the background. Use NSProgress to communicate progress to anyone wanting to listen.
EDIT
Thanks for the advice on my core data context usage. I digged into all
the contexts in my code and re-organized the contexts inside, the
conflict problem does not happen anymore. As for NSProgress , it's a
pity that the WWDC presentation focus on the feature on iOS 9 (while
my app must compact on iOS 8 devices). However, even though I use
NSProgress, I should still tell the main thread how many data the core
data (on another thread) already has, right? How does the thread on
NSProgress know the loading progress on my core data thread? –
whitney13625
You can still use NSProgress for iOS8, then only real difference is that you can't explicitly add children, but the implicit way still works, and that video explains it as well.
You really should watch the whole video and forget about the iOS9 part, except to know that you must add children implicitly instead of explicitly.
Also, this pre-iOS9 blog post should clear up any questions you have about it.

Resources