Realm write fails on second time being called - swift

I have a service for retrieving data from an api, it returns json that I map and then populate to Realm and finally display this to the view.
I have way for a user to force refresh the retrieval of data, which means I need to update my Realm data aswell. This works fine on the first time I call the method. But if I try to do it again it crashes every single time with this exception.
*** Terminating app due to uncaught exception 'RLMException', reason: 'Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
This is how my function works:
private func writeCollection(someKey: String?) {
let realm = try! Realm()
try! realm.write {
realm.add(someObject[someKey!]!, update: true)
}
}

This error occurred inside a different function then initially thought. I was holding a dictionary like this [someKey:someObject] and using this dictionary as a reference to write from.
Problem was this was outside the try! realm.write
// removed this reference
// var dictionary = [String:SomeObject]()
realm.write {
let someObject = SomeObject()
someObject.id = someKey
realm.add(someObject, update: true)
}

Related

SwiftUI Query Realm Database

In my Realm DB for ChatMessage I have the following data object:
When i follow the tutorial on retrieving data/query : https://www.mongodb.com/docs/realm/sdk/swift/crud/read/
let realm = try! Realm()
let specificPerson = realm.object(ofType: ChatMessage.self,
forPrimaryKey: ObjectId("6369ee9db15ac444f96eb5d6"))
print(specificPerson!.author as String)
I receive fatal error Nil, it cannot find anything. from the line
print(specificPerson!.author as String)
LiveChat/ChatView.swift:59: Fatal error: Unexpectedly found nil while unwrapping an Optional value
When I do a broader query for all,
let chatM = realm.objects(ChatMessage.self)
print(chatM)
print(type(of: chatM))
I receive empty object
Results<ChatMessage> <0x7fe4d163efa0> (
)
Results<ChatMessage>
I am adding the Chat messages Via
#ObservedResults(ChatMessage.self,
sortDescriptor: SortDescriptor(keyPath: "timeStamp", ascending: true)) var messages
private func addMessage(){
let message = ChatMessage(room: room, author: userName, text: messageText)
$messages.append(message)
messageText = ""
}
Similar to https://github.com/mongodb-developer/LiveTutorialChat/blob/main/iOS/LiveChat/LiveChat/Views/ChatsView.swift
Regarding your updated code:
So yes, you are indeed creating the objects (using the .append(_:) method on an #ObservedResults collection) in a correct way. This means that you are likely opening the wrong realm database when you're querying for the objects. Please have a look at Realm's documentation regarding realm configuration, specifically on how to set the default configuration. Calling try! Realm() without any parameters will use this default configuration to open the database.
Original reply
If let chatM = realm.objects(ChatMessage.self) returns an empty Results object, the realm you're querying does not contain any objects of the ChatMessage type. It's as simple as that. Logically, let specificPerson = realm.object(ofType: ChatMessage.self, forPrimaryKey: ObjectId("6369ee9db15ac444f96eb5d6")) would then also return nil.
Without seeing how and where you're creating the missing ChatMessage objects, it's hard to say what's going wrong. Some common missteps:
You are querying the wrong realm database: If you are only every accessing realm through let realm = try! Realm() this shouldn't be a problem.
You haven't actually added any ChatMessage object to the realm database: Simply initializing an object is not enough, You need to explicitly add objects to the Realm database using either Realm.create() or Realm.add():
let realm = try! Realm()
let exampleObject = ChatMessage()
print(realm.objects(ChatMessage.self).count) // Prints "0"
// Add to the realm using either create() or add()
realm.add(exampleObject)
print(realm.objects(ChatMessage.self).count) // Prints "1"

Object has been deleted or invalidated. (Realm)

Please refer Error: Object has been deleted or invalidated. (Realm)
I encounter this error with both 2 cases also.
I try to find the DBProduct before delete, but it also got Error: Object has been deleted or invalidated. Is this wrong? Please help me. I call this method in block of Alert view as Case 2.
let realm = try! Realm()
try! realm.write {
let dbProduct = realm.objectForPrimaryKey(DBProduct.self, key: product.id)
if dbProduct != nil {
realm.delete(dbProduct!)
}
}
Update: This issue happens on iOS8 only, and it is OK on iOS 9.
Normally, that error should only be thrown if you try and access a property of a Realm object that has been deleted, or if you explicitly told its parent Realm object to invalidate.
Like James said, it's quite likely that your product variable there has already been invalidated, in which case trying to call product.id would likely cause that crash.
If that's the case, then the easiest thing to do to fix this would be to avoid using the product variable and instead, simply making a copy of the value of id directly. This way, if the object is deleted/invalidated, you still have its primary key in which you can test to see if it still exists.
On a side note, this code could certainly be made a bit more efficient as well. It's not necessary to perform queries inside write transactions and you should only open a write transaction if there actually was an object to delete (Write transactions are pretty heavy things, so they should be avoided as much as possible).
let productID = product.id //save a copy of the ID in case 'product' gets deleted.
let realm = try! Realm()
let dbProduct = realm.objectForPrimaryKey(DBProduct.self, key: productID)
if dbProduct != nil {
try! realm.write {
realm.delete(dbProduct!)
}
}
I hope that helped!

Swift mutable dictionary being treated as immutable

I am trying to implement In App Purchases, and I am tracking which purchases a user has purchased via NSUserDefaults. I have a function that sets the values of each purchase, but when it runs, I get an error saying that I am mutating the dictionary of purchase values even though the dictionary is declared with a var instead of a let and is an NSMutableDictionary. Sometimes it does work, but most of the time it doesn't. I get a few warnings about declaring my variables with let instead of var, but I ignore them to give my variables maximum mutability. Why does this not work?
The error I get is:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'
Code:
static func setPurchased(purchase:PurchaseID, value:Bool)
{
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.objectForKey(PURCHASES_KEY) == nil)
{
initializePurchases() // go set some initial values
}
if var purchases = (defaults.objectForKey(PURCHASES_KEY) as? NSMutableDictionary) // WARNING: should be declared with let because not mutated
{
print("setting purchase \(purchase.id()) to \(value)")
var key = purchase.id() // WARNING: should be declared with let because not mutated
purchases[key] = value // CRASH HERE
defaults.setObject(purchases, forKey:PURCHASES_KEY)
defaults.synchronize()
}
}
This is not the right way of converting an immutable dictionary into its mutable counterpart.
var already ensures that whatever is returned from defaults.objectForKey(PURCHASES_KEY) will be copied as a mutable type so all you need is specify the type of the mutable object which in our case can safely be Dictionary<String: AnyObject> if you are sure all keys are String type:
if var purchases = defaults.objectForKey(PURCHASES_KEY) as? Dictionary<String: AnyObject> {
...
purchases[key] = value
}
Please see this SO question for more information about mutability/immutability in collection types.

