Realm Threading Confusion - ios

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.

Related

Firestore in Swift - Listener producing needless reads

I have an app that uses a snapshot listener to listen to data in a particular document. However, when a field in the document is updated, the data is read 7-10x over. Never read once, and never read the number of fields that are in my document, it always seems to be an arbitrary number. Also, when the read data prints out, it seems like every printout is the same except for a couple of fields that I'm not setting (like an array prints out "<__NSArrayM 0x282d9f240>" but the number changes on each print). As a result, minimal usage of my app is causing 5-10k reads. I'm trying to reduce the number of reads and I don't know exactly how, but the app has to read as data is updated, but my two questions are:
when I print the data from the listener, does each data print out signify a separate read operation? and
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated, then perform one read instead of multiple reads every time any field is updated? Or another strategy to reduce reads when multiple writes occur?
Not sure if this is helpful, but here is the code I'm using to perform the read...its pretty much the standard code from the firestore sdk:
env.db.collection(env.currentSessionCode!).document(K.FStore.docName).addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
self.env.data1 = data[K.FStore.data1] as? String ?? "????"
self.env.data2 = data[K.FStore.data2] as? String ?? "????"
self.env.data3 = data[K.FStore.data3] as? [String] ?? ["????"]
self.env.data4 = data[K.FStore.data4] as? [String] ?? ["????"]
self.env.data5 = data[K.FStore.data5] as? Double ?? 0
self.env.data6 = data[K.FStore.data6] as? Double ?? 0
self.env.data7 = data[K.FStore.data7] as! Bool
self.env.data8 = data[K.FStore.data8] as! Bool
print("Current data: \(data)")
Update - For clarification, the way I have been updating my data to firebase is with a environment object, and using "didSet" when the new data is changed/updated in the environment to update it on firebase...I think this might be the root of the problem, as the function called on didSet runs 4-5 times each time it is called...
relevant code:
#Published var data1: String {
didSet {
postValuesToFB(fb: K.FStore.data1, string: data1)
}
}
func postValuesToFB(fb: String, string: String) {
guard let code = currentSessionCode else {
fatalError("Error - Connection Check - no value for current session code in Global Env")
}
let docRef = db.collection(code).document(K.FStore.docName)
docRef.getDocument { document, _ in
guard let document = document else {
return
}
if document.exists {
let session = self.db.collection(code).document(K.FStore.docName)
session.updateData([
fb: string,
K.FStore.dateLastAccessed: FieldValue.serverTimestamp(),
])
return
}
}
}
Based on your comments, it sounds as if you've written no code to remove a listener after it's been added. Based on this, it's relatively safe to assume that your code could be adding many listeners over time, and each one is getting called for each change.
You should take a moment to think about the architecture of your app and figure out when is the appropriate time to remove listeners when they're no longer needed. Usually this corresponds with the lifecycle of whatever component is responsible for display of the data from the query. Review the documentation for getting realtime updates, especially the section on detaching a listener. It's up to you to determine the right time to remove your listener, but you definitely don't want to "leak" a listener as you are now.
A common source of unexpected read charges for developers who are new to Firestore is the Firebase console itself. When that console displays Firestore content, you are charged for those read too. To ensure you measure the impact of your code correctly, test it with the Firebase console closed.
when I print the data from the listener, does each data print out signify a separate read operation?
Not really. You get charged for a document read, when the document is read on your behalf on the server. You are not charted for printing the same DocumentSnapshot multiple times.
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated
Nope. To know the document has changed, the server needs to read it. So that requires a charged read operation.

Realm - database isn't up to date after a delete

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.

Error acessing list of Realm's objects: Realm accessed from incorrect thread

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)
}

Updating a many-to-many relationship in Core Data with Swift

I have the following Core Data model:
And I'm trying to update the many-to-many relationship between Speaker and TalkSlot from a JSON I receive from a REST API call.
I have tried dozens of ways, replacing my many-to-many by 2 one-to-many's, removing from one side or the other, but one way or the other I keep getting EXC_BAD_ACCESS or SIGABRT and I just don't understand the proper way to do it. Here is the last thing I tried:
for speaker in NSArray(array: slot!.speakers!.allObjects){
if let speaker = speaker as? Speaker {
speaker.mutableSetValueForKey("talks").removeObject(slot!)
}
}
slot!.mutableSetValueForKey("speakers").removeAllObjects()
if let speakersArray = talkSlotDict["speakers"] as? NSArray {
for speakerDict in speakersArray {
if let speakerDict = speakerDict as? NSDictionary {
if let linkDict = speakerDict["link"] as? NSDictionary {
if let href = linkDict["href"] as? String {
if let url = NSURL(string: href) {
if let uuid = url.lastPathComponent {
if let speaker = self.getSpeakerWithUuid(uuid) {
speaker.mutableSetValueForKey("talks").addObject(slot!)
slot!.mutableSetValueForKey("speakers").addObject(speaker)
}
}
}
}
}
}
}
}
If it helps, the API I'm using is documented here as I'm trying to cache the schedule of a conference into Core Data in an Apple Watch extension. Note that I managed to store all the rest of the schedule without any issue. But for this relationship, each time I try to update it after storing it the first time, I get an EXC_BAD_ACCESS (or sometimes a SIGABRT), at a random place in my code of course. Any idea what I'm doing wrong?
OK, after reading a few other questions associating Core Data EXC_BAD_ACCESS errors and multi-threading, I noticed that I was doing my caching on a NSURLSession callback. Once I called my caching function on the main thread using the code below, the EXC_BAD_ACCESS errors completely disappeared and now the data seems to be saved and updated properly:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.cacheSlotsForSchedule(schedule, data: data)
})

Saving Realm objects on interactive UI

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)
}
}

Resources