iOS GoogleAnalytic continuously produce an exception - ios

I'm currently using the last version of Google Analytics (v2.0)
I instantiate it the most common way in my appDelegate:
[GAI sharedInstance].trackUncaughtExceptions = NO;
[GAI sharedInstance].dispatchInterval = 0;
[[GAI sharedInstance] trackerWithTrackingId:#"..."];
But when i'm running the app it continuously produce this exception in logs:
An observer of NSManagedObjectContextDidSaveNotification illegally threw an exception. Objects saved = {
deleted = "{(\n <GAIHit: 0xc1cac50> (entity: GAIHit; id: 0xc160740 <x-coredata://8854889C-BE6C-49BB-BBA9-99465B86265E/GAIHit/p26> ; data: {\n dispatchUrl = \"https://ssl.google-analytics.com/collect\";\n gaiVersion = \"2.0b4\";\n parametersData = <62706c69 73743030 d4010203 04050852 53542474 6f705824 6f626a65 63747358 24766572 73696f6e 59246172 63686976 6572>;\n timestamp = \"2013-07-10 10:21:55 +0000\";\n})\n)}";
inserted = "{(\n)}";
updated = "{(\n)}";
} and exception = Object's persistent store is not reachable from this NSManagedObjectContext's coordinator with userInfo = (null)
It doesn't make the application crash, but it's very verbose and pollute my logs.
Moreover, it seems to work, because GA logs says:
-[GAIDispatcher dispatchComplete:withStartTime:withRetryNumber:withResponse:withData:withError:] (GAIDispatcher.m:415) DEBUG: Successfully dispatched hit /GAIHit/p51 (0 retries).
Any idea to stop these logs?

From GAM documentation:
If your app uses the CoreData framework: responding to a notification,
e.g. NSManagedObjectContextDidSaveNotification, from the Google
Analytics CoreData object may result in an exception. Instead, Apple
recommends filtering CoreData notifications by specifying the managed
object context as a parameter to your listener. Learn more from Apple.
I guess that is your case

You should merge changes only from your managed object contexts, not from any other contexts created by the third-party libraries.
However filtering by contexts implies storing somewhere the list of all your background contexts. I've found easier solution: instead of comparing the context with your list of contexts it's sufficient just to check whether the context was created for your PersistentStoreCoordinator:
- (void) managedObjectContextDidSaveNotification: (NSNotification *) notification {
NSManagedObjectContext * context = notification.object;
if (context != self.managedObjectContextForMainThread) {
if (context.persistentStoreCoordinator == self.persistentStoreCoordinator) {
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
}

Related

NSPersistentContainer concurrency for saving to core data

I've read some blogs on this but I'm still confused on how to use NSPersistentContainer performBackgroundTask to create an entity and save it. After creating an instance by calling convenience method init(context moc: NSManagedObjectContext) in performBackgroundTask() { (moc) in } block if I check container.viewContext.hasChanges this returns false and says there's nothing to save, if I call save on moc (background MOC created for this block) I get errors like this:
fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ... }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ...} and new database row = {id = 2; ...}"
)}
So I've failed to get the concurrency working and would really appreciate if someone could explain to me the correct way of using this feature on core data in iOS 10
TL:DR: Your problem is that you are writing using both the viewContext and with background contexts. You should only write to core-data in one synchronous way.
Full explanation: If an object is changed at the same time from two different contexts core-data doesn't know what to do. You can set a mergePolicy to set which change should win, but that really isn't a good solution, because you will lose data that way. The way that a lot of pros have been dealing with the problem for a long time was to have an operation queue to queue the writes so there is only one write going on at a time, and have another context on the main thread only for reads. This way you never get any merge conflicts. (see https://vimeo.com/89370886#t=4223s for a great explanation on this setup).
Making this setup with NSPersistentContainer is very easy. In your core-data manager create a NSOperationQueue
//obj-c
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
//swift
let persistentContainerQueue = OperationQueue()
persistentContainerQueue.maxConcurrentOperationCount = 1
And do all writing using this queue:
// obj c
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext;
[context performBlockAndWait:^{
blockCopy(context);
[context save:NULL]; //Don't just pass NULL here, look at the error and log it to your analytics service
}];
}]];
}
//swift
func enqueue(block: #escaping (_ context: NSManagedObjectContext) -> Void) {
persistentContainerQueue.addOperation(){
let context: NSManagedObjectContext = self.persistentContainer.newBackgroundContext()
context.performAndWait{
block(context)
try? context.save() //Don't just use '?' here look at the error and log it to your analytics service
}
}
}
When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.
At first I thought that NSPersistentContainer's performBackgroundTask had an internal queue, and initial testing supported that. After more testing I saw that it could also lead to merge conflicts.