error: execution was interrupted, reason: breakpoint 1.2. Xcode 7.1, Swift

So the context is that I made a realm object and is giving one of it's variables a value, to do this I go ahead and call an instance of this object, then I connect to my server, get some value, then say something like
let someObject = someObjectClass() //this being a realm object class
someQuerySuccessBlock { (success, error) -> void in
...
if let someValue = objects[0].value {
someObject.id = someValue //this line is where the issue is
}
...
})
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction
realm.addObject(someObject)
realm.commitWriteTransaction
Error in llvm is error: execution was interrupted, reason: breakpoint 1.2.
Error does not show unless i make a breakpoint for all exceptions.
Also to note that code does compile, does run, will not cause a crash (but simply.. not run any of the code from that specific line onwards. That someObject does get saved and created, but the field that is to be assigned simply did not get assigned, etc
After some testing around, turns out this is because the realm object was already saved into Realm, where as the query block is async, it was trying to write into a variable of an object that had already been added.
Seems like the error was only like this because what I was trying to edit was the primaryKey of the object?
My fix:
let someObject = someObjectClass() //this being a realm object class
someQuerySuccessBlock { (success, error) -> void in
...
if let someValue = objects[0].value {
someObject.id = someValue //this line is where the issue is
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction
realm.addObject(someObject)
realm.commitWriteTransaction
}
...
})
If you try to edit the primary key of a saved object, then you will hit an assertion. Primary keys in Realm are immutable. Depending on your exact needs for your use case, you may want to create a new instance of your object class and assign all new properties which should be saved. You can add this new object then in a write transaction with -createOrUpdateInRealm:withValue:. Note: Be careful with to-one relations and other nullable properties as the merging strategy is here that null values are overwritten.

Delete all data from specific Realm Object Swift

Before i get too far into my question. My goal, which may influence your answers, is to remove Object data if it is no longer in the cloud.
So if I have an array ["one", "two", "three"]
Then in my server I remove "two"
I want my realm to update the change.
I figure the best way to do this is to delete all data in the specific Object, then call my REST API to download the new data. If there is a better way, please let me know.
Okay so here is my problem.
I have an Object Notifications()
every time my REST API is called, before it downloads anything I am running this:
let realm = Realm()
let notifications = Notifications()
realm.beginWrite()
realm.delete(notifications)
realm.commitWrite()
I get this error after running: Can only delete an object from the Realm it belongs to.
so i tried something like this:
for notification in notifications {
realm.delete(notification)
}
realm.commitWrite()
The error I get within xcode is this: "Type Notifications does not conform to protocol 'SequenceType'
Not really sure where to go from here.
Just trying to figure out realm. Completely new to it
Note: realm.deleteAll() works, but I don't want all of my realm deleted, just certain Objects
You're looking for this:
let realm = Realm()
let deletedValue = "two"
realm.write {
let deletedNotifications = realm.objects(Notifications).filter("value == %#", deletedValue)
realm.delete(deletedNotifications)
}
or perhaps this:
let realm = Realm()
let serverValues = ["one", "three"]
realm.write {
realm.delete(realm.objects(Notifications)) // deletes all 'Notifications' objects from the realm
for value in serverValues {
let notification = Notifications()
notification.value = value
realm.add(notification)
}
}
Although ideally, you'd be setting a primary key on Notifications so that you can simply update those existing objects rather than taking the extreme approach of nuking all your local objects simply to recreate them all (or almost).