CoreStore create object in context without saving to database - ios

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.

Related

What happens to local variables storing references to deleted NSManagedObjects

When I delete an NSMangedObject from the database, what happens to local variables who were assigned to it?
For example, I have a simple NSManagedObject:
class MyManagedObject: NSManagedObject {
#NSManaged var name: String
}
And then in my ViewController, I pull it out of the database, and assign it locally:
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
}
Then I delete it from the database.
If print the object name I get the following in the console
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
As if the object isn't there? But if I turn the variable into an optional and check it for nil, I am told it's not nil.
I'm not quite sure how to reconcile this in my mind. There seems to be something pointing to the local variable, but its properties are gone.
If I have many disparate UI objects that rely on that object for its properties, I can't assume that there is some local deep copy of it in memory?
Here is more complete code:
In viewDidLoad I create the new object, save the context, fetch the object, then assign it locally.
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
//1 Create the new object
let newObject = NSEntityDescription.insertNewObject(forEntityName: "MyManagedObject", into: coreDataManager.mainContext) as! MyManagedObject
newObject.name = "My First Managed Object"
//2 Save it into the context
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
//3 Fetch it from the database
let request = NSFetchRequest<MyManagedObject>(entityName: "MyManagedObject")
do {
let saved = try coreDataManager.mainContext.fetch(request)
//4 Store it in a local variable
self.myManagedObject = saved.first
} catch {
//handle errors
}
}
}
At this point if I print the local variable's name property, I get the correct response:
print("The object's name is: \(myManagedObject.name)")
//prints: The object's name is: My First Managed Object
So, now I delete it from the database:
if let storedObject = myManagedObject {
coreDataManager.mainContext.delete(storedObject)
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
}
But now, when I print I get the strangest output:
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
This is totally not the way I'm expecting memory to work. If I create a instance of a class Foo, and then pass that instance around to different objects, it's the same instance. It only goes away once no one is pointing to it.
In this case--- what is the variable, myManagedObject? It's not nil. And what is the string, name? Is it an empty string? Or is it some other weird meta-type?
The main thing what you are probably looking here for is the core data context. The context is a connection between your memory and the actual database.
Whenever you fetch the data you fetch it through context. These are managed objects which can be modified or even deleted. Still none of these really happen on the database until you save the context.
When you delete an object it is marked for deletion but it is not deleted from the memory, it must not be since if nothing else it will still be used by the context to actually delete the object from the database itself.
What happens to the managed object once you call to delete it is pretty much irrelevant, even if documented it may change as it is a part of the framework. So it is your responsibility to check these cases and to refetch the objects once needed. So you must ensure your application has a proper architecture and uses core data responsibly.
There are very many ways on how you use your database and more or less any of them has a unique way of using it optimally. You need to be more specific on what you are doing and where do you see potential issues so we can get you on the right track.
To give you an example consider data synchronization from remote server. Here you expect that data can be synchronized at any time no matter what user is doing or what part of the application he is.
For this I suggest you have a single context which operates on a separate thread. All the managed objects should be wrapped and its properties copied once retrieved from database. On most entities you would have something like:
MyEntity.findAll { items in
...the fetch happens on context thread and returns to main, items are wrappers
}
MyEntity.find(id: idString, { item in
...the fetch happens on context thread and returns to main, items are wrappers
})()
Then since you do not have any access to the managed object directly you need some kind of method to copy the data to the managed object like:
myEntityInstance.commit() // Copies all the data to core data object. The operation is done on a context thread. A callback is usually not needed
And then to save the database
MyEntity.saveDatabse {
... save happens on the context thread and callback is called on main thread
}
Now the smart part is that saveDatabse method will report to a delegate that changes have been made. A delegate is usually the current view controller so it makes sense to have a superclass like DataBaseViewController which on view did appear assigns itself as a delegate MyEntity.delegate = self, on view did load calls some method reloadData and in the databaseDidChange delegate method calls reloadData and same in viewWillAppear.
Now each of your view controllers that are subclass of DataBaseViewController will override the reloadData and in that method you will fetch the data from the database again. Either you are fetching all items or a single one. So for those singe ones you need to save the id of the object and fetch it again by that id. If the returned object is nil then item was deleted so you catch the issue you seem to be mentioning.
All of these things are oversimplified but I hope you get a basic idea about core data and how to use it. It is not easy, it never was and it most likely never will be. It is designed for speed to be able to access the data even from a very large database in shortest time possible. The result is that it might not be very safe.

How to delete object in Realm properly and thread-safe

