Realm invalidated error even if invalidated == false - ios

I know that Realm can crash easily with invalidated object... however, I try to catch such state before pursuing with a given object and it usually works.
However, for my Request object sometimes I get a crash on realm.add(self) with "Adding a deleted or invalidated object to a Realm is not permitted".
I am not sure whether invalidated is a false negative or if my "custom" way of verifying an object has been deleted is wrong (I'm just adding the id of the object in a shared dictionary before actually deleting it).
I am a bit stuck on that one and it causes crashes in my app :(
if self.invalidated == false{
if let deleted = RequestHelper.sharedHelper.deletedRequests[id] where deleted == true{
return
}
let realm = try! Realm()
do{
try realm.write{
realm.add(self)
}
}catch{}
id = self.id
}
else{
print("realm invalidation")
}

I'm a little confused about the logic you're using here. You use realm.add() to add a new object to a Realm instance for the first time. Before then, checking if it's invalidated before that will most likely always return false because it shouldn't be backed by a Realm at that point yet.
If it's already backed by a Realm instance, it's also important to note that Realm write transactions happen sequentially. Only one may be open at a time, and each one after that will wait until the current one has finished. That being the case, it's feasible that self.invalidated was indeed false, but by the time you've opened the write transaction here, another write transaction may have just deleted it.
My recommendation would be to try and rely on Realm itself as much as possible for checking the deleted state of objects instead of relying on a custom mechanism. If id has been set as a primary key, you can use realm.objectForPrimaryKey(ObjectType.self, key: id) to very quickly check if it already exists in the Realm instance or not (Instead of trying to manage a separate list yourself).
Please let me know if you need any extra clarification.

Related

iOS15: NSPersistentCloudKitContainer: how to un-share objects?

I’m working on an app that uses the new sharing support in iOS 15 using NSPersistentCloudKitContainer. I do not see a way to tell Core Data that an object, or set of objects, is no longer shared.
For example, if I create a set of objects and then call share(_:to:completion:), the objects are shared properly and moved to a new, custom CKRecordZone, as expected.
Now, if the user stops sharing an object using UICloudSharingController, the CKShare that Core Data created is properly deleted from CloudKit, but the objects are still in the custom zone, and Core Data still has the original CKShare associated with those objects. So, when I call fetchShares(matching:), I still get the CKShare, but of course, that is no longer valid. In the past, with my own code, I’d use UISharingControllers delegate to get notified that the user stopped sharing, and then update my model. But there doesn’t seem to be a way to tell Core Data about the change.
Forcing Core Data to fetch CloudKit changes by either moving the app to background and then foreground, or by stopping the app and relaunching does not cause Core Data to notice the change to the share.
Does anyone know how to tell Core Data that these objects are no longer shared?
I worked around this by always checking to see if the share actually exists in CloudKit, rather than relying on the existence of a CKShare from fetchShares(matching:). I get the URL from the CKShare returned from fetchShares(matching:) and call this:
private func remoteShare(at url: URL) async throws -> CKShare? {
do {
let metadata = try await cloudKitContainer.shareMetadata(for: url)
return metadata.share
} catch let error as CKError {
if error.retryable {
throw RemoteError.retryable
} else if error.userInterventionRequiredError {
throw RemoteError.userInterventionRequired
} else if error.code == .unknownItem {
return nil
} else {
throw RemoteError.remoteFailure
}
} catch {
throw RemoteError.remoteFailure
}
}
}
If I get unknownItem that means there is no share on the remote, so the object is not actually shared.
The RemoteError is my custom error handling, and I have some extensions on CKError to categorize the errors.
Another workaround to the issue is to create a copy of the object being shared, and then deleting the original. It's not elegant, but it works in the meantime. (Hopefully Apple will address this at some point with a better solution.)
The copy will be placed in the default zone like it was before you shared the original. You'll be left with an empty share zone, but I dealt with that by running a background task upon app launch that deletes all empty share zones, so it doesn't get out of hand.
I haven't tried the solution posted by Dharman, but I imagine it's slower than querying the local cache. Using the method above, you can still use the "isShared" code from the demo app.

Can I make realm.writes directly in the Object data model?

I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.

CoreStore create object in context without saving to database

I want to solve next problem:
I would like to work with some NSManagedObject in context and change some properties in runtime, but without telling SQLite about any changes in it.
I just want to save NSManagedObject to database when I hit save button or similar.
As I found out from source code demo we need to use beginUnsafe for this purposes (maybe I am wrong)
func unstoredWorkout() -> WorkoutEntity {
let transaction = CoreStore.beginUnsafe()
let workout = transaction.create(Into<WorkoutEntity>())
return workout
}
let workout = unstoredWorkout()
workout.muscles = []
Now when I try to update workout.muscles = [] app crashes with error:
error: Mutating a managed object 0x600003f68b60 <x-coredata://C00A3E74-AC3F-47FD-B656-CA0ECA02832F/WorkoutEntity/tC3921DAE-BA43-45CB-8271-079CC0E4821D82> (0x600001c2da90) after it has been removed from its context.
My question how we can create object without saving it and how we can save it then when we modify some properties and avoid this crash as well.
The reason for the crash is that your transaction only lives in your unstoredWorkout() method, so it calles deinit, which resets the context (and deletes all unsaved objects).
You have to retain that unsafe transaction somewhere to keep your object alive - such as in the viewcontroller that will eventually save the changes.
But I would rather encourage you to think about that if you really want to do that. You might run into other synchronization issues with various context or other async transactions alive, like when API calls are involved.

Dealing with Realm transactions

I have an RESTful API from which I retrieve a large set of data. I am persisting it locally using Realm and the following call:
func addObjectType(object: ObjectType){
// Check for existence of data
if (realm.object(ofType: ObjectType.self, forPrimaryKey: object.id) == nil) {
// Persist your data easily
try! realm.write {
realm.add(object)
}
}
}
The app has a feature to delete the data locally. I have implemented it as following:
func deleteAllData() {
if(!realm.isEmpty){
do{
if(!realm.isInWriteTransaction) {
realm.beginWrite()
realm.deleteAll()
try! realm.commitWrite()
}
}
NotificationCenter.default.post(name: Notification.Name("updateUI"), object: nil)
}
}
However, looking at the Realm documentation I see the following:
Indicates whether the Realm is currently in a write transaction.
Warning
Do not simply check this property and then start a write transaction
whenever an object needs to be created, updated, or removed. Doing so
might cause a large number of write transactions to be created,
degrading performance. Instead, always prefer performing multiple
updates during a single transaction.
Is my implementation correct?
I feel that I am missing some checks..
Realm's general rule-of-thumb is that you should try to minimize as many write transactions as you can. This includes batching together multiple writes inside one block, and trying to avoid transactions all together if the date hasn't actually changed.
Realm write transactions are self-contained on separate threads. If a background thread is performing a write transaction, all other transactions on other threads will be blocked. As a result of that, it's not necessary to check isInWriteTransaction unless a write transaction is open on that particular thread.
So, no, you're not missing any extra checks. As long as you haven't accidentally left a write transaction open somewhere else, you can even reduce the number of checks you've got there. :)

Detach an Object from a Realm?

Lets say I have the following scenario...
ViewController1 loads a Person object from a Realm on the main thread and passes it to ViewController2. User interaction in ViewController2 causes the same Person object to change, but I only want to persist the changes once the User has pressed "Save".
Currently, when changing the passed Person object in ViewController2 a runtime error is thrown saying changes to an object need to be made in a Write block. This makes sense, but in this scenario I don't actually want to persist the changes right away.
Is there a way to detach an Object from a Realm to avoid these
checks?
If there isn't, what would be a suggested work around? (Copying the Object to a new instance? Tracking the changes to the Object separately and applying them later? Both seem pretty messy.)
Right now, you can make a 'standalone' copy of your object, via Object(value: existingObject) -- that'll probably be the simplest solution for now, until Realm adds something like nested transactions that will make undoing an arbitrary number of changes easier.
Realm has added a freeze() function for Realm Objects on RealmSwift 5.+ versions.
Imagine we have a Realm object Edible with a property name. Previously you weed need to hold a ThreadSafeReference to the object, get a realm on the other thread and unwrap the reference. Meh.
If you didn't, it would crash:
// Code running on main thread
let edible = realm.objects(Edible.self)[0]
DispatchQueue.global(qos: .background) {
let name = edible.name // Realm accessed from incorrect thread error
}
How to do detach an object in RealmSwift 5.+:
let edible = realm.objects(Edible.self)[0].freeze()
DispatchQueue.global(qos: .background) {
let name = edible.name // No longer crashes
}
Keep in mind as of Jan 11 2021, the freeze() function can create a lot of unknown weird errors affecting the users, as mentioned repeatedly in the issues for the new Realm version. For now, stay away from it. Use deep copying.

Resources