When to use a predicate over passing a CoreData object via the prepare method - ios

I have an iOS app that I am currently building. I'm using CoreData for the first time and have a question in regards to passing data from one ViewController to the next. Right now I have a tableview set up in VCa, and when I tap a cell I want to get the details of that object (Person) in VCb. I have been thinking of doing this in 2 different ways.
Pass a reference of the Persons name in the prepage function. And over in VCb use a predicate to fetch that person back out of CoreData. Is using another fetch request the correct solution here. There will not be a ton of data in this app, so I don't think it would be very taxing on the system to do another request, but I'm new to CoreData.
Capture the data object in VCa and pass that object over to VCb via the prepare function. This would eliminate me having to do another fetch request in VCb but seems like I could be limiting myself for future expansions.
Again, I'm new to CoreData and just wanted some thoughts on which method of thinking is better and why.

Don't ever fetch an object that you have already fetched. In your case #1 you already have the object that you need, but you'd go ahead and fetch it again for no good reason. It might not be "very taxing" in your case but that just means you're probably not duplicating a lot of work for no reason yet, but it's still a bad design. And anyway-- this is an entity representing a person. Are you certain that there would never, ever be more than one person with the same name?
Your second idea is the better one here.

Related

Crash when deleting object from Core Data and toggling view controllers

I am making a NSFetchRequest for a NSManaged Object on my initial screen. I sometimes have a crash in a scenario when I :
switch to another view controller within my tab bar controller
make another fetch request with the same managed object type
delete a common managed objects which also appears in my initial VC's fetchrequest. The VC contains a table view.
save the managed context
toggle to the first VC, and reload the data
I am not using NSFetchResutltsController to manage these returned objects. The crash happens when my tableview reloads. I do make another request, and expected the deleted objects not be returned, but it does. When my cells are trying to read a property of the deleted object, it reads uninitialized and crashes. This happens about 1 out of 5 times when toggling between the 2 VCs. I am using performAndWait in all of my CoreData functions.
Is there a way to decouple the the relationship of the Managed Objects between the two screens? If not, how can I get my fetch request in the first VC, not return the objects that were deleted in the second VC, keeping them in sync?
A NSManagedObject is not like other other objects. It does not contain any information itself. It has a pointer to its context and an objectID. When you access it's properties it forwards the request to the context to get the information that it needs. So when an entity is deleted from the context the managedObject stops working and causes a crash. This is why in general I think it is a bad practice to EVER keep a pointer to a managedObject and ALWAYS access them using a fetchedResultsController even if only for one object, and only do a fetch if the managedObjects results are discards right afterwards.
There are two possible solutions, which you hinted to in your question. Either you can copy the values out of the managedObject, or you can use a fetchedResultsController. If you copy the values then it will appears as normal even after the entity is deleted. If you use a fetchedResultsController then the fetchedObjects property will be never contain deleted object, and the object will be inaccessible after it is deleted.
I would recommend using a fetchedResultsController. You don't need to be afraid of it. It is not a large overhead and it reasonable to use even if you are fetching only one object.

Fix uneccessary copy of NSManagedObject