I've just starting to using Realm and feel it's very good, fast except one thing: delete an object in Realm is easily cause an exception.
Is there any way I can delete an object in Realm safety?
In my project, I usually have to create, update, delete hundred objects on the background thread. The issue is:
If the app currently display/using one object on the main thread
In the background, I delete that object.
=> On the main thread will cause an exception when using that object's properties.
I know Realm has isInvalid method to check, but I cannot add the check in every assign properties code, it's look not good.
So, as of now, what I do is: instead of actually delete, I have a property call "deleted", and in delete, I only update that value. And on the UI, I will filtered out objects which have deleted = true
I wonder is there any way better to do this?
This is intended functionality. If a background thread deletes a Realm Object, the next time you try and access that object from any thread, an exception will be thrown.
In order to handle this, Realm provides a rich notification system that you can use to automatically receive alerts of when the contents of a Realm database have been changed and to update the UI accordingly.
If you've got a view controller that is displaying the contents of a single Realm Object, you could implement a system to be notified of any changes being made to your Realm database, and then check to make sure your object is still valid:
class MyViewController : UIViewController {
var myModel: Object = nil
var notificationToken: NotificationToken? = nil
init(model: Object) {
self.myModel = model
}
override fun viewDidLoad() {
super.viewDidLoad()
notificationToken = myModel.realm.addNotificationBlock { notification, realm in
guard myModel.invalidated == false else {
// The object has been deleted, so dismiss this view controller
}
}
}
deinit() {
notificationToken?.stop()
}
}
That notification block will be triggered each time a write transaction modified something in that particular Realm file (Even on background threads), which gives you a chance to check to see if your particular Realm Object in that UI hasn't been deleted. If it has, then you can simply dismiss the UI.
Depending on your specific needs, there is also a more fine-grained notification system you can use to specifically track changes to Realm Objects that were part of the results of a query. There's sample code for that in the Collection Notifications of the Realm documentation.
Please let me know if you need additional 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.

Why I don't get the same objects when using objectWithID with CoreData?

I have a NSManagedObjectContext where two NSManagedObject are saved.
I'm calling a method in another thread and I need to access those two NSManagedObject so I created a child context like the following:
let childManagedContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
childManagedContext.parentContext = self.managedContext
When I do:
let myNSManagedObject1 = childManagedContext.objectWithID(self.myNSManagedObject1.objectID) as! MyNSManagedObject
let myNSManagedObject2 = childManagedContext.objectWithID(self.myNSManagedObject2.objectID) as! MyNSManagedObject
myNSManagedObject1 and myNSManagedObject2 are not the same objects as self.myNSManagedObject1 and self.myNSManagedObject2. Can someone explain me why?
Plus if I use existingObjectWithID instead of objectWithID, it seems I still have a fault object for my relationship in myNSManagedObject1 and myNSManagedObject2:
relationShipObject = "<relationship fault: 0x170468a40 'relationShipObject'>"
Understand that they are the "same" in the sense that they refer to the same object in your object graph. If you compare all attributes, you will find that they are equal.
However, because they are in different contexts, they will be two separate instances of this object. So the machine address you see will be different. I hope that clears up the confusion.
As for the "fault", that only means that the underlying object (or attribute) has not yet been fetched into memory. This is simply an optimization mechanism to minimize memory footprint. If you were to log the object or attribute explicitly, it would be fetched from the store and displayed as expected. See "Faulting and Uniquing" in the Core Data Programming Guide.
You have one object, that is the version that's in Core Data. When you use objectWithID: you create an instance of that object. So, if you do it twice you get two instances of the same object. (Much in the same way that you can create two objects of the same class.)
Of course, if you try to save your context, having changed one but not the other, weird things might happen.
A common pattern is where you create a new "editing" managed object context and create a new instance there. Then if the user pressed Cancel, you can just delete the context and not have to worry about rolling back any changes. I can't think where having two instances on the same context would be useful.

get rid of temporary saved managedObjectContext

how can I manage temporary saved CoreData?
as soon as I do something like this:
var myClass: MyClass = NSEntityDescription.insertNewObjectForEntityForName("MyClass", inManagedObjectContext: (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!) as MyClass
it is part of my managedObjectContext and will be saved when i do a context.save(nil)
Is there any way to get an object of the NSManagedObject Class without messing with my current context.
In other words: I want to have optional objects that just end up unused when I don't save them explicitly and i want to have objects that I really want to save persistently.
This answer is late but hopefully helps someone else out there. Yes there is. You create a child context. Do anything you want there. If
you want to keep your changes, save the child context - which does NOT persist data. It notifies parent context of the changes, the changes are now in your main context. You can then perform save on the main context.
let childContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
childContext.parentContext = mainContext
guard let myClass = NSEntityDescription.insertNewObjectForEntityForName("MyClass", inManagedObjectContext: childContext) as? MyClass else {
// something went wrong, handle it here
return
}
// do your thing here
to let the main context know that you want to keep your changes:
do {
try childContext.save() // does not persist anything, just lets know the parent that we are happy with the changes
} catch {
// handle error
}
when you want to NOT keep your changes you can reset:
childContext.reset()
when you want to persist the changes on the main context you do as always:
do {
try mainContext.save()
} catch {
// handle error
}
There is no direct way u can get rid of updated object what you can do is update it again with real data (which is previous data before updating). whenever you update object of 'NSManagedObject' (core data entity object) 'NSManagedObjectContext' captures all the changes and it saves whenever you do save context.
In this type of use case better to use Sqlite database.
You can delete the objects from the managed context before you call context.save, which prevents them from being committed. Assuming you have an NSManagedObjectContext called context and an instance of MyClass called myClass, then the following will work:
context.deleteObject(myClass)
See:
NSManagedObjectContext.deleteObject
Discussion
... If object has not yet been saved to a persistent store, it is simply removed from the receiver.
Of course the decision about which objects to save and which to discard are up to the logic of your application. So before you call save, you need to check all of the inserted, but not yet, committed instances that are registered with the NSManagedObjectContext you're using. You can keep track of that in your application, or perhaps this will be of use:
NSManagedObjectContext.insertedObjects
The set of objects that have been inserted into the receiver but not yet saved in a persistent store. (read-only)

Resources