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.
Yes of course I did search in the whole internet .
But can't get out of this issue.
I got two entity named:
Post & PopularPost.
(Almost duplicate of one another)
When I fetch the Post and update it's properties like numberoflikes,numberofcomments it's good.
But when I fetch the PopularPost and try to update it's properties then it says
"optimistic locking failure"
MY code to fetch and update and save the entity:"PopularPosts".
let postpopFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PopularPosts")
postpopFetch.predicate = NSPredicate(format: "id = %#", postId)
let resultp = try? CoreDataManager.sharedInstance.managedObjectContext.fetch(postpopFetch)
let resultDatap = resultp as! [PopularPosts]
for object in resultDatap {
print(object.numberOfHearts)
if like {
object.numberOfHearts = object.numberOfHearts + 1
object.isLiked = true
}else{
(object.numberOfHearts > 0) ? (object.numberOfHearts = object.numberOfHearts - 1) : (object.numberOfHearts = 0)
object.isLiked = false
}
}
do {
try CoreDataManager.sharedInstance.managedObjectContext.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
Generally optimistic locking failure is caused by two different managed object contexts trying to change the same data. I think you can also have this issue even with one context if you are inappropriately accessing it from different threads at the same time. ManagedObjectContexts are not
thread safe neither for reading or for writing.
I have seen situation that there is access to a managedObjectContext from the wrong thread and there is a crash much later when on a line of code that is doing nothing wrong. I would recommend to careful search your code for any access to core-data that is not on the main thread. You use [NSThread isMainThread] to check if you are not on the main thread for debugging.
In my case this was caused by using a newBackgroundContext().
I had a potentially long running task which started when the app went into the background.
Even though it completed before using the app again, I found that all saves made after the background task would fail.
I solved the issue by using the viewContext for the long running task.
I have a list of points of interest. This points were loaded from a Realm database. Each point should present its distance to the user's position.
Each time I get a new location, I calculate the distance to all points. To avoid a frozen screen, I was doing the math in a background thread, after i display the list in a table in the main thread.
func updatedLocation(currentLocation: CLLocation) {
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
for point in self.points{
let stringDistance = self.distanceToPoint(currentLocation, destination: point.coordinate)
point.stringDistance = stringDistance
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView?.reloadData()
})
})
}
However I get this error:
libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.
I know i am getting this error because I'm accessing the realm objects in a background thread, however, they are already loaded into an array and I never make a new query to the database.
In addition, the var i'm updating his not saved into the database.
Any idea how to solve this? I wanted to avoid doing the math in the main thread.
thanks in advance
I assume you wrap Realm Results objects into Array like the following:
let results = realm.objects(Point)
self.points = Array(results)
However, that is not enough. Because each element in the array is still tied with Realm, that cannot be access another thread.
A recommended way is re-create Realm and re-fetch the Results each threads.
dispatch_async(backgroundQueue, {
let realm = try! Realm()
let points = realm.objects(...)
try! realm.write {
for point in points{
let stringDistance = self.distanceToPoint(currentLocation, destination: point.coordinate)
point.stringDistance = stringDistance
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
...
})
})
Realm objects have live-update feature. When committed changed to Realm objects on sub-thread, those changes reflect to the objects in other thread immediately. So you do not need to re-fetch the query in the main thread. What you should do is just reload the table view.
If you'd like to wrap array and pass it to other thread directly, you should wrap all elements of resutls as follows:
let results = realm.objects(Point)
self.points = results.map { (point) -> Point in
return Point(value: point)
}
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.
I have a table view and once a cell is about to become visible I load its image. When the image is loaded a model holding its NSData (which inherits from Object) should be written to a database.
I have tried two ways:
Wait until all images are loaded and then write the data to the
database.
Write each loaded image's model to the database.
The first one requires either to scroll through the whole table view (if we load images lazily) or to load images on viewDidLoad() which isn't the best choice either.
The second way is good, as soon as an image is loaded its model eventually updates. But Realm freezes the UI on write() function.
I've tried to use an asynchronous main queue for writing but this produces short glitches each time Realm performs the write operation. I also tried to use a UserInitiated asynchronous queue but this only caused my app to crash...
let queue = NSOperationQueue()
queue.qualityOfService = .UserInitiated
// this code is executed on imageDidLoad()
queue.addOperationWithBlock {
let realm = Realm()
realm.refresh()
realm.write {
realm.add(downloadable!, update: true)
}
}
As a result I'm getting:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object is already persisted in a Realm'
What a solution might be?
It's hard to tell without more context, but it appears that downloadable is already a persisted Object, so trying to use it on another thread won't actually work. If downloadable has a primary key, you might want to use that instead, doing something akin to the following:
let queue = NSOperationQueue()
queue.qualityOfService = .UserInitiated
let primaryKey = downloadable.primaryKey
// this code is executed on imageDidLoad()
queue.addOperationWithBlock {
let realm = Realm()
realm.refresh()
realm.write {
realm.create(DownloadableClass.Self, value: ["primaryKey" : primaryKey, "imageData" : imageData], update: true)
}
}