Updating Realm Object in Swift from in Different thread/session - ios

After saving the realm objects i doing the following
try! self.realm.write{
article.imageUri = path
try! self.realm.commitWrite()
}
although I can see the value while i am still in the same session of the simulator, after restarting or rerunning the app the update value in the snippet above doesn't show, but the rest of the data is there
this update is done after fetching data using Alamofire result

First thing, when you call write with a block, you don't need to specify try! self.realm.commitWrite() inside the block. That's automatically called for you as per Realm's example:
try! realm.write {
realm.add(myDog)
}
You only need to call self.realm.commitWrite() when you have previously called self.realm.beginWrite()
Secondly, It's hard to say exactly why you might not be seeing the data update with more context. What's article? Where's that being set? How is the Realm being loaded/stored? Are you deleting the Simulator app in between runs? There's a lot of variables here.

Related

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.

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. :)

Realm invalidated error even if invalidated == false

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.

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.

Core Data: how to just delete and rebuild the data store?

I'm using Core Data in an iOS 7+ app that does not need to save user's data, all data the app needs is requested to services and it can be recovered at any time. So, if I change my data model in a next app update, I have no problem in deleting all the previous data and requesting it all again. But I don't know how to simply replace the previous data model with the new one, without performing a migration since it looks that I don't need to do that...
Thanks in advance
Working Swift solution if you target iOS 9 or higher
The shared CoreData manager:
class CoreDataContext {
static let datamodelName = "CoreDataTests"
static let storeType = "sqlite"
static let persistentContainer = NSPersistentContainer(name: datamodelName)
private static let url: URL = {
let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).\(storeType)")
assert(FileManager.default.fileExists(atPath: url.path))
return url
}()
static func loadStores() {
persistentContainer.loadPersistentStores(completionHandler: { (nsPersistentStoreDescription, error) in
guard let error = error else {
return
}
fatalError(error.localizedDescription)
})
}
static func deleteAndRebuild() {
try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: storeType, options: nil)
loadStores()
}
}
call loadStores only once in the appDelegate and deleteAndRebuild when you want to delete and rebuild the database :)
Case 1: You're using a SQLite store
This applies if your store type is NSSQLiteStoreType. Even if you plan to delete your data from time to time, it's not a bad idea to stick to SQLite, as it gives you the flexibility to keep your cached data on disk as long as you wish, and only delete it when you change your model and you don't want to apply any migrations.
Quick solution? Delete your NSPersistentStoreCoordinator's store at startup, when you're initializing Core Data.
For instance, if you're using the default SQLite store, provided by Apple's boilerplate code:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"cd.sqlite"]
you can simply delete the file:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
then use storeURL to add a new persistent store as usual.
Your NSPersistentStoreCoordinator won't complain if you have a new model and you won't need any migrations, but your data will be lost, of course.
It goes without saying that you can decide to use this solution when you detect a change in the data model, leaving the store alone if there's no change, so that you can preserve your cached data as long as necessary.
UPDATE:
As suggested by TomHerrington in the comments, to be sure you've removed the old store completely, you should also delete journal files, which might come back and haunt you in the future, if you don't take care of them.
If your store file is named cd.sqlite, like in the example, the additional files to be removed are cd.sqlite-shm and cd.sqlite-wal.
WAL journaling mode for Core Data was introduced as the default in iOS 7 and OSX Mavericks, as reported by Apple in QA1809.
Case 2: Use an in-memory store
As suggested, you could switch to an in-memory store, using NSInMemoryStoreType instead of NSSQLiteStoreType. Erasing the store is much easier in this case: all your data resides in memory, all of it will disappear when your app stops running, nothing will remain on disk for you to clean. Next time, you could potentially load a completely different model, without any migrations, as there's no data to migrate.
However, this solution, implemented as it is, wouldn't let you cache data between sessions, which looks like something you'd like to do between app updates (i.e., the store must be erased only when the app is updated and the model changes, while it could be useful to keep it on disk otherwise).
Note:
Both approaches are viable, with their pros and cons, and I'm sure there could be other strategies as well. In the end, you should have all the elements to decide what the best approach would be in your specific case.
I think destroyPersistentStoreAtURL method of NSPersistentStoreCoordinator is what you want.
It will remove the data store and journal files and all other things that need to be removed.
Look at Apple documentation.

Resources