I'm sorry the title may mislead you, since I'm not so good at English. Let me describe my problem as below (You may skip to the TL;DR version at the bottom of this question).
In Coredata, I design a Product entity. In app, I download products from a server. It return JSON string, I defragment it then save to CoreData.
After sometimes has passed, I search a product from that server again, having some interaction with server. Now, I call the online product XProduct. This product may not exist in CoreData, and I also don't want to save it to CoreData since it may not belong to this system (it come from other warehouse, not my current warehouse).
Assume this XProduct has the same properties as Product, but not belong to CoreData, the developer from before has designed another Object, the XProduct, and copy everything (the code) from Product. Wow. The another difference between these two is, XProduct has some method to interact with server, like: - (void)updateStock:(NSInteger)qty;
Now, I want to upgrade the Product properties, I'll have to update the XProduct also. And I have to use these two separately, like:
id product = anArrayContainsProducts[indexPath.row];
if ([product isKindOfClass:[XProduct class]] {
// Some stuff with the xproduct
}
else {
// Probably the same display to the cell.
}
TL;DR
Basically, I want to create a scenario like this:
Get data from server.
Check existed in CoreData.
2 == true => add to array (also may update some data from server).
2 == false => create object (contains same structure as NSManagedObject from JSON dictionary => add to array.
The object created in step 4 will never exist in CoreData.
Questions
How can I create an NSManagedObject without having it add to NSMangedObjectContext and make sure the app would run fine?
If 1 is not encouragement, please suggest me a better approach to this. I really don't like to duplicate so many codes like that.
Update
I was thinking about inheritance (XProduct : Product) but it still make XProduct the subclass of NSManagedObject, so I don't think that is a good approach.
There are a couple of possibilities that might work.
One is just to create the managed objects but not insert them into a context. When you create a managed object, the context argument is allowed to be nil. For example, calling insertNewObjectForEntityForName(_:inManagedObjectContext:) with no context. That gives you an instance of the managed object that's not going to be saved. They have the same lifetime as any other object.
Another is to use a second Core Data stack for these objects, with an in-memory persistent store. If you use NSInMemoryStoreType when adding the persistent store (instead of NSSQLiteStoreType), you get a complete, working Core Data stack. Except that when you save changes, they only get saved in memory. It's not really persistent, since it disappears when the app exits, but aside from that it's exactly the same as any other Core Data stack.
I'd probably use the second approach, especially if these objects have any relationships, but either should work.

Creating a copy of a PFObject

I am in a situation where I allow the user to download a PFObject and modify it locally, and they can then either cancel the changes or hit Done, which will dismiss the editing interface but NOT upload the changes to Parse yet. They need to hit Save on the previous screen to write all changes to the database at once.
The problem is once the PFObject is modified, you cannot revert it to its prior state without refetching from the database. But I cannot always refetch the data from the database every time they hit Cancel because the prior state may not be uploaded to Parse yet (and that's a bad UX making them wait to discard changes that are only stored locally).
For example, imagine the user taps to edit the PFObject, they make changes then hit Done, then tap on it again and further edit the object, then hit Cancel. In this case, the object needs to be reverted to its prior state, but that state has not been uploaded to Parse yet. So I cannot refetch the data from the database to revert changes otherwise it would overwrite the changes they made the first time.
To solve this problem, I would simply fetch the PFObject and store a copy of it. I'd call that the transient object. I would have another property that stores the real object. The user would modify the transient object, and when they hit Cancel I would simply set that to nil, if they instead hit Done I would set the real object equal to the transient object, and once they finally hit Save I would save the real object to the database. That way I can be sure changes aren't being made to the real object until the user commits the changes. The problem is, PFObject does not adopt the NSCopying protocol (not sure why), therefore I cannot create a copy of the PFObject. Any change I make to it affects the real object.
How can this be resolved, without modifying the app's design that allows control over when the data is committed and later saved? Is there a way to extend PFObject and adopt NSCopying, has it been done before?
I did consider storing the attributes of the object in a dictionary and allow the user to edit that instead, then upon commit set each of those attributes on the PFObject. The problem with this solution arises with complex structures. In this app, I allow the user to modify multiple arrays that contain multiple PFObjects. It's just infeasible to try to recreate and later merge changes with complex structures like this beyond a single simple PFObject.
I ran into this same problem. I did not make any changes directly to the PFObject, but rather, saved the updates in an NSDictionary. When the user clicks the done button, I then update the PFObject and saveInBackground. I don't think there is a "discard local changes" option for PFObject. If you don't do this, the only option is to throw out the existing PFObject and fetch again.
Regarding the NSDictionary comment, perhaps NSArray would be better. The implementation really depends on your specific program, but I'll give a quick example. The NSArray we'll call instructionArray. Imagine there are 3 sections in a tableView. Also assume that the data source for each section is an NSArray of PFObjects. Now say you want to set the age property of each PFObject in Section 2 to 35.
Add an NSArray object (corresponding to an instruction to carry out) to the instructionArray. This instruction to carry out could have the form
Section to update
Property to update
Value to update to
So the object you'll add is #[#(2),#"age",#(35)];
Given that the user is probably carrying out a finite amount of instructions, it might not be that performance heavy to loop through the instructionArray in cellForRowAtIndexPath so when a cell uses its corresponding PFObject to figure out what to display, it can loop through the instructions after and change what is displayed as if the PFObject was updated.
When the save button is touched, loop through the instructions and actually edit the PFObjects themselves.
If you need the instructions to handle specific objects rather than sections, then you just have to update the structure of the instructionArray. Maybe you could include an identifier to indicate what type of instruction it is.

Init object with asynchronously downloaded data

I have an object that needs to be initialised with data from network and doesn't really make sense without the downloaded data. But it seems to me that doing an asynchronous network call in its init method is not a good idea because the object will not be ready to user right away and it might cause confusion. Should I only use a basic init method that will alloc init all its properties to create an empty object, and have other (non-init) methods populate the data from the network which will be called explicitly by other objects (such as the view controller using this object)? What would be a good way to approach this?
I think that the solution comes from having the right order of code running:
1) Go to network and fetch data (show the user acivity indicator in this time)
2) Server return response -> fetch the response into your object
3) Show the data or use it

NSFetchedResultsChangeInsert gets called twice for 1 insert with different object IDs

I'm losing my mind around this question.
So I have a Core Data setup in my iOS app done this way:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
I then insert an object by creating a temporary MOC (as explained in the blog post) and perform saves on all 3 contexts in performBlock: methods.
In a view controller I have an NSFetchedResultsController and it gets notified that I did indeed insert a new object. The problem is that the NSFetchedResultsChangeInsert is fired twice and each time the object that is passed trough has a different objectID (it also is a different object instance in memory). What happens is that I then have 2 rows inserted in my table view but un the SQL database there is only one new. It then of course crashes when I scroll to the bottom of the table view.
If I also perform some updates on the object I get NSFetchedResultsChangeUpdate called only once and with the objectID that was passed in the second NSFetchedResultsChangeInsert call.
The first ID looks like this:
<x-coredata:///ReceivedMessage/t605BB9A7-A04E-4B89-B568-65B12E8C259A2>
The second (and all consequent ones) like this:
<x-coredata://02A917C5-850F-4C67-B8E4-1C5790CF3919/ReceivedMessage/p28>
What could this be? Am I missing out something obvious?
PS: I also checked if the notification comes from the same context, thread, etc. It does.
The two IDs you are seeing may very well represent one object. The difference between them is just that the first one is a temporary object ID, assigned to the object on creation, and the second one is the permanent object ID, assigned to the object when it gets stored to the managed object store (see NSManagedObjectID's isTemporaryID).
To work around this issue you could call NSManagedObjectContext's obtainPermanentIDsForObjects:error: just before you save the temporary MOC. This way the inserted object will have just one ID during the save propagation and the NSFetchedResultsControllerDelegate methods should get called just once.

Resources