I have a Core Data app with a single model consisting of several entities. For the purposes of this question, let's call the model "Person" and have the entities "name" (string), "age" (int), "occupation" (string), and "description" (string).
As an example, I may add a new Person. I set their name, age, occupation, and description. I confirm that the context has been updated by checking the contents of the NSManagedObjectContextObjectsDidChangeNotification userInfo. The context is then saved and persisted to disk (confirmed through both logging and querying the SQLite db).
At this point, I can edit any of my Person objects and all entities can be modified, added, deleted, etc., and all changes are confirmed to change the context with NSManagedObjectContextObjectsDidChangeNotification and confirmed to be persisted to disk via logging and querying SQLite - everything works as expected.
However, "occupation" and "description" do not persist after relaunching the app. They are not set as transient (however they are "optional"), and the main context is saved (sometimes several times) after they are set. More so, if I quit the app, before opening it again, I can query the SQLite db and confirm that the changes still exist on disk.
When the app is reopened, I can confirm that all is the same on disk as it was when I quit. However, when I initialize Core Data in my App Delegate, NSManagedObjectContextObjectsDidChangeNotification notifies me that something has changed. For some reason, "occupation" and "description" have disappeared. They haven't been set to null, they've been set to zero-length strings. Therefore, next time something happens that triggers a save, "occupation" and "description" are set to zero-length strings and disappear. This all happens just on launch - I do not access a view controller or anything else that even queries for those specific fields. I do, however, show a view controller that does require a fetch request that populates a UITableView that displays the People objects, although it does not specifically display either of the entities in question.
There is a lightweight Core Data Model migration from Person 1 to Person 2, however, when switching back to Person 1, this problem persists, leading me to believe it is not the migration which is an issue.
I'm asking for recommendations on where to start troubleshooting this issue. I can't post code - I have no idea where in my app the problem is occurring. I've tried to troubleshoot as much as possible with confirming when changes occur in the context and how that compares with what is on disk, I just don't know where to go from here. I don't know why these entities (and only some of them) are disappearing. How would someone start troubleshooting this kind of issue? Any help is appreciated.
Thanks to the guidance of #Rog and #Avi, I was able to both set up custom setters for the properties in question and override validateValue:forKey:error, and set breakpoints in each. From here, I was able to see the calling methods and diagnose the issue correctly.
The actual issue was that I was misunderstanding that initWithEntity:insertIntoManagedObjectContext: is called for each object when Core Data is initialized, where I was under the impression it would only be called when an object is initially created (once and only once). The problematic code was in initWithEntity:insertIntoManagedObjectContext:.
Thank you for your help!
Related
Hi fellow iOS developers, I have seem to run into a roadblock here with coredata and Im fairly certain this is should be a common use case. But I am either not querying google correctly or my design with handling managed objects was bad which led to this problem.
So basically, the problem that I am facing is the following..
User creates a new managed object object.
User updates said new managed object object (say edit the name
field)
** The following is the problem **
User wants to edit the same field again, but decides to discard their changes
What is the right thing to do here? Initially I was using .refresh() on the managed object context, but that only works if the object is persisted in the database.
Essentially, I don't want to save the object until the user explicitly opts to "save". Which leaves me in this limbo land. At first one of my thoughts was to created regular objects and convert them into core data objects when "save" is selected. However, that proved to be inefficient especially when handling multiple relationships.
I would think that there is something that is provided that aims to solve this problem, im just not quite querying google correctly.
I want to ask for a safe way to clear subEntities in coredata.
I have my a many-to-many relationship like this: Product *<->* Product. Therefore, I've got to create a subEntity to hold some special values between (sortPosition, groupName.....).
So it's like this: Product *<->1 ProductSubEntity 1<->*Product.
When I download products from server's API, the easiest way to update correctly correspond to the server's result is:
Remove all child relationship ([self removeProductSubEntities:self.subEntities]).
Add sub from server's result.
Result: There'd be a lot of subEntity in coredata (which won't hold relationship to any product), and this might take storage/memory/cpu when CRUD (I think?). But I can't actual delete the subEntity (in case it's being hold reference to as an viewController's Object somewhere, and it might cause crash: access to a deleted object).
QUESTION:
How can I clear those sub entities (might occur sometimes) if:
No relationship to any product.
No actual reference from anywhere (any viewControllers or objects)???
P/S: I'm thinking of implement a batch delete when terminate app. Could that be consider a safe solution?
I don't consider this to be a datastore issue, rather a UI update issue. You should delete the objects from the datastore when you don't need them any more and you should update the UI accordingly.
1 thing you didn't mention is re-use. It's possible that your download may be an update to an existing item, which you could find and update, then life is easy all round. Arguably everything below still applies in this case though as your UI might not update to reflect changes and you may need to refresh the managed object.
For the UI update it's generally wise to observe the datastore for changes, usually with an NSFetchedResultsController. If you're doing this then your UI would automatically update itself with the changes.
If you're explicitly passing entity instances around then you should have some way to trigger an update explicitly, and exactly how that works depends on your UI. Generally speaking you'd be doing something like posting a UINotification to tell the system that the datastore changed and they need to re-validate their data objects. For the UI you shouldn't be showing now-dead objects to the user, and in your question where you talk about not deleting to avoid crashes, it's probably worse to allow the user to update invalid objects and just quietly not telling them that their updates won't be saved. When the notification is received you may want to pop a (some) controller(s) off the stack, or re-query the datastore for the new data to be displayed.
If for some reason you don't want to do the above, then yes, you can query for all of the entities with a nil relationship and then batch delete them. This should be done on a background thread just like data loading and I'd recommend doing it on app load instead of close (because you won't have so many view controllers loaded and the ones that are should all have only valid references now...).
I am currently doing some tests with Ensembles, specifically testing Core Data light migration.
My current configuration is as follow:
Device-A running my app with data model 1
Device-B running my app with data model 2
data model 2 is based on data model 1 with one additional string property, which is optional
My scenario is as follow:
At the beginning, running my app with data model 1 on both Device-A, and Device-B, everything synced fine using Ensembles (iCloud configuration)
On Device-B, install and run my updated app using data model 2
On Device-A, keep running my old app using data model 1, and add a new record
The result: the new record added on Device-A is uploaded to iCloud and then synced to device-B
My question: can I configure Ensembles to prevent it from uploading changes to iCloud in case that related data model is not the latest one? (i.e. in my case, Device-A uploads an object based on data model 1 while iCloud is already based on data model 2)
Thanks in advance!
UPDATE 1:
Drew, thank you very much for your answer. I definitely agree that uploads can't (and probably shouldn't) be prevented as Ensembles is a decentralised, peer-to-peer system.
In the ideal case, I would like that the device with the new data model will ignore data that is based on the old data model. (in a similar way to the existing behavior where the device with the old data model will ignore any data based on the new data model). Is that supported?
If not, please consider the following scenario as an example:
The old data model has an entity called 'Book' with two properties: title, and author (both fields are non optional)
The new data model has a new optional property called titleFirstLetter that should hold the first letter of the title field.
Currently, when Ensembles is not involved, I have full control when saving new NSManagedObject to the persistence store. Therefore, the updated code of my app which responsible for adding a new book, will make sure to extract the first letter from the title field and save it to the new titleFirstLetter property. (i.e. a book titled Catch-22 will have C in the titleFirstLetter property when book is saved).
In addition, when light migration occurs on the core data stack, I detect that, and perform a one-time procedure where I iterate all existing books in the database, and set the titleFirstLetter according to the title value. From this point and on, the database is consistent and valid, while the new code will ensure that future books added to the database will keep database valid.
Regarding Ensembles, if I don't have any control on old data coming from devices with older data model, how can I fill the new property of titleFirstLetter, if my code is never being called?
Thank you for your kind assistance!
You can't prevent it, no. Ensembles is a decentralised, peer-to-peer system. There is really no way for one device to know the current state of another device, so you couldn't prevent an upload.
The updated device should be capable of handling the old data from the other device. The device with the old model will ignore any data based on the new model, until it too is updated. Then it will merge all of that ignored data.
It is best to avoid migrations where possible, and stick to simple stuff like adding properties or entities, rather than tricky refactors. If you need to make a lot of changes, consider simply starting with a new ensemble (e.g. change the ensembles identifier).
I'm trying to keep the phone's contacts in my app up to date, in a persistent way. For that I'm thinking of using Core Data.
My plan right now seems highly suboptimal :
Browse the address book and every ABPerson in it
Store every field in a CoreData persistent store
Store the image in a separate file with an unique name and a reference in another "Contact" field.
And I do this every time the app comes in foreground in case the user would change one of his contact's name or picture, etc.
Some of my users have more than 2500 contacts, sometimes the operation lasts up to 10 seconds.
My question is :
Is there a way to keep some kind of reference to my ABPerson in coredata, so I can always load my ABPerson properties everywhere instead of Contact properties? (which would then always be up to date).
And I'm not even sure it's the right decision :
Should I always use the ABRecord that I find with a reference?
Should I always use my own copied data that I update regularly (from the ABAddressBook)?
If not, do you guys think I'm doing this in a decent way or would you suggest something else?
EDIT:
As asked in the comments:
I need to keep the contacts up to date simply to use their firstname, lastname and picture properties. If I notice the ABRecord changes, I'll update the related custom objects accordingly and that's it. I won't really need anything else afterwards (until they're edited again)
Thanks
Obtain and store only the ABRecord's unique identifier value. This is the one persistent way to reliably refer to the same person repeatedly and consistently.
You can always get all the other info out of the contacts database by using this unique identifier.
In iOS, call ABRecordGetRecordID to obtain the person's unique ID. Store that. When you later want to obtain the corresponding person, call ABAddressBookGetPersonWithRecordID.
This is a bit of a crazy one. I have an IOS app that saves an object to Parse.com's datastore. Until a couple of days ago everything worked well. Now, suddenly, the object is not going into the database. I would put the code here but it doesn't seem to be the issue, as it gets even more bizarre:
When I go directly to the data browser and insert a row manually, it shows up right there.
But when I refresh the data browser -- the object is gone.
Looking at the forums here's some more data:
When I save from the app, I have an existing current user.
The object itself exists.
The permissions on my class are all "Public."
The app codes are correct (I'm able to read from the database.)
I'm even seeing the saveInTheBackground success block getting called, and printing out the object it has a valid object id!!!
But then it goes pooooooffff and is gone.
Any idea what's happening?
EDIT: I added a different class with the exact same columns, changed the appropriate fields in the app, and now everything works. But this is obviously alarming, that a class stops saving objects all of a sudden. Any idea why?