NSFetchedResultController not calling controllerDidChangeContent in delegate - ios

There are many very similar looking questions on StackOverflow but I am asking this because almost every existing question is caused by not calling performFetch on the FRC.
In this case we are calling it.
fetchedResultsController = NSFetchedResultsController(fetchRequest: StepRecord.fetchRequest(forDate: date),
managedObjectContext: theMainThreadContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch (let error) {
print(error)
}
Then in a later function, we have something like this...
func updateScreen() {
if fetchedResultsController.fetchedObjects.count == 0 {
// download data and store into core data
}
// update the screen
}
And we have the delegate method...
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
updateScreen()
}
The place that is writing into core data is definitely writing into a backgroundThreadContext (this is a pattern we have used several times in the app and it is working elsewhere).
However, the delegate method is not getting called in this case.
If we exit the screen and go back in the updateScreen method runs and the FRC DOES have data. So the fetch request is correct, the download is putting records in the correct place and saving properly and the update screen method is able to get those items and populate the screen.
The only problem we are having here is that the delegate method is not being called.
Is there something we have missed here? Like I said, we have used this same pattern in a few places and it works. It's just in this case that it isn't working.
Let me know if there is any other code you would like to see and I'll pass it along if I can.

TL:DR - Objects that don't exist, won't listen for NSNotifications.
OK, after some serious debugging we finally found the problem that was causing this and it's a doozy.
So, the code in my question existed inside a class that was essentially a "worker" for a factory class.
This worker was used by the factory to create an object and return it to the owner of the factory (the "consumer" if you like).
Consumer - view controller
- Factory - strongly referenced
- Worker - function variable
- FetchedResultsController stuff - strongly referenced
While we were keeping a strong reference to the Factory, the factory didn't actually store a reference to the worker outside of the function it was used in.
This means that by the time the download completed and saved stuff into CoreData, the Fetched Results Controller and the "worker" didn't actually exist in memory anymore.
So, the easy fix was to add a stored reference to the worker.
The longer fix is to refactor some code I think, but that's a job for another day.
Thanks for all the help. It definitely helped us root out the problem.

What is happening is that you are using a different NSManagedObjectContext for saving the downloaded data. That context uses a background queue as you say. But perhaps that context is not a child of the context of the FRC. In that case, you need to merge into the main context listening the notification of the other changing.
But the easy way to fix it is using a background context that is a child of the main context. That is, create a new context of type privateQueue and set its parentContext equal to the current context.

Related

Best way to avoid singleton

