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?)
Related
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.
I was wondering if someone could point me in the right direction about whether or not I am using concurrency correctly. I find that currently, the code is very unwieldy and almost unintuitive. For the purposes of my demonstration, please note the CoreData architecture is as described by Marcus Zarra in his article http://martiancraft.com/blog/2015/03/core-data-stack/. Since the operation could take a very long time, I want this entire process to be done in a background thread.
NSManagedContext *context = [[NSManagedContext alloc] initWithConcurrency:NSPrivateConcurrencyQueue];
context.parentContext = [[CoreDataController sharedDispatch] managedObjectContext];
[context performBlock: ^{
NSManagedObject *someManagedObject = [[context executeFetchRequest:request error:nil] firstObject];
NSString *resultFromLongOperation = [self someLongOperation:someManagedObject];
[self doSomething];
BOOL anotherResultFromLongOperation = [self aDifferentLongOperation:someManagedObject];
}];
So from this short example, we can see that I am manipulating the variable someManagedObject, which is of NSManagedObject type and therefore must be used in a performBlock/performBlockAndWait as prescribed by Apple. Should I be passing in the context into someLongOperation and aDifferentLongOperation? If I don't, wouldn't that mean I would have to create another child context and do whatever the function does inside a performBlock/performBlockAndWait and return the result with a __block type? What about in the event if the result of someLongOperation affects the response of aDifferentLongOperation? Am I still structuring my code properly for a situation like this?
Thanks very much in advanced!
I struggled with this same issue not long ago.
I've looked for best practice scenarios and found that:
You indeed, as you stated, shouldn't violate the one context/thread rule set by Apple.
You should normally use completion blocks or notifications with long running operations, whether it's for not making the UI freeze, or just for code clarity.
When you use a completion block, you can create a child context in your long running operation function (someLongOperation) and do whatever you want with it.
If your result is a NSString, then you shouldn't have a problem, because it's not a NSManagedObject and it not bound to any one context.
If you result is a NSManagedObject, then you should pass its NSManagedObjectID to the completion block (with [managedObject objectID] method).
You can use the context's method obtainPermanentIDsForObjects in case you created a new entity and it doesn't have an object ID yet.
Basically, you can use the same context for every long running operation if they all use the same thread. In my case they don't necessarily so I'm creating a new child context for every long running operation just to be sure (creating a new child context is a very fast operation so performance isn't an issue)
I'm trying to add iOS 6 State Restoration to an app that I'm just about finished with. It's an app where the model mostly comes from CoreData.
As recommended, I'm using the "pass the baton" approach to moving managed object contexts between View Controllers - I create the MOC in my App Delegate, pass it to the first View Controller, which passes it to the second in prepareForSegue:, which passes it to the third in prepareForSegue:, etc.
This doesn't seem to jive very well with State Restoration. The only thing I can think of to do is to retrieve the MOC from my App Delegate directly in an implementation of viewControllerWithRestorationIdentifierPath:coder:. In fact, it appears that the Apple developers did something similar when watching the WWDC session.
Is this the best/only way? Does State Restoration effectively break Pass-The-Baton, at least for view controllers that are restored?
To become familiar with state restoration I highly recommend the WWDC 2013 session What's New in State Restoration. While state restoration was introduced a year earlier in iOS 6, iOS 7 brought some notable changes.
Passing it forward
Using the "pass the baton" approach, at some point a root NSManagedObjectContext is created and an NSPersistentStoreCoordinator is attached. The context is passed to a view controller and subsequent child view controllers are in turn passed that root context or a child context.
For example, when the user launches the application the root NSManagedObjectContext is created and passed in to the root view controller, which manages an NSFetchedResultsController. When the user selects an item in the view controller a new detail view controller is created and an NSManagedObjectContext instance is passed in.
Saving and Restoring State
State restoration changes this in ways that are significant to applications using Core Data in view controllers. If the user is on the detail view controller and sends the application to the background the system creates a restoration archive with information useful for reconstructing the state visible when they left. Information about the entire chain of view controllers is written out, and when the application is relaunched this is used to reconstruct the state.
When this happens it does not use any custom initializers, segues, etc. The UIStateRestoring protocol defines methods used for encoding and decoding state which allow for some degree of customization. Objects that conform to NSCoding can be stored in restoration archives and in iOS 7 state restoration was extended to model objects and data sources.
State restoration is intended to store only the information that is required to reconstruct the visible state of the application. For a Core Data application this means storing the information needed to locate the object in the correct persistent store.
On the surface, this seems simple. In the case of a view controller managing an NSFetchedResultsController this may mean storing the predicate and sort descriptors. For a detail view controller that displays or edits a single managed object the URI representation of the managed object would be added to the state restoration archive:
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
NSManagedObjectID *objectID = [[self managedObject] objectID];
[coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
[super encodeRestorableStateWithCoder:coder];
}
When the state is restored the UIStateRestoring method -decodeRestorableStateWithCoder: is called to restore the object from the archived information:
Decode the URI from the restoration archive.
Get a managed object ID for the URI from the persistent store coordinator
Get a managed object instance from the managed object context for that managed object ID
For example:
- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
NSURL *objectURI = nil;
NSManagedObjectID *objectID = nil;
NSPersistentStoreCoordinator *coordinator = [[self managedObjectContext] persistentStoreCoordinator];
objectURI = [coder decodeObjectForKey:kManagedObjectKeyPath];
objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
[[self managedObjectContext] performBlock:^{
NSManagedObject *object = [self managedObjectContext] objectWithID:objectID];
[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self setManagedObject:object];
}];
}];
}
And this is where things become more complicated. At the point in the application life cycle where -decodeRestorableStateWithCoder: is called the view controller will need the correct NSManagedObjectContext.
Pass the Baton vs. State Restoration: FIGHT!
With the "pass the baton" approach the view controller was instantiated as a result of user interaction, and a managed object context was passed in. That managed object context was connected to a parent context or persistent store coordinator.
During state restoration that does not happen. If you look at the illustrations of what happens during "pass the baton" vs. state restoration they may look very similar - and they are. During state restoration data is passed along - the NSCoder instance that represents an interface to the restoration archive.
Unfortunately the NSManagedObjectContext information we require can't be stored as part of the restoration archive. NSManagedObjectContext does conform to NSCoding, however the important parts do not. NSPersistentStoreCoordinator does not, so it will not be persisted. Curiously, the parentContext property of an NSManagedObjectContext also will not (I would strongly suggest filing a radar on this).
Storing the URLs of specific NSPersistentStore instances and recreating an NSPersistentStoreCoordinator in each view controller may seem like an attractive option but the result will be a different coordinator for each view controller - which can quickly lead to disaster.
So while state restoration can provide the information needed to locate entities in an NSManagedObjectContext, it can't directly provide what is needed to recreate the context itself.
So what next?
Ultimately what is needed in a view controller's -decodeRestorableStateWithCoder: is an instance of NSManagedObjectContext that has the same parentage that it did when state was encoded. It should have the same structure of ancestor contexts and persistent stores.
State restoration begins in the UIApplicationDelegate, where several delegate methods are invoked as part of the restoration process (-application:willFinishLaunchingWithOptions:, -application:shouldRestoreApplicationState:, -didDecodeRestorableStateWithCoder:, -application:viewControllerWithRestorationIdentifierPath:coder:). Each one of these is an opportunity to customize the restoration process from the beginning and pass information along - such as attaching an NSManagedObjectContext instance as an associated object reference to the NSCoder used for restoration.
If the application delegate object is responsible for creating the root context that object could be pushed down throughout the view controller chain once the launch process is complete (with or without state restoration). Each view controller would pass the appropriate NSManagedObjectContext instance to it's child view controllers:
#implementation UIViewController (CoreData)
- (void) setManagedObjectContext:(NSManagedObjectContext *)context {
[[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
}
#end
And each view controller that provided it's own implementation would create a child context of it's own. This has other advantages - any approach that has the users of a managed object context react to it changing makes it easier to create the context asynchronously. Creating a context itself is fast and lightweight, but adding the persistent stores to the root context is potentially very expensive and should not be allowed to run on the main queue. Many applications do this on the main queue in an application delegate method and end up being killed by the OS when opening the files of the store takes too long or a migration is required. Adding the persistent store on another thread and then sending the context to the objects that use it when it's ready can help prevent these kinds of problems.
Another approach may be to leverage the responder chain in the view controller. During state restoration the view controller could walk the responder chain to find the next NSManagedObjectContext up the chain, create a child context, and use that. Implementing this using an informal protocol is simple, and results in a solution that is flexible and adaptable.
The default implementation of the informal protocol would walk further up the responder chain:
#implementation UIResponder (CoreData)
- (NSManagedObjectContext *) managedObjectContext {
NSManagedObjectContext *result = nil;
if ([self nextResponder] != nil){
if ([[self nextResponder] respondsToSelector:#selector(managedObjectContext)]){
result = [[self nextResponder] managedObjectContext];
}
}
return result;
}
#end
And any object in the responder chain can implement -managedObjectContext to provide an alternate implementation. This includes the application delegate, which does participate in the responder chain. Using the informal protocol above, if a view or view controller calls -managedObjectContext the default implementation would go all the way to the application delegate to return a result unless some other object along the way provided a non-nil result.
You also have the option of using restoration class factories with state restoration to reconstruct the chain of managed object contexts during restoration.
These solutions are not appropriate for every application or situation, only you can decide what will work for you.
I think the best way to handle this would be to encode the MOC in:
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
and then decode when it's restored via:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
This should retain the pass the baton approach between state restores.
Bear in mind, every VC that uses a MOC should implement this if you're going with this approach.
To expand slightly, utilize the + (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder method to initialize your VC, then the MOC should be decoded via the method mentioned above and you should be all set.
This should hopefully provide enough information to get you going as far as encoding and decoding any information you want to recover when restoring.
I have not done a ton with state restore but I would think along these lines:
Does the app delegate get woken up first? Is there an opportunity for the app delegate to walk the view controllers?
Can the view controller pause while it waits for the AppDelegate to give it the context?
Sounds like State Restoration might be a special case but I would explore the option of making the view controllers smart enough to wait for the MOC to appear before asking for data. Maybe even having a roll back state in the view controllers where they step back to a place where the view controller can wait for the context.
Subclass NSPersistentContainer (as the docs state); adopt the UIStateRestoring protocol so it can be registered with state restoration which allows the pointer to be accessible during restoration methods, the object itself isn't actually encoded in the archive. Call UIApplication's registerObjectForStateRestoration inside the lazy persistentContainer getter. Also as the docs state pass the persistentContainer to the view controllers in application:willFinishLaunching and prepareForSegue, not just the context. For view controllers that cannot get the container passed at willFinishLaunching, encode the persistentContainer and the object's URI in the VC's encodeRestorableStateWithCoder. For show (i.e. push) VCs that are pushed on the master's navigation stack, use the restorationClass technique and UIViewController class method viewControllerWithRestorationIdentifierPath and decode the persistentContainer and then using existingObjectWithID return nil if the object no longer exists which prevents the VC from being created, if it exists then init the VC using the encoded storyboard and the object. In the case of show detail VCs that are always created irrespective of the object existing there is no need to encode the persistentContainer and no need for the restoration class design, just implement the app delegate's application:viewControllerWithRestorationIdentifierPath: and use the app delegate's persistentContainer and set the object on the detail view controller from the initial storyboard (capture it in a property in application:willFinishedLaunching and clear it in finished restoring, the reason is the split can collapse before application:viewControllerWithRestorationIdentifierPath: is called, meaning it can't be retrieved via the window).
The reason we do not decode the object in the decodeRestorableStateWithCoder method is because that is called after viewDidLoad which is too late to set the things we require.
I learned one very clean way of setting up the Core Data stack from NSScreencast. Basically, you start your Xcode project without choosing the "Use Core Data" option. Then you add a singleton class which is your data model. So to get the main MOC, you'd do [[DataModel sharedModel] mainContext]. I find that much cleaner that dumping everything in the App Delegate.
I've never used it this way, but I guess in your case you could also do this in your view controllers:
-(NSManagedObjectContext*)moc
{
if (_moc != nil) return _moc;
_moc = [[DataModel sharedModel] mainContext];
return _moc;
}
My solution to this has been to make the view controllers to default to use a global shared MOC. This context will be the intended one in most cases, and if you need to pass any other MOC it is perfectly possible to do so.
This approach confirms to Apple's "pass the baton"-approach as well as being both convenient and compatible with state restoration.
When I first created my app, I stored all my runtime custom objects and properties in my app delegate so I could share them across views. I never liked this and always wanted to change it so I did some reading today and moved all my runtime properties and objects to a singleton object like so:
#synthesize gblStr;
+(AppDataSingleton *)singleObj
{
static AppDataSingleton * single=nil;
#synchronized(self)
{
if(!single)
{
single = [[AppDataSingleton alloc] init];
}
}
return single;
}
where lets say gblStr can be accessed from any view controller that has the singleton.
This works great and I now have all my objects stored here instead of in my app delegate.
In each view controller I add the property:
AppDataSingleton *globalSingleton;
and in the viewDidLoad, I instantiate it:
globalSingleton = [AppDataSingleton singleObj];
MY QUESTION IS:
Will there ever be a case where a user will come back to the app and the singleton has been destroyed? Do I need to check for this?
Or, in the case it was destroyed, will it start the app over from scratch?
The singleton will be destroyed, if the app has crashes or stops running. Unless you write your objects to a persistent store (CoreData, .plist, SQLite, etc...), you will have to recreate your objects as well...
In my view, the singleton should persist for the life of the program run (i.e. an entire session). If you are not clearing it actively, that data should be there whenever the app returns from an inactive state. In any case, you are checking if the object is nil and reinstantiating if it is, so the data should be rehydrated.
If you have concerns about data not being there, you should be actively persisting it to CoreData or the sandbox.
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.