get rid of temporary saved managedObjectContext - ios

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)

Related

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.

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.

Re-inserting NSManagedObject to ManagedObjectContext

I have this instance of a NSManageObect that I create without a valid context just to use it to hold data and pass it around
convenience init() {
let entityDescription = NSEntityDescription.entityForName("UserEntity", inManagedObjectContext:managedContext)
self.init(entity: entityDescription!, insertIntoManagedObjectContext: nil)
}
But sometimes it's handy for me to actually let them be tracked (saved) by Core Data as well. In those instances I do the following to add it to core data managed object context
myManagedContext.insertObject(myUserEntityObject)
This all works great.
My question is, does is actually matter whether if I re-insert the same reference to myManagedContext couple of times? Is there any down sides to this re-insertion? in my mind it shoudln't make a difference as it's inserting the same object reference.
It's safe as long as two conditions are true:
It's the same managed object context
The managed object's ID is still a temporary ID (i.e. the managed object hasn't been saved yet).
It would be safer to make the insert call look something like
if myUserEntityObject.objectID.isTemporaryID {
myManagedContext.insertObject(myUserEntityObject)
}

Use NSManagedObject in child NSManagedObjectContext instead of its parent

I have two MOCs: the first is the root context. When I save this context, the changes are saved to the persistent store coordinator. The second MOC has the first MOC as the parent. When I save the second MOC, I also have to save the first MOC in order to save the changes in the second MOC to the persistent store coordinator.
I use the second MOC to let the user edit an object. He can save or cancel the changes. When he saves the changes, all MOCs are saved. When he cancels the changes, I call rollback() of the second MOC.
Unfortunately, the object comes from the first MOC. This means, I execute an NSFetchRequest to fetch the object on the first MOC. Then I create the second MOC in which the user can edit the object. But there is a problem: when the second MOC should change something, for example delete an object that is contained in an array of the original object the user wants to edit, this is not possible, because a MOC can only delete objects that have this MOC as the context. But the object was fetched in the first MOC.
That's why I need to "transfer" somehow the object from the first MOC to the second MOC before the user edits the object. I don't want to fetch the object again with a NSFetchRequest or something, there must be a better way…
Is this possible? Or do you recommend to do this completely different, maybe without parent contexts?
This is where the objectID property of NSManagedObject will come in handy.
Ask the object for its ID
let objectID = myManagedObject.objectID
Ask the child context for a managed object with that ID
do {
let childManagedObject = try childContext.existingObjectWithID(objectID)
print("\(newObject)")
} catch {
}
I think you might be complicating your time with this. Unless it is completely necessary for proposed changes to also be saved there should be no reason to have two contexts for this. There are multiple ways for you to handle a temporary data which you can use to compare your actual record to without having it stored twice.
Why don't you just create a copy of the NSManagedObject and handle the information correction by comparison or simply replacing the original NSManagedObject with the data of the copy and then save it? I personally like this setup a bit more since all I have to do is either compare individual properties when update is desired.
When a full update is required than you can simply work directly on the one NSManagedObject without worrying about copies since you will probably be replacing the entire thing anyway. Like I said, there are other ways to handle, but if it is absolutely necessary for you to have both contexts then looking for a comparison of each property value to the replaced value and then simply save the one in the parent context.

NSManagedObjectContextWillSaveNotification unable to understand if object is deleted

I'm using private child queue (parent queue = NSMainQueueConcurrencyType) to delete an object and in my controller I'm listening to NSManagedObjectContextWillSaveNotification.
The goal is to understand if a given object (that is displayed in controller) was deleted. Object is fetched in main queue (with NSMainQueueConcurrencyType).
Here is the code:
let savingContext = notification.object as! NSManagedObjectContext
let deletedObjects = savingContext.deletedObjects
if deletedObjects.contains(myModel!) {
myModel = nil
}
After deleting object I get the notification and my objects exists there. The only problem, that they have different objects
deletedObjects: [<model: 0x7fab705e5ca0> (entity: model; id: 0xd000000000b00002 <x-coredata://B60C4E0B-EE90-49DE-8E51-3A3C75763994/Model/p44> ; data: {
// same values here
})],
mymodel:(<model: 0x7fab705e2090> (entity: model; id: 0xd000000000b00002 <x-coredata://B60C4E0B-EE90-49DE-8E51-3A3C75763994/Model/p44> ; data: {
// same values here
}))
So myModel = nil is never executed. What am I doing wrong?
You should be careful about what you are doing here, as it looks like you may be building a Rube Goldberg machine... and one that may end with a nasty explosion.
Since you didn't give enough details in your question, I have to make some assumptions... and hope it doesn't bite me...
Anyway, it appears that you are watching the NSManagedObjectContextWillSaveNotification for the child context, and comparing the deleted object with an object obtained from the main/parent context. This will not work (and can be dangerous to boot). The two contexts contain completely separate objects.
You should compare the NSManagedObjectID objects instead of the managed objects themselves.
Better yet, if you want to know when the child has deleted an object in the parent MOC, you could monitor the NSManagedObjectContextObjectsDidChangeNotification in the main/parent MOC. Note that this notification will occur anytime you change the MOC yourself, and not just when the child context saves.
However, the notification carries a nice set of information in its userInfo dictionary, including a set of deleted objects.

Resources