For our iOS programming class we must make a framework for Swift iOS. We had the idea of a framework simplifying CoreData manipulation. I began by creating a class where you put the NSManagedObjectContext created in AppDelegate at the beginning, so you don't have to write this long (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext anymore.
open class SimpleCoreData {
var context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
func delete(entity: NSManagedObject) /*-> Bool*/ {
// some code
}
func getAll(entityClass: NSManagedObject.Type) throws -> [NSManagedObject]? {
// some code
}
func create(entityDescr: NSManagedObject.Type) -> NSManagedObject? {
// some code
}
But I would like it to be accessible from everywhere in the application, and this simplification would be useless if you have to instantiate this each time.
I was first thinking about a singleton, but I recently learned it wasn't a good practice.
So do you know any solution to make this accessible from everywhere in the client app? Or maybe singleton is okay in this case?
Keeping Rob Napier's excellent comments in mind, if you decide to avoid a singleton in this case, a common approach would be
Create an instance of your SimpleCoreData class in the app delegate when the app launches.
Have the app delegate pass this to your initial view controller. That view controller would have a SimpleCoreData property but would not create the instance-- it would expect one to be assigned by whichever code creates it, which here is the app delegate.
Repeat this pattern everywhere you need a SimpleCoreData. That is, when you create an object that needs a SimpleCoreData, make sure it has a property with that type, and assign a value when you create it. For view controllers, a good place to do this is in prepare(for:sender:), if you're using segues.
It's not necessary to create the SimpleCoreData in the app delegate, though. You could create it at the first point in the app hierarchy where it's needed, and pass it along from there. So if it's only needed in the second view controller in the hierarchy and in other objects loaded from there, create it in that view controller.
This doesn't make your SimpleCoreData instance available everywhere automatically, it means that you're creating one and then passing it around. Often that works fine, but as Rob notes it's not always the best approach. It can lead to passing an object along to an object that doesn't need it, because some other object that gets created later on does. For example if you need SimpleCoreData in your initial view controller but then not again until five levels down the hierarchy, you still need to pass it along every step of the way. That's an example of when a shared instance can be useful. It's not a singleton since other instances are allowed, but it's a default instance that can be used as needed.
I finally learned thanks to you that singletons aren't so evil, they can be used in some case, including this one, and it seemed in my case that it was a good choice. Maybe I will change it for a shared instance pattern.
So the singleton works well. Thank you everybody for your advices, I learned a lot in design patterns.

Universal context for CoreData

I use NSPersistentContainer as a dependency in my classes. I find this approach quite useful, but there is a dilemma: I don't know in which thread my methods will be called. I found a very simple solution for this
extension NSPersistentContainer {
func getContext() -> NSManagedObjectContext {
if Thread.isMainThread {
return viewContext
} else {
return newBackgroundContext()
}
}
}
Looks wonderful but I still have a doubt is there any pitfalls? If it properly works, why on earth Core Data confuses us with its contexts?
It's OK as long as you can live with its inherent limitations, i.e.
When you're on the main queue, you always want the viewContext, never any other one.
When you're not on the main queue, you always want to create a new, independent context.
Some drawbacks that come to mind:
If you call a method that has an async completion handler, that handler might be called on a different queue. If you use this method, you might get a different context than when you made the call. Is that OK? It depends what you're doing in the handler.
Changes on one background context are not automatically available in other background contexts, so you run the risk of having multiple contexts with conflicting changes.
The method suggests a potential carelessness about which context you're using. It's important to be aware of which context you're using, because managed objects fetched on one can't be used with another. If your code just says, hey give me some context, but doesn't track the contexts properly, you increase the chance of crashes from accidentally crossing contexts.
If your non-main-queue requirements match the above, you're probably better off using the performBackgroundTask(_:) method on NSPersistentContainer. You're not adding anything to that method here.
[W]hy on earth Core Data confuses us with its contexts?
Managed object contexts are a fundamental part of how Core Data works. Keeping track of them is therefore a fundamental part of having an app that doesn't corrupt its data or crash.

MagicalRecord with NSOperation causing persistence issues

I'm using MagicalRecord to manage my core data. My app often receives chunks of data that needs iterated over then individually added to the store as records, and saved. I put this code into an NSOperation. It looks something like this:
class AddIncomingMessageOperation: NSOperation {
override func main() {
let context = NSManagedObjectContext.MR_context()
self.message = Message.createMessage(
json: self.messageJSON,
context: context)
if self.message != nil {
context.MR_saveToPersistentStoreAndWait()
}
}
}
Running this on NSOperationQueue.mainQueue seems to work without issue, other than holding up the UI. But my next move is to run all of these operations on their own background operation queue. Adding them to this NSOperationQueue and running them then results in some mixed up data.
What I mean by this - my createMessage function checks for an existing Conversation object, and adds it to the conversation if it exists, or creates one if it doesn't. So it requires knowing what already exists in the store. What seems to be happening how, with them running on a background queue, is that they're creating conversations that have just been created in another operation.
I can solve all of this by setting operationQueue.maxConcurrentOperationCount = 1, but that obviously slows the whole thing down.
You cannot use the main thread context on a background thread. Not in Core Data and not in Magical Record.
Use the MR methods designed for background operations, such as saveWithBlock. Create background contexts with MR_newContext.
If you make use of these simple APIs, you might be able to dispense with the cumbersome NSOperation subclasses.

How to create/manage multiple ManagedObjectContexts?

I have a problem, and I'm pretty confident that I know in a broad sense what it is and how to fix it, but I'm not sure and haven't found what the clean / best practice way to implement the solution is.
My problem: I am loading some data from a file into my Core Data Model in a background thread using dispatch_async, which works fine except when I do things in the GUI that also affect the model and then bang, for example:
'NSGenericException', reason: '*** Collection ... was mutated while being enumerated.'
... which I assume is due to two threads messing with data in the same ManagedObjectContext, because I am only using one at the moment.
All the articles and answers I've read tell me that I should use a separate ManagedObjectContext for the background thread, but how/where to set it up?
I currently create my PersistentStoreCoordinator and (one) ManagedObjectContext in my App Delegate, and pass the ManagedObjectContext to my (only) View Coordinator. It in turn passes it to the background data load task (which is a class method of one of my model classes), thus causing the problem.
Should I
pass the PersistentStoreCoordinator to the View Controller, so that it in turn can pass it to the background task, so that the background task can create its own local ManagedObjectContext?
create a pool of ManagedObjectContexts in the App Delegate and pass all of them to the View Controller, so that it can use one itself and give others out to background tasks?
something else entirely?
I still haven't been able to consistently reproduce the problem; it seems to be highly timing-dependent. But here is what I have done to try to prevent it.
In the View Controller:
- (void) loadNewStuff: (NSString *)stuffID
{
dispatch_async(taskQueue,
^(void){[MyModelClass loadNewStuff: stuffID withContext: myContext];}
);
}
In the model class:
+ (void) loadNewStuff: (NSString *)stuffID withContext: (NSManagedObjectContext *)passedContext
{
NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
[localContext setPersistentStoreCoordinator: passedContext.persistentStoreCoordinator];
// load new stuff, save local context, finished
}
... is this ok, or am I overlooking something that is going to bite me horribly?
(I'm using ARC, so I assume not explicitly releasing the locally-created context is ok?)

iOS - Core data - completion handler

Overview
I have a iOS project that uses core data
The core data is used by view controllers as well as for notifications
Implementation
Created a singleton class for database activities called DatabaseEngine
In the appDelegate didFinishLaunchingWithOptions, DatabaseEngine is instantiated
DatabaseEngine contains properties (delegate) for the view controller and for notifications
In the viewDidLoad of the view controller I am setting the DatabaseEngine delegate to the view controller instance
Once the database is opened, the completion handler (through the delegate properties) calls the methods to setup the view controller and notifications
Concern (Timing issue)
I am concerned there might be scenario (a timing issue), where the DatabaseEngine is created first and at that moment the view controller's viewDidLoad would not be executed, and therefore the DatabaseEngine delegate would not initialized, therefore the database would execute the completionHandler but since the delegate is nil, no tasks would be done
What I have done to address the concern
Inside the view controller's viewDidLoad, I am checking if the Database is up and if the view controller is not loaded, if yes then i execute the tasks (setting up the views of the view controller) again.
Note- I am NOT using threads explicitly but based on my understanding completionHandler is executed asynchronously.
Question
I have tried it several times, and the view controller data is loaded correctly and there seems to be no timing issue. I even tried looping through a large value(to create a delay) and still there is no timing issue. I wonder why ?
Is my implementation a good design or is there a better way to do this ?
Is that the correct way to address my concern ?
Your design is a bit convoluted, but seems solid. (I prefer to have core data managed by the app delegate, but your approach is just as fine if you prefer it.)
I would, however, use the usual pattern of lazy initialization of your DatabaseEngine class. In this way, when it is needed and really does not exist, it will create itself and do the necessary initialization routines while the view controller will wait until the call to the engine returns something.
// in view controller viewDidLoad, e.g.
self.managedObjectContext = [databaseEngine managedObjectContext];
If the context is not initialized, it will happen here.
I think the best approach too is to have your app delegate manage the data. Seems like the best approach, and it is what a default CD application template does.
I would look into using MagicalRecord, which is pretty amazing if you ask me. With MagicalRecord you just call [NSManagedObjectContext MR_defaultContext]; and you get the default context just like that. MR also has amazing class methods for free like
NSArray *array = [SomeObject findAll]
which returns an array with all your CD objects. You can even set predicates, etc. and it's quite fast.

Resources