Changes on Background NSManagedObjectContext not visible on Main, using NSFetchedResultsController

This is a really bizarre issue, and I thought I understood Core Data.
I use a background context that has no parent. Hooked right into the Persistent Store Coordinator. I update objects on this background context then save it. I listen to the ContextDidSaveNotification and merge those changes into my main thread context. Those updated objects are not faults on the main thread as they are already used to populate table view cells. So I would expect those changes to actually merge. But they are not.
Without getting into the details of my data models, it suffices to say that an object has a property "downloadState". Once the parsing work is done on the background thread, downloadStateValue (an enum) gets set to "3", which corresponds to 'completed'.
I subscribe to the ContentWillSave notification now to inspect what's going on. I get this at the end of my parsing work:
2016-06-13 10:19:21.055 MyApp[29162:52855206] Going to save background context.
updated:{(
<QLUserPinnedCourse: 0x7fe195403c10> (entity: QLUserPinnedCourse; id: 0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11> ; data: {
course = "0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55>";
courseId = 2794;
/* other fields redacted */
}),
<QLCourse: 0x7fe1954cded0> (entity: QLCourse; id: 0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55> ; data: {
/* other fields redacted*/
contentDownloadState = 3;
courseId = 2794;
pinnedUserData = "0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11>";
})
The NSFetchedResultsController that is listenting to QLUserPinnedCourse objects gets the delegate calls, which triggers cell reloads in my tables.
The predicate is:
// Specify criteria for filtering which objects to fetch
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pinned == %# && course.contentDownloadState IN %#",
#YES,
#[#(QLDownloadStateSucceeded), #(QLDownloadStateNotYetAttempted), #(QLDownloadStateFailed), #(QLDownloadStateIncomplete)]
];
Now when I get to the cell code, I have a QLUserPinnedCourse object to work with. I set a breakpoint in the debugger and get:
(lldb) po userCourse.course
<QLCourse: 0x7fe19568f740> (entity: QLCourse; id: 0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55> ; data: {
contentDownloadState = 1;
courseId = 2794;
pinnedUserData = "0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11>";
})
The question is, WHY is contentDownloadState not 3, but still 1 ?? I don't get it.
Shouldn't these changes have been merged??
Details as to my stack:
PSC -> Private Concurrent (saving context) -> Main Thread context
PSC -> Private Concurrent (import context)
ContextDidSave:
if the context was an import context, merge changes into both contexts above:
_contextSaveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:nil
usingBlock:^(NSNotification* note)
{
NSManagedObjectContext *contextSaved = note.object;
NSManagedObjectContext *moc = weakself.mainQueueContext;
// basically, if this was a background worker thread
DDLogDebug(#"updatedObjects:%#", note.userInfo[NSUpdatedObjectsKey]);
if ([contextSaved.userInfo[CoreDataUserInfoKeyIsWorkerContext] boolValue])
{
[weakself.privateSavingContext performBlock:^(){
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey]) {
[[weakself.privateSavingContext objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[weakself.privateSavingContext mergeChangesFromContextDidSaveNotification:note];
[moc performBlock:^(){
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey]) {
[[moc objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}];
}
}];
Note that I'm asking the userCourse.course object for its attribute, although my FRC is interested in QLUserPinnedCourse objects. I thought because I specify a keypath in the predicate that relates to a QLCourse object, these changes are refreshed.
It's a quirk of Core Data. You actually need to re-fault objects in the main context which were updated by the save operation.
Here's an example in Swift:
mainContext.performBlock {
let updatedObjects : Set<NSManagedObject> = notification.userInfo![NSUpdatedObjectsKey] as! Set<NSManagedObject>
for obj in updatedObjects {
self.mainContext.objectWithID(obj.objectID).willAccessValueForKey(nil)
}
self.mainContext.mergeChangesFromContextDidSaveNotification(notification)
}
The main part is the call to willAccessValueForKey:nil, which causes the object to be marked as a fault. This will cause NSFetchedResultsControllers in the main context to fire.
So I found a solution but I can't tell you why it works.
The problem I suppose is that I would have a method 'start downloading content' and I would update the property contentDownloadState on the main thread context to 'incomplete/downloading', then proceed to get all the content.
All the rest of the work was done on a background thread context. When finished I updated that value with 'succeeded'. It wasn't merging that change. I have no idea why.
Once I decided to do everything on the worker context. i.e. change its value then save the context to disk, the changes, ALL the changes were propagating.
So in the end I solved it, but really don't understand the problem.

Core data crashes the app [duplicate]

I created a Core Data model in xcode 3.2 and after upgrading in Xcode 4.2, I then added a new entity of the NSManagedObject subclass (refer to the new entity).
First thing, it looks weird because it's not in the same group as the old one. Here is the picture on my xcode 4.2 (AlkitabDB is the one i created in xcode 3.2, EndeDB is the new one from current xcode version(4.2):
Second thing, I let it as it is, then I accessed the second entity (the new one) the same way as the first entity (the old one), and the error as titled appears.
Here is the error:
2012-01-16 21:13:38.496 iHuria[55953:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x8829cd0 {metadata=<CFBasicHash 0x882a370 [0x1839b38]>{type = immutable dict, count = 7,
entries =>
2 : <CFString 0x8829b90 [0x1839b38]>{contents = "NSStoreModelVersionIdentifiers"} = <CFArray 0x8829ff0 [0x1839b38]>{type = immutable, count = 0, values = ()}
4 : <CFString 0x8829bc0 [0x1839b38]>{contents = "NSPersistenceFrameworkVersion"} = <CFNumber 0x8829770 [0x1839b38]>{value = +320, type = kCFNumberSInt64Type}
6 : <CFString 0x8829bf0 [0x1839b38]>{contents = "NSStoreModelVersionHashes"} = <CFBasicHash 0x882a080 [0x1839b38]>{type = immutable dict, count = 1,
entries =>
0 : <CFString 0x882a010 [0x1839b38]>{contents = "AlkitabDB"} = <CFData 0x882a030 [0x1839b38]>{length = 32, capacity = 32, bytes = 0xd02ac5f8be6ab0b39add450aca202ac0 ... 3d45d462998d2ccd}
}
7 : <CFString 0x10e3aa8 [0x1839b38]>{contents = "NSStoreUUID"} = <CFString 0x8829e60 [0x1839b38]>{contents = "4F2EE7FF-463B-4055-BBED-8E603CDBDF59"}
8 : <CFString 0x10e3948 [0x1839b38]>{contents = "NSStoreType"} = <CFString 0x10e3958 [0x1839b38]>{contents = "SQLite"}
9 : <CFString 0x8829c40 [0x1839b38]>{contents = "NSStoreModelVersionHashesVersion"} = <CFNumber 0x6b1c7c0 [0x1839b38]>{value = +3, type = kCFNumberSInt32Type}
10 : <CFString 0x8829c70 [0x1839b38]>{contents = "_NSAutoVacuumLevel"} = <CFString 0x882a0c0 [0x1839b38]>{contents = "2"}
}
, reason=The model used to open the store is incompatible with the one used to create the store}, {
metadata = {
NSPersistenceFrameworkVersion = 320;
NSStoreModelVersionHashes = {
AlkitabDB = <d02ac5f8 be6ab0b3 9add450a ca202ac0 ebd1e860 cbb578c2 3d45d462 998d2ccd>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "4F2EE7FF-463B-4055-BBED-8E603CDBDF59";
"_NSAutoVacuumLevel" = 2;
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
I looked for the solution before and discovered that I should remove the appliation from simulator and rerun the app, and it didn't work.
Does anyone know a solution for this issue?
Please help.
Deleting the app is sometimes not the case! Suggest, your app has already been published! You can't just add new entity to the data base and go ahead - you need to perform migration!
For those who doesn't want to dig into documentation and is searching for a quick fix:
Open your .xcdatamodeld file
click on Editor
select Add model version...
Add a new version of your model (the new group of datamodels added)
select the main file, open file inspector (right-hand panel)
and under Versioned core data model select your new version of data model for current data model
THAT'S NOT ALL ) You should perform so called "light migration".
Go to your AppDelegate and find where the persistentStoreCoordinator is being created
Find this line if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
Replace nil options with #{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} (actually provided in the commented code in that method)
Here you go, have fun!
P.S. This only applies for lightweight migration. For your migration to qualify as a lightweight migration, your changes must be confined
to this narrow band:
Add or remove a property (attribute or relationship).
Make a nonoptional property optional.
Make an optional attribute nonoptional, as long as you provide a default value.
Add or remove an entity.
Rename a property.
Rename an entity.
For Swift 4
coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true])
Remove the app from the simulator and perform a clean on your project. That should clear those issues up. Make sure that you are not running in the debugger when you delete the app or else it won't actually delete it properly.
If you want to be sure its gone, check this directory Users/INSERT_YOUR_USER_HERE/Library/Application Support/iPhone Simulator/ for your app's folder, under the version you're running.
Note: This is for development only. For production, you need to implement some sort of migration. Google "Core Data Migration", with lightweight migration being the simplest.
Just add Options attribute while creating persistentStoreCoordinator in AppDelegate.m file for the core data method as below
OBJECTIVE-C
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil)
{
return _persistentStoreCoordinator;
}
NSLog(#"persistentStoreCoordinator___");
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyApp.sqlite"];
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"persistentStoreCoordinator___2");
return _persistentStoreCoordinator;
}
SWIFT
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
// MAIN LINE OF CODE TO ADD
let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: mOptions)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}
It had solved my problem..
Answer : Remove the app from the Simulator , Perform a Clean and Re-Build your Project.
Note : Whenever you perform changes to the Core Data definition, Delete the app installed on the Physical Device or Simulator, Clean the Project and Re-Build again.
Yes. Once you delete app on physical device and rebuild it works.
For swift, in AppDelegate.swift find the line
try coordinator!.addPersistentStoreWithType(NSXMLStoreType, configuration: nil, URL: url, options: nil )
and replace it with
try coordinator!.addPersistentStoreWithType(NSXMLStoreType, configuration: nil, URL: url, options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true])
I just spent several days fighting this error, as well as mergedModelFromBundles crashes, and getting the "Can't merge models with two different entities named *" error.
It turns out the root problem was that Xcode doesn't remove old resources from devices and I had old versions of my data model (.mom files) that were causing conflicts. This is why deleting the app fixed the problem on one of my devices.
After finding this blog post via another SO answer I made my app more tolerant of old models by changing this line which looks for ALL .mom files:
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
to this, which only looks in the Filters directory:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Filters" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
I used recursivePathsForResourcesOfType from this so question: to help figure this out by logging all of the .mom files in the app:
NSArray *momPaths = [self recursivePathsForResourcesOfType:#"mom" inDirectory:[[NSBundle mainBundle] resourcePath]];
NSLog(#"All .mom files:%#",momPaths);
I also used iExplorer to look at the extraneous .mom files (I didn't try deleting them yet).
The method below was also helpful. It showed that an entity was in the merged model returned by [psc managedObjectModel] that didn't exist any more in any of my models or in the store itself. This was what let me to believe an old model was being cached on the device itself that clean building didn't remove. The method logs each entity that is the same, been changed, or added to, or removed from the model. (written with this SO answer as a starting point):
- (BOOL)comparePersistentStore:(NSPersistentStoreCoordinator *)psc withStoreURL: (NSURL *)storeURL {
NSError *error = nil;
// Get the entities & keys from the persistent store coordinator
NSManagedObjectModel *pscModel = [psc managedObjectModel];
NSDictionary *pscEntities = [pscModel entitiesByName];
NSSet *pscKeys = [NSSet setWithArray:[pscEntities allKeys]];
//NSLog(#"psc model:%#", pscModel);
//NSLog(#"psc keys:%#", pscKeys);
NSLog(#"psc contains %d entities", [pscModel.entities count]);
// Get the entity hashes from the storeURL
NSDictionary *storeMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeURL
error:&error];
NSDictionary *storeHashes = [storeMetadata objectForKey:#"NSStoreModelVersionHashes"];
//NSLog(#"store metadata:%#", sourceMetadata);
NSLog(#"store URL:%#", storeURL);
NSLog(#"store NSStoreUUID:%#", [storeMetadata objectForKey:#"NSStoreUUID"]);
NSLog(#"store NSStoreType:%#", [storeMetadata objectForKey:#"NSStoreType"]);
NSSet *storeKeys = [NSSet setWithArray:[storeHashes allKeys]];
// Determine store entities that were added, removed, and in common (to/with psc)
NSMutableSet *addedEntities = [NSMutableSet setWithSet:pscKeys];
NSMutableSet *removedEntities = [NSMutableSet setWithSet:storeKeys];
NSMutableSet *commonEntities = [NSMutableSet setWithSet:pscKeys];
NSMutableSet *changedEntities = [NSMutableSet new];
[addedEntities minusSet:storeKeys];
[removedEntities minusSet:pscKeys];
[commonEntities minusSet:removedEntities];
[commonEntities minusSet:addedEntities];
// Determine entities that have changed (with different hashes)
[commonEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
if ( ! [pscDescrip.versionHash isEqualToData:storeHash]) {
if (storeHash != nil && pscDescrip.versionHash != nil) {
[changedEntities addObject:key];
}
}
}];
// Remove changed entities from common list
[commonEntities minusSet:changedEntities];
if ([commonEntities count] > 0) {
NSLog(#"Common entities:");
[commonEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\t%#:\t%#", key, pscDescrip.versionHash);
}];
}
if ([changedEntities count] > 0) {
NSLog(#"Changed entities:");
[changedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\tpsc %#:\t%#", key, pscDescrip.versionHash);
NSLog(#"\tstore %#:\t%#", key, storeHash);
}];
}
if ([addedEntities count] > 0) {
NSLog(#"Added entities to psc model (not in store):");
[addedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\t%#:\t%#", key, pscDescrip.versionHash);
}];
}
if ([removedEntities count] > 0) {
NSLog(#"Removed entities from psc model (exist in store):");
[removedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSLog(#"\t%#:\t%#", key, storeHash);
}];
}
BOOL pscCompatibile = [pscModel isConfiguration:nil compatibleWithStoreMetadata:storeMetadata];
NSLog(#"Migration needed? %#", pscCompatibile?#"no":#"yes");
return pscCompatibile;
}
usage: called before adding each store to NSPersistentStoreCoordinator :
[self comparePersistentStore:self.psc withStoreURL:self.iCloudStoreURL];
_iCloudStore = [self.psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.iCloudStoreURL
options:options
error:&localError];
Every time you making change to the Core Date definition, you should delete the apps installed on the physical device or simulator.
Stop app from running.
Delete app on simulator.
Product - > Clean
Build, run.
The simplest solution that worked for me in Swift 2.1, Xcode 7 is :
Delete the app from the Simulator ( Cmd + Shift + H to go to the Home Screen. Long Press the app, Click cross, just the usual way you delete an app from your phone)
Cmd + Shift + H again to stop the dancing of apps
Go back to your project and rerun
I had this issue while writing/reading from Core Data with 2 entities set up. Deleting the app and rerunning the program fixed the issue
I just deleted [Simulator App Folder]/Document/*.sqlite file after making changes in entities and it worked.
And of course, .sqlite file contains all stored data and structures which will be lost.
Please Delete a application from simulator and clean a code and run .its work fine .do it may be its help YOU.
If you are using Swift.
Follow the answer by #Stas and insert options, in place of nil, in your App Delegate:
let myOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: myOptions, error: &error) == nil {
Try "Reset Content & Settings" in the simulator. Worked for me after deleting app and Clean build
I experienced the same issue with my app (not yet released in App Store).
Here's how I fixed it:
Run Clean (Cmd+Shift+K)
Restart iOS Simulator
iOS Simulator -> Reset Content and Settings (from navbar)
(3) was the step that finally got it to run properly. Hope this helps!
In my case, I had two persistent stores, one local store for user specific data, and one CoreData+CloudKit store for common data that syncs automatically with iCloud. Thus the data model has two configurations, and the entities are assigned to both configurations as required.
Due to a bug during development, I tried to store an entity that was no longer assigned to any configuration. So when the context was saved, CoreData realized the incompatibility, and crashed with this error.
Of course, deleting the app does not help in such a case. One has to ensure that only assigned entities are stored in a persistent store.
Although sometimes you can just remove the app from the device when changing schema in managed object model, in some scenarios this is not possible e.g. because you already published your app with an old schema.
If this is the case, you have to take care of migrating old data to the new schema:
Core Data Model Versioning and Data Migration
You'll need to migrate the Core Data model using migration. Any time you change the model, you make it incompatible without versioning. Strap yourself in, it's a bit of a hairy topic.
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html
If you make changes to your Core Data model, you have to provide a migration policy that tells Core Data how to adopt existing persisted objects (that your users created with the currently released version) to the new model.
For some scenarios, Core Data is able to automatically infer the mapping from the old model to the new one. For more complex changes, you might have to implement some logic that performs the migration.
Details can be found in the Core Data Model Versioning and Data Migration Programming Guide.
Update
This answer here on Stack Overflow covers the basics of Core Data's lightweight migration and also has some code to get you started.
First, the only things that should be in the xcdatamodeld bundle are xcdatamodel files. Your subclasses should NOT be in the xcdatamodeld. Move those out of there. There is a fair chance they are confusing the compiler.
Second, the error indicates that Core Data cannot find your model. Have you created data and then touched the model? If so you are in an inconsistent state and need to fix that either by deleting the data (which Philippe suggested) or by rolling your changes of the model BACK.
This issue generally occurs due to incompatibility between the version on which DB has been created. General approach to this problem is to delete the app and reinstall it. But in your mentioned case the version of DB are completely different on Xcode 3.2 and 4.2. So better use same version of Xcode for DB.
I was getting the error but the reason I was getting the error was because of the following.
I originally had one Entity named "Entry" and had one row saved for that entity in the database. I then added another Entity named "Person" and after adding that went to build and got the error. So I solved the issue by deleting "Person" Entity and then building the app, deleted the row that was in "Entry" and then closed the application. I then deleted the app entirely off my phone and then did a rebuild and it worked fine. Not sure which step corrected the problem (the deletion of the row or the app), but hopefully if you're looking for a solution this will help. :)
Edit: Oh and if you worried about deleting your new Entity (in my case "Person") to build the app again remember you can get it back afterwards by using CMD+Z!
I had this problem - I first reset my simulator and then clean the project and rebuild. And then it works.
When you change core data, ( adding a field to table , removing field etc ), the sqlite file in applications document folder needs to be in sync with your schema.
This file is not overwritten by default, this file needs to be regenerated.
Follow these steps:
Go to the folder pointed by NSURL. (This path can be found in exception message generated by application before crashing.)
example : /Users//Library/Application Support/iPhone Simulator//Applications//Documents
remove or rename the sqlite file
Clean and Rerun the application
Rerunning application would generate a new sqlite file.
This will make sure that the schema and Xcode are in sync.
This may help some people but may not answer the question. In my case, the problem was solved because I forgot to add the model to the correct configuration. See the screenshot attached. All the models are added to the default configuration, but my application uses the private configuration. Drag and drop your model from the default configuration to the correct configuration.
iOS Simulator -> Reset Contents and Settings...
Worked for me
iOS Simulator -> Reset Contents and Settings... -> Reset
Works on iOS9 (xcode 7.1) as well

Cannot update object that was never inserted

I create an category object and save it:
NSManagedObjectContext *managedObjectContext = [[FTAppDelegate sharedAppDelegate] managedObjectContext];
_category = (Category *)[NSEntityDescription
insertNewObjectForEntityForName:#"Category"
inManagedObjectContext:managedObjectContext];
NSError *error = nil;
[managedObjectContext save:&error];
if (error) {
NSLog(#"error saving: %#",error);
}
then edit the name of the category object and save again.
_category.name = _nameTextField.text;
NSManagedObjectContext *managedObjectContext = [[FTAppDelegate sharedAppDelegate] managedObjectContext];
NSError *error = nil;
[managedObjectContext save:&error];
if (error) {
NSLog(#"error saving: %#",error);
}
and get this error:
2013-01-12 17:53:11.862 instacat[7000:907] Unresolved error Error Domain=NSCocoaErrorDomain Code=134030 "The operation couldn’t be completed. (Cocoa error 134030.)" UserInfo=0x2027b300 {NSAffectedObjectsErrorKey=(
"<Category: 0x1ed43cf0> (entity: Category; id: 0x1ed52970 <x-coredata://68E5D7B6-D461-4962-BC07-855349DB3263-7000-00000141BAB4C399/Category/tE8AB2F2E-C14C-4E93-8343-CC245B7726622> ; data: {\n categoryId = nil;\n isPrivate = 0;\n name = techies;\n users = (\n );\n})"
), NSUnderlyingException=Cannot update object that was never inserted.}, {
NSAffectedObjectsErrorKey = (
"<Category: 0x1ed43cf0> (entity: Category; id: 0x1ed52970 <x-coredata://68E5D7B6-D461-4962-BC07-855349DB3263-7000-00000141BAB4C399/Category/tE8AB2F2E-C14C-4E93-8343-CC245B7726622> ; data: {\n categoryId = nil;\n isPrivate = 0;\n name = techies;\n users = (\n );\n})"
);
NSUnderlyingException = "Cannot update object that was never inserted.";
}
Thank you for your time and consideration.
I am using the AFIncrementalStore.
How about something like this:
self.category.name = self.nameTextField.text;
NSManagedObjectContext *managedObjectContext = [[FTAppDelegate sharedAppDelegate] managedObjectContext];
if(![self.category isInserted])
{
[managedObjectContext insertObject:self.category];
}
NSError *error = nil;
[managedObjectContext save:&error];
if (error) {
NSLog(#"error saving: %#",error);
}
Basically check the object is it has been inserted before, if not, insert it and then save the context.
When you update an object, you can't use insertNewObjectForEntityForName, you need to first save your object, then call something like
[self.managedObjectContext refreshObject:_category mergeChanges:YES]
Then use managedObjectContext save again.
This is the difference in direct SQL as "INSERT" and "UPDATE".
Your object is loosing the managedObjectContext. Either use
self.managedObjectContext
or refetch the object in
[[FTAppDelegate sharedAppDelegate] managedObjectContext]
and edit the refetched object and then save it.
I have the same error but different and rare scenario, it happens once in almost 100 attempts. Find my problem below:
I have 2 NSManagedObjects in core data model:
1- Lead
2- LeadAttirbute
Lead has 1-M relationship with LeadAttribute.
There is a form that inputs for lead and refresh(create new lead) the form after submitting a lead. If i keep on submitting the leads then at a stage, [managedObjectContext save:&error]; starts giving below error:
Domain=NSCocoaErrorDomain Code=134030 "The operation couldn’t be completed. (Cocoa error 134030.)" UserInfo=0x1f251740 {NSAffectedObjectsErrorKey=(
" (entity: LeadInfoAttribute; id: 0x1f2eb920 ; data: {\n attributeId = 0;\n lead = nil;\n optional = nil;\n orderId = 0;\n title = nil;\n value = Bjjbjp;\n})"
), NSUnderlyingException=Cannot update object that was never inserted.}
And it keeps on giving the same error until i dont terminate and re-launch the app. I'm not able to update anything in core data model after this error occur, So my questions are:
1- Can we remove the fault state of core data? i.e to capture and delete the object that is creating trouble before making the save call again.
2- What could be the possible reasons for this issue? Since its very rare and can't reproduce this everytime.
I've just run into this issue and in my case the problem was following:
1) create new managed object in context A
2) save the context A
3) retrieve this object by objectID from context B
4) make changes on managed object and save the context B
Normally it wouldn't be a problem, but in this case the context A is child context and therefore doesn't save to persistent store (just to parent context, which isn't the context B). So when fetch for managed object is done from context B, context doesn't have this object. When changes are made, context tries to save them anyway...and thats when this error occurs. In some cases (as #Trausti Thor mentioned) the refreshObject:mergeChanges: method could help (it passes the data to another context).
In Your case I'll check if:
1) managed object context from [[FTAppDelegate sharedAppDelegate] managedObjectContext] returns always the same context
2) when you save the category, check if it was really saved to persistent store (self.category.objectID.isTemporaryID == NO)
NOTE:
The second point is more important, because if you look carefully, your category object still has temporary objectID, that means it's not persisted.
What I think it's happening is that you are not getting the right NSManagedObjectContext.
Think about it as a session. So when you update you are not getting the right session and so your object doesn't exist there.
Before doing the second save try to find your object on that NSManagedObjectContext.
If you need further help please describe what happens between the creation and the update.
Getting the wrong NSManagedObjectContext can be due to bad code on the AppDelegate or accessing from another thread other than the main thread.

MagicalRecord: multiple databases

I have an app that uses MagicalRecord, and I'm pre-populating the database with a large amount of data that is used for reference. Within that same data model, I have user-definable information pertaining to things the user may do in the app.
The app was rejected because the pre-populated data is supposed to be marked as "do not backup". So, I'd like to have that data in a separate datastore so that I can keep the user data as backupable.
Is there a way to have two separate datastores using MagicalRecord?
I think it's possible, but not too easy though.
As you know, to work with more than one database, you should bring some changes to your PersistentStoreCoordinator, so it will have two PersistentStores. After this, you Core Data stack would look like this:
The other way is two make two separate PersistentStoreCoordinators, each carrying one store.
In Magical Record, there are several class methods for adding stores in
NSPersistentStoreCoordinator+MagicalRecord.h.
(NSPersistentStore *) MR_addInMemoryStore;
(NSPersistentStore *) MR_addAutoMigratingSqliteStoreNamed:(NSString *) storeFileName;
(NSPersistentStore *) MR_addSqliteStoreNamed:(id)storeFileName withOptions:(__autoreleasing NSDictionary *)options;
I think, that this is the place where you could do the thing you want.
Also i should mention, that the whole proccess of setting up the stack goes in MagicalRecord+Setup.h
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName
So you can add your Stores and Coordinators there.
I've never managed it by myself, that was just a brief investigation of a possible solution.
I was able to solve this issue using configurations. Since Magical Record always sends null for the configuration parameter, I broke apart setupCoreDataStackWithAutoMigratingSqliteStoreNamed and replaced it with a method that supports multiple configurations.
Because Magical Record does a good job of handling auto migrations, I first call setupCoreDataStackWithAutoMigratingSqliteStoreNamed, followed by cleanup, and then I supply my replacement code.
I have one object model with my seed data objects assigned the the "Seed" configuration and user objects assigned to the "User" configuration. Magical Record has already been initialized so it could auto migrate if necessary.
+(void) RB_setupMultipleStores:(NSString *) seedStoreName userStore:(NSString *) userStoreName
/* change persistent store to one with multiple configurations. Assumes Magical Record is initialized. */
{
NSError * error= nil;
[MagicalRecord cleanUp];
NSManagedObjectModel * model = [NSManagedObjectModel MR_defaultManagedObjectModel];
NSURL *seedURL = [NSPersistentStore MR_urlForStoreName:[seedStoreName stringByAppendingString:#".sqlite"]];
NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSPersistentStore * seedStore =[coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Seed"
URL:seedURL
options:options
error:&error];
if (!seedStore || error)
{
NSLog(#"Error setting up seed store:%# for %#", [error localizedDescription], seedURL);
exit(-1);
}
NSURL *userURL = [NSPersistentStore MR_urlForStoreName:[userStoreName stringByAppendingString:#".sqlite"]];
NSPersistentStore * userStore = [coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"User"
URL:userURL
options:options
error:&error];
if (!userStore || error)
{
NSLog(#"Error setting up user store:%# for %#", [error localizedDescription], userURL);
exit (-1);
}
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}
Also, MR 3.0 has concurrent stacks which may solve the problem once it is done.
Keeping data for different Core Data entities in different store files is well supported and fairly straightforward. However, MagicalRecrd doesn't provide any convenience methods for setting up your Core Data stack in this way. You simply have to allocate your stack manually, and tell MagicalRecord to use the NSPersistentStoreCoordinator you create. Here's how I did it in swift:
import Foundation
import CoreData
import MagicalRecord
class CoreDataSetup {
static func setupAutoMigratingStack(withContentConfigurationName contentConfigurationName: String, userConfirgurationNameName: String) {
MagicalRecord.cleanUp()
let managedObjectModel = NSManagedObjectModel.MR_defaultManagedObjectModel()
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!)
let contentURL = NSPersistentStore.MR_urlForStoreName(contentConfigurationName + ".sqlite")
let userURL = NSPersistentStore.MR_urlForStoreName(userConfirgurationNameName + ".sqlite")
let options = [
NSMigratePersistentStoresAutomaticallyOption : true,
NSInferMappingModelAutomaticallyOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"]
]
do {
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: contentConfigurationName, URL: contentURL, options: options)
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: userConfirgurationNameName, URL: userURL, options: options)
NSPersistentStoreCoordinator.MR_setDefaultStoreCoordinator(persistentStoreCoordinator)
NSManagedObjectContext.MR_initializeDefaultContextWithCoordinator(persistentStoreCoordinator)
} catch {
print("Error adding persistent store to coordinator: \(error) ")
}
}
}
Note that in my code I'm referring to your concept of the "seed" store as "content" and the user-definable store as "user".
To accomplish the second aspect of your question, configuring the content store to not be backed up, you simply have to play around with the URLs where you store each store, placing the content store in a non-backed up temporary directory, and copying it to that location up launch from your app bundle if it doesn't exist.

Resources