I'm receiving an RLMException for the following reason:
Attempting to create an object of type 'Student' with an existing
primary key value '258975085-504336622-62850'.
The confusing part is that it's occurring just after a check that there are no existing objects with this key in the Realm.
let realm = try Realm()
if let info = realm.object(ofType: Student.self, forPrimaryKey: newStudent.userId) {
try realm.write {
info.name = newStudent.name
info.school = newStudent.school
info.email = newStudent.email
}
}
else {
try realm.write {
realm.add(newStudent) //RLMException occurs here
}
}
This code is all running asynchronously on the GCD utility queue, inside a do/catch block. It's triggered by a button in the user interface, but nothing else is accessing realm at the same time.
Why could that if statement allow the else code to run?
try! self.realm.write {
self.realm.add(newStudent, update: true)
}
You're adding same Object (student) with existing primary key. So you can just update current one. Instead of deleting and adding new one.
In my case, I added condition to check whenever new user logs in:
if newStudent == nil{
self.realm.add(newStudent, update: .all)
}
Answering my own question because I found the problem.
I think the problem was a previous app screen trying to save (the same) student object on a queue with Utility quality of service, meaning that it finished saving after the call to realm.object(...), but before the call to realm.add(...).
Moving the if statement inside the realm write transaction also helped (thanks EpicPandaForce).
Related
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.
So, for the purposes of saving space and caching, I defined a Photo model in CoreData that has an attribute imageDataURL (a fileURL).
This data would be stored in the Documents directory. As such, I want to make sure I am cleaning up that data if the user deletes the Photo object.
My question is, where should I look to have a deleteDataAtImageURL(...) method?
I'm thinking it would be in the prepareForDeletion() method on NSManagedObject and I check if that object's context's parent is nil. This tells me that it is a context directly in contact with the Persistent Store.
This should work, unless of course the user resets the context and doesn't save it.
I can't imagine I'm the first one to want to do this, so any advice on this approach (or a better one!) would be appreciated!
I had a similar issue. Here's how I solved it by using a NotificationCenter observer for an action on my root saving context.
//done as part of a singleton class setup
NotificationCenter.default.addObserver(self, selector: #selector(SingletonClass.handleModelDataChange), name:NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: yourRootSavingContext)
internal func handleModelDataChange(notification: NSNotification) {
//get documents directory
let documentsURL = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
//get deleted items from dictionary
if let deleted = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> , deleted.count > 0 {
for object in deleted{
//sort out your objects of interest, I cared about objects of a certain class
if ...{
do {
//delete filepath
try FileManager.default.removeItem(at: documentsURL.appendingPathComponent(myPathComponent, isDirectory: true))
//print("Deleted the folder \(documentsURL.URLByAppendingPathComponent(myPathComponent, isDirectory: true))")
}
catch {
//print("I tried :(")
print(error)
}
}
}
}
}
In the end the solution for me was to override prepareForDeletion and check if the the object's .managedObjectContext.parent property is nil. That tells me that it's connected to the PersistentStore, and there I can do the task I want to.
I don't know if this is best practices, but it is working.
I believe the above answer from sschale would also work although I didn't try it.
I've got some objects in my Realm database. I need to delete an object from it.
When I delete my object from my Realm() instance, the objects are well deleted.
But after performing the delete request, I need to retrieve all objects from the database, but here surprise, the object is still here.
I think I have a thread problem, or something like that.
I don't know where to investigate.
My simplified code :
My delete method :
func myDeleteFunc(completion : ()->Void){
let realm = try! Realm()
// ...
try! realm.write {
realm.delete(myObject)
}
completion()
}
// Here the object have been deleted from the realm instance, no problem
This method is called from a viewController where I execute the completion block.
This completion block contains the request that retrieve all objects from my Realm database :
The viewController that executes the method and the completion block :
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
I think my 2 realm instances differs, or have problems between threads because I have a DispatchQueue.main.async.
Any ideas ?
EDIT :
I noticed that when I check with breakpoints, sometimes it works.
So maybe that the delete request have not been committed yet, and that I retrieve the objects before the end of the delete request ?
Make sure you put the async block in an autorelease pool:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
Should be
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
}
Make sure you do this autoreleasepool { ... } wrap for any background thread where you create a Realm instance, primarily in the GCD.
If that still doesn't work, you can do:
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
Realm isolates transactions on each thread to avoid changes from one thread immediately affect another. This mechanism also avoids the "faults" inherent to ORMs.
In your code, you can choose to refresh (advance) the realm to the latest state at points that you control and can handle data before and after the refresh being different.
Realms on a thread with a runloop (such as the main thread) auto-advance on every iteration of the runloop by default.
In your code sample, you invoke DispatchQueue.main.async immediately after a commit from another thread, which means that if you already have a Realm on the main thread, the async block will be at the same state and won't include the last commit.
But you can call Realm.refresh() explicitly at the start of your async block, which will ensure that this block sees that last commit:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
realm.refresh()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// ...
}
}
See Realm's docs on Seeing Changes From Other Threads for more information.
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)
}
}