I recently managed to catch an extremely rare exception in my code and was hoping folks here could help me understand it. I have a game which is tracking elapsed time and persisting the value to Core Data every second. At the same time a user could be playing the game and causing the score to update, which is also saved in Core Data. I suspect that I should be serializing saves, but at the moment I'm not. Everything works great 99.99% of the time, but once in a while the call to context.save() throws an exception.
I have a merge policy set as so:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
And here's how I save elapsed time. The same pattern is used for updating a player's score.
func saveElapsedTime() {
let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
container.performBackgroundTask() { (context) in
let storedGame = context.object(with: self.storedGameID) as! StoredGame
storedGame.elapsedTime = Int32(self.elapsedTime)
do {
try context.save()
} catch let nserror as NSError {
print("saveElapsedTime: failed to save: \(nserror). userInfo: \(nserror.userInfo).")
}
}
}
The rare error I was able to catch looks like this:
Error Domain=NSCocoaErrorDomain Code=133020 "Could not resolve merge changes."
<snip>...with oldVersion = 463 and newVersion = 463...<snip>
I've seen plenty of posts where the old and new versions are off by one or a small number, and people recommend setting a merge policy which is different than the default. I'm guessing that in my case where oldVersion == newVersion it's because multiple threads are trying to save at the exact same time. Can someone confirm this just by looking at the error message? Should I be serializing all saves using an OperationQueue like in this example: NSPersistentContainer concurrency for saving to core data?
Thanks in advance.
My app is crashing whenever I try to save the Core Data MOC with a "Could Not Merge Changes" error. The baffling part to me is that it only occurs with a SQLite file that I seed to the app upon initial launch when the SQLite file has 40k+ records on one of the tables. The data displays perfectly fine in tableViews and I can update existing objects. I just can't save any new objects without crashing.
I've taken the same SQLite file (to maintain the same schema), removed all records, added back in a small quantity of records on the tables and everything works fine in the app -- I can move between view controllers, add new objects to the Core Data entities, and save the MOC anywhere.
I'm using a Core Data stack in a singleton. I've logged each request for the MOC and there is only one on the main thread. The app is very basic with no background tasks running. My build target is iOS 13.0.
The error is also confusing because the error isn't even on the new object that I'm inserting into Core Data. It is for some other existing object in Core Data and is different each time it crashes (For instance this would occur when I add a BetaCat or DeltaCat object):
Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x600000025480) for NSManagedObject (0x600003661900) with objectID '0xe3afe1928ab74f71 <x-coredata://E62171B1-E38F-4BF5-B012-DB1842171304/TextCategory/p4>' with oldVersion = 0 and newVersion = <deleted> and old cached row = {\n category = AlphaCat;\n isLocked = \"<null>\";\n}"
), NSExceptionOmitCallstacks=true}, ["conflictList": <__NSArrayM 0x6000007e35d0>(
NSMergeConflict (0x600000025480) for NSManagedObject (0x600003661900) with objectID '0xe3afe1928ab74f71 <x-coredata://E62171B1-E38F-4BF5-B012-DB1842171304/TextCategory/p4>' with oldVersion = 0 and newVersion = <deleted> and old cached row = {
category = AlphaCat;
isLocked = "<null>";
I've read everything that I can find on the merge change error and it almost always seems to be an issue related to multiple MOCs trying to save data and there being conflicts which doesn't seem to apply to my situation.
Does anyone have any idea why there are issues with the larger SQLite file and not the smaller one?
EDIT
Code example of how I get a MOC, set entity attributes, and save MOC:
let newCategory = Category(context: CoreDataStack.getContext())
newCategory.attribute1 = textField.text!
newCategory.attribute2 = true
CoreDataStack.saveContext()
getContext() simply returns a persistentContainer.viewContext
and saveContext() is a basic:
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
let nserror = error as NSError
fatalError("Unresolved error when saving MOC: \(nserror), \(nserror.userInfo)")
}
}
If your context is shared, it is best to set its mergePolicy in advance, like this:
yourContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy//OR: NSMergeByPropertyObjectTrumpMergePolicy
Note:
This post does not apply since I actually use CoreData.
In this post the last answer suggests to fetch all items in the new background thread before adding new objects, but this is done in my code.
This post suggests to unfault an item before its context is saved, but this is also done in my code.
My app uses CoreData to store objects called shoppingItems. I wrote a class CoreDataManager that initialises CoreData and has essentially one function to overwrite the currently stored items, and one function to fetch all items. Both functions operate in the background, i.e. on a separate thread.
Here is my code (irrelevant parts are left out).
I setup core data on the main thread:
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
})
return container
}()
This is the write function:
func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: #escaping (Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
// Delete currently stored shopping items
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
do {
let result = try backgroundContext.fetch(fetchRequest)
let resultData = result as! [NSManagedObject]
for object in resultData {
backgroundContext.delete(object)
}
if !shoppingItems.isEmpty {
// Save shopping items in managed context
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
nextCdShoppingItem.name = nextShoppingItem.name
}
}
let saveError = self.saveManagedContext(managedContext: backgroundContext)
completion(saveError)
} catch let error as NSError {
completion(error)
}
}
}
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
And this is the fetch function:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
guard !cdShoppingItems.isEmpty else {
completion([], nil)
return
}
for nextCdShoppingItem in cdShoppingItems {
}
completion(shoppingItems, nil)
} catch let error as NSError {
completion(nil, error)
}
}
}
In normal operation, the code seems to work.
The problem:
I also wrote a unit test that tries to provoke multi-threading problems. This test uses a concurrent dispatch queue:
let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)
A timer defines the test time.
In the scheme for tests, I have set the arguments -com.apple.CoreData.Logging.stderr 1 and -com.apple.CoreData.ConcurrencyDebug 1.
During the test time, overwriteShoppingItems and fetchShoppingItems are inserted repeatedly in the queue, and executed concurrently.
This unit test executes a few reads and writes, before it stops at the line
let itemName = nextCdShoppingItem.name!
because nextCdShoppingItem.name is nil, which should never happen, because I never store nil.
Immediately before the crash, the following is logged:
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)
If I do only fetches, or only writes, the CoreData warning is not logged. Thus it seems definitely to be a multi-threading issue.
However, CoreData.ConcurrencyDebug does not detect it.
It looks as if during a fetch operation on one thread, the other thread deletes the currently fetched item, so that its properties are read back as nil.
But this should not happen because fetches and saves are done with backgroundContext.performAndWait, i.e. serially.
And the stack trace shows that only a single thread accesses CoreData: Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)
My questions:
What is my misuse of the CoreData API, and how to do it right?
Why is an item sometimes read back as nil?
EDIT:
Maybe this helps to identify the problem: When I comment out backgroundContext.delete(object) in overwriteShoppingItems, the error is no longer logged, and no item is fetched as nil.
Okay so i got same error but only while "archiving" the project. Took me several hours to find the issue, since I was getting the error only on uploading, in the assets catalog, I had some duplicate images, by removing them the error is gone.
Anyone else getting this error AND your Coredata setup is fine, check your assets folder.
If you are not using core data in your project then check your assets files are proper or not.
I am also getting error like this then i have findout this error from my project.
Problem seems to be solved.
It happened apparently, because the functions overwriteShoppingItems and fetchShoppingItems both setup a separate background context with let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) with their own queue so that fetches and saves were not serialized by a single queue.
I modified my code now in the following way:
I have now a property
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
that is initialized as
self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
and fetches and saves are done using
backgroundContext.perform {…}
Now the CoreData error is no longer logged, and no item is fetched as nil.
In my case I created an new managedObjectContext without (yet) assigning a persistent store. Eventually I called -[psc metadataForPersistentStore:store] with the store being nil. This caused the log message to be written.
(side-note: you can always catch these outputs by setting a breakpoint on fprintf)
If you are seeing this during tear-down of a Core Data stack, check for un-removed observers or memory leaks.
In my case, I had managed objects which were each observing themselves – one of their relationships, using Swift's Key-Value Observing (NSKeyValueObservation objects). (Example use case: When the last a department's employees are deleted, delete the department.) The console messages appeared while the containing Core Data document was being closed. By breaking on fprintf I could see that the message was logged when a notification was being sent to an observer, and the number of messages was always equal to the number of non-faulted such objects in the managed object context (5 departments --> 5 console messages, etc., etc.). The problem was that I was not removing these observations, and the solution was of course to implement func willTurnIntoFault() in the observing objects, and in this func remove the observation.
In another situation, these messages stopped after I fixed a memory leak, but I did not study that in detail.
The full error messages were: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0)
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0) and they occurred during the call to NSPersistentStoreCoordinator.remove(_:). So, this error is telling me that the store's PSC was nil, but I checked this with the debugger and found that the store's PSC was not nil before the call. I filed a bug to Apple on this (FB11669843) asking if they could improve the error message.
I'm getting an intermittent bug that is proving very hard to debug.
I'm getting the following error from the following method
EXC_BAD_ACCESS(code=1, address=0x10) on Core Data Fetch
class func getAll(context: NSManagedObjectContext) -> [Tag] {
var returnValue: [Tag] = []
do {
let fetchRequest = NSFetchRequest(entityName: Tag.entityName())
returnValue = try context.executeFetchRequest(fetchRequest) as! [Tag]
} catch {
}
return returnValue
}
The bug is extermely intermittent, and is only happening on every few 100 sessions, but is appearing frequent enough that I need to deal with it. The code breaks on the line returnvalue = try context.execute...
From debugging, my fetchRequest is not nil
my context is not nil
returnValue has a default value of an empty array
my backgroundContext is running on a background thread
I've turned on the NSZombieFlag to try to see if some memory is deallocated somewhere and then being accessed, but I'm stumped on what is causing this. Any ideas or insight would be much appreciated.
Almost all EXC_BAD_ACCESS issues I've seen with Core Data are caused by trying to use thread concurrency instead of the newer queue concurrency model.
Since iOS 5 you are required to use performBlock or performBlockAndWait when accessing a managed object context.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Concurrency.html
So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.