NSManagedObject ignoring changed transformable - ios

I have a very basic caching system for some objects that conform to NSCoding. A method takes the object, extracts some information for keys and search parameters, creates or updates an NSManagedObject, and saves it to the persistent store:
+ (void)updateQuestion:(StacManQuestion *)question site:(StacManSite *)site
{
// 1. Look up with a basic predicate.
SECachedQuestion *cachedQuestion = [SECachedQuestion
findFirstWithQuestionId:question.questionId site:site];
// 2. Create if missing.
if (!cachedQuestion) {
cachedQuestion = [SECachedQuestion MR_createEntity];
cachedQuestion.questionId = question.questionId;
cachedQuestion.site = site.apiSiteParameter;
}
// 3. Update properties.
cachedQuestion.isFavorite = question.favorited;
cachedQuestion.lastAccessTime = [NSDate date];
cachedQuestion.question = question;
// 4. Save
[[NSManagedObjectContext MR_contextForCurrentThread]
MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
/* ... */
}];
}
The first time this code runs, it works perfectly. The object gets saved to the cache and is instantly available for the next run. After that, however, assigning a new value to cachedQuestion.question does nothing.
I've added some breakpoints and found the following:
The assignment to question does not update changedValues:
(lldb) po [cachedQuestion changedValues] {
lastAccessTime = "2014-07-16 17:16:45 +0000";
}
question and cachedQuestion.question start out with the different addresses and then end with the same so an assignment is definitely occurring.
If I put a step before the assignment that sets cachedQuestion.question = nil, I get the following, but the the record for question disappears again on reassignment.
(lldb) po [cachedQuestion changedValues] {
lastAccessTime = "2014-07-16 17:16:45 +0000";
question = "<null>";
}
Here's another super up thing. I went ahead and did a test where I just set cachedQuestion.question = nil for a few builds of the app. This is working fine for subsequent lookups in a single run, but every time when the app launches the first read of cachedQuestion.question goes back to the original value! I can confirm that new cached questions are begin created and lastAccessTime has no problem updating. It's just this one field that is frozen in time. This was because of data validation. nil got it to show up in changed values but save failed as it was not optional.
UPDATE
Right now I'm doing a sad sad hack where any time there is a change I delete the old record and insert a new one. It works well enough but I'd much rather know what's going wrong.

You're not writing your changes to the correct context. First, stop using contextForCurrentThread. It's deprecated and will cause you a crash 1 time out of 100, which is one too many. You also risk crashing since this method can and will return a context that is not the default context. Making managed object assignments across contexts will also crash, for good reason.
Instead, you should explicitly create your context, write your changes to it, and then save it at the end of your method. That will likely solve the majority of the problems you're seeing.

I had to read this several times...
I think the problem here is that you do not use relationships correctly.
NSManagedObjects should be set up as an Entity.
It should not be Transformable but a relationship to an entity.
From the context you can get the first all objects in the first entity in a MutableArray, then get all of its related objects in a new MutableArray of objects through its relationship by calling this when one object is selected:
self.newArray = [[self.oldObject.relationshipName allObjects]mutableCopy];
You will then only get content related to selected object and not all objects in the related entity.
This means there should not be a question attribute but a relationship to the entity question.

Related

Is there a way to save NSManagedObjects 1 at a time

I'm facing this problem where I have an NSMutableArray containing NSManagedObjects.
And I want to iterate each one NSManagedObject, set a value to a variable and then save them.
The problem is that I have a verification to see if the objects are in Database or not, and after I save my first NSManagedObject to the database all the others NSManagedObjects that are in that array are also inserted into the database...
Here's a piece of code that describes what is my issue:
for (Category* c in categories) {
Category* original = [Database getCategoryByID:c.categoryid];
// After the first save this will have value because it saves all the objects
// that are in categories Array and the version will be the same...
if (original != nil && original.version >= c.version) {
// This object is already up to date so do not make any changes
} else {
// This object is not up to date, or it does not exist
// update it's contents
c.name = name;
[[c managedObjectContext] MR_saveToPersistentStoreAndWait];
// Here it saves all the objects instead of only 1 object
}
}
Is there a way to only save 1 object at a time while having other NSManagedObjects inside an NSMutableArray ?
With Core Data, you tell a context to save, and it saves everything it has. There's no way to save just one object unless that object is the only one in a context with changes.
In your case, it looks like your array of Category objects are managed objects and that they belong to a managed object context. The easiest way to avoid unexpected saving is to move the save command until after the loop. When you reach that point,
Any Category that needed to be created or updated is ready to save
Any Category that didn't need to be updated has no changes, so it won't be affected by saving the context.
So it should be safe to save everything in the context then. If it has changes, it gets updated, and if it doesn't, it won't change.

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.

iOS - Core data - NSManagedObjectContext - not sure if it is saved

Overview
I have an iOS project in which I am using Core data
I am inserting an object, then I want to save it.
I am not sure if save works.
Save seems to be working when app goes into background
When using Simulator, If I click on Stop button on Xcode, save doesn't seem to be working.
Question
Is the save actually happening ?
Am I facing a problem because I created a view based app (the core data checkbox was not available) ?
Steps Followed
I am using the simulator to test it.
Insert an object (code is in the next section)
Save the inserted object (code is in the next section)
I press the Stop button on Xcode to stop running the app
Output noticed
setBeforeSave.count = 1
setAfterSave.count = 0
Before saving, The NSManagedObjectContext method insertedObjects returns 1 object
Before saving, The NSManagedObjectContext method insertedObjects returns 0 objects
When Xcode Stop button is pressed, and when the app is relaunched, the previous data is not available (is it because I clicked on stop on xcode)
managedObjectContext is NOT nil
The NSManagedObjectContext method save: returns YES.
Code to Insert Object
Test *test = [NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.database.managedObjectContext];
Code to Save:
//database is a property of the type UIManagedDocument
NSSet *setBeforeSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setBeforeSave.count = %i", setBeforeSave.count);
NSError *error = nil;
if(![self.database.managedObjectContext save:&error])
NSLog(#"error = %#", error);
NSSet *setAfterSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setAfterSave.count = %i", setAfterSave.count);
According to the UIManagedDocument documentation, you should not call save on either of the internal managed contexts. Instead, if you want data saved, you should do one of two things.
Use the undoManager, as it will mark the context dirty, and ready to be saved.
Call [document updateChangeCount:UIDocumentChangeDone];
Thus, in your case, you should replace that save call with:
[self.database updateChangeCount:UIDocumentChangeDone];
And your data will get saved.
EDIT
To provide additional detail. A UIManagedDocument has two MOCs., in a parent/child relationship. The child is the one you get when calling document.managedObjectContext. Now, when a NSManagedObjectContext has a parent, the normal way to propagate changes to the parent is to call save:. However, the UIManagedDocuememt does other stuff, and its documentation specifically says NOT to call save on either the parent or child context.
Well, how does stuff get saved, then? Well, you tell the UIManagedDocument that it is "dirty" and needs to be saved. The two ways you can do that are by either using the undoManager, or calling updateChangeCount:.
When doing either of those, the internals of UIManagedDocument will make sure that the parent context is notified of the change. At some point in the future, the parent will effect the change to the actual backing store (i.e., file(s) on disk).
Furthermore, when a context is "saved" it may or may not keep references to the objects that were changed. You can set a property which tells it to retain objects that have been saved, or to release them.
Hopefully, that addresses your problems.
to summarize, though, see the original answer.
BTW, you can actually see a log of what the SQL store is doing underneath by adding "-com.apple.CoreData.SQLDebug 1" to your command line arguments. You do that in the "Edit Scheme" dialog.

CoreDataGeneratedAccessors to remove object don't seem to be deleting

I have an NSManagedObject that has a to-many relationship to another NSManagedObject.
During creation of the NSManagedObject I can use the generated accessors 'removeNotesObject' and the deletion works fine. I can create an object to add to the parent object, save the object, delete the object and then save again. When I fetch this parent object the object I created and deleted is still deleted.
However, after I add the object and then save it (but don't delete and save after) and then fetch it, I can't seem to delete the object that was previously created. I am using the generated accessors to try and remove the object, which appears to work but when I fetch it again the object hasn't been deleted.
(Note: Adding objects does work so it is not a problem with the saving)
To delete the object I retrieve the set of object and select the objects I want to delete. Then I remove the objects
NSSet *notes = summary.notes;
NSSet *oldNotes = [notes objectsPassingTest:^(id obj,BOOL *stop){
Note *oldNote = (Note *)obj;
BOOL sameRow = (oldNote.row == newNote.row);
BOOL sameColumn = (oldNote.column == newNote.column);
BOOL success = (sameRow && sameColumn);
return success;}];
[summary removeNotes:oldNotes];
I have tried making the relationship inverse to delete the objects which didn't delete them. I have also tried different delete rules (cascade and nullify) which again didn't work. Finally, I tried to remove each object separately and deleting each object from the context after I had removed it from the parent object which again unfortunately didn't work.
I assume the problem must be something to do with it being a fetched object. If anyone could help I would really appreciate it as I can't think of any other ways to test or solve this problem.
You need to do
NSManagedObjectContext * moc = .......;
[moc deleteObject:note]
edit: The core data generated accessors simply remove the object from the relationship, but do not delete the object permanently. This makes sense because you may have one NSManagedObject associated to multiple other NSManagedObjects via relationships.
edit: Deleting in the above mentioned fashion will invoke the deletion rules. I suggest you double check that they are setup correctly.
The reason the above code did not work is that == will not actually compare the NSNumber. Instead you need to call 'isEqualTo:'. I think before it was checking the address hence working before I saved it. What's more it was returning an object in the NSSet so appeared to be working. During debugging it wasn't clear what the object was but clearly wasn't the one I needed.

Resources