I am getting some crashes that I have never experienced myself during test, development or usage.
I can see them on Fabric Dashboard, and it concerns the NSManagedObjectContext.
Here is the first call on StackTrace :
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:[MyAppDelegate mainContext]];
CDFavori is a class representing the CoreData object, which is extended in order to implement some methods (in order to fetch) :
+(CDFavori *)favoriWithIndicatif:(NSString*)indicatif context:(NSManagedObjectContext*)context
{
if (nil == indicatif || nil == context)
return nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"CDFavori"];
[request setPredicate:[NSPredicate predicateWithFormat:@"indicatif LIKE %@", indicatif]];
NSError *error = nil;
NSArray *favoris = [context executeFetchRequest:request error:&error];
CDFavori *fav = nil;
if (nil != error) {
DDLogError(@"Error = %@ (%@)", indicatif, error);
} else if (0 < [favoris count])
{
fav = [favoris objectAtIndex:0];
if (1 < [favoris count]) {
DDLogWarn(@"More than one object present in DB : %@", indicatif);
}
}
return favori;
}
The Crashes don't come from this method, it is just to give you some context.
The issue comes from the AppDelegate and the NSManagedObjectContext.
Here are my code for the Core Data methods :
+(NSManagedObjectContext*)mainContext
{
return ((MyAppDelegate*)[UIApplication sharedApplication].delegate).managedObjectContext;
}
The crashes are here :
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
EDIT - Just to mention the declarations :
in header :
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
in .m file:
#pragma mark - Core Data stack
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
EDIT After Answer :
Do you think something like this would be better ?
Remove this declaration :
#pragma mark - Core Data stack
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
And replace it with :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
_managedObjectModel = [[NSManagedObjectModel alloc] init];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] init];
}
This would be better ? With the same .h file.
But I have to change all my variables, and change the readonly properties ?
I believe that you have 3 separate issues:
try replacing:
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:[MyAppDelegate mainContext]];
with
NSManagedObjectContext* context = [MyAppDelegate mainContext];
CDFavori* fav = [CDFavori favoriWithIndicatif:homeFavoriteIndicatif context:context];
I had a similar issue and this fixed it. The reason is that when the context is created and then pass directly as a parameter ARC release the context on the next line. Then the managedObject has no context and it crashes. If you first assign it to a local variable then ARC will keep the context around for the entire scope. This cannot happen in a debug environment because ARC behaves differently there.
The next question is why is the context being release. While you haven't shown any wrong code, I suspect that this is happening very early in the application lifecycle and that there are multiple threads creating a the main context at the same time. So the first call creates a context and assigns it to _managedObjectContext
and then a second context gets assigned and the first context gets release. (And it is not retained in the local scope so there is a crash).
In your core data setup you should ONLY access the _managedObjectContext
variable on the main thread. I suggest adding a check at the beginning of the managedObjectContext
method
if (![NSThread mainThread]) {
// log error to fabric
//[[Crashlytics sharedInstance] recordError:...];
return nil;
}
Also I would create the _managedObjectContext
explicitly on launch in application:didFinishLaunchingWithOptions:
and not create it lazily. When it is created lazily you don't know when exactly it will be created. And if it is created from a background thread your entire stack will be messed up. You have little to gain from doing it lazily as you are certainly going to create it in order to show anything to the user.
You could leave your code as is and simply add
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self managedObjectContext]; //force loading of context