Using Core Data in app without UITableView - ios

I have a simple to do list application. It uses dynamically generated text fields spaced programmatically for the tasks (I didn't use UITableView because of some custom animations and whatnot that I want to use).
In the app the user can create multiple lists (home, work, school, etc.) each with their own tasks.
I want to use Core Data to store the information
Saving the information is straightforward enough but updating my Core Data objects (List and Task) are where I'm getting stuck. Also how, with Core Data, to associate in a specific tasks with a specific list.
let me know if I need to clarify anything.

Your best bet is NSFetchedResultsController. You can use it exactly like in the pattern suggested by the Xcode templates (you can look at it by creating a new project Master-Detail and checking "User Core Data").
You can device your object model (entity Task) with a string attribute for the name as well as a NSNumber boolean attribute for done etc. I also recommend a timestamp and maybe a serial number for ordering it (I find NSOrderedSet unreliable). Your entity List should have a to-many relationship to Task. Pretty straight forward.
List <---->> Task
The only difference is now to find the right object, because you cannot use objectAtIndexPath. You can use the index in fetchedResultsController.fetchedObjects for that. Just make sure your objects are ordered as expected.

I'm not totally clear on your question, however, the task of updating a managed object is straightforward. When you're doing an initial add (similar to an "insert" in SQL) you might use code like this:
NSManagedObject *obj;
obj = [NSEntityDescription insertNewObjectForEntityForName:#"UserData" inManagedObjectContext:context];
[obj setValue:user forKey:#"userName"];
[obj setValue:goalCategory forKey:#"goalCategory"];
[obj setValue:goalDetail forKey:#"goalDetail"];
NSError __autoreleasing error;
[context save:&error];
That's about it for inserting a new item. For updating after you're found the managed object you're working on, you just change the values in the managed object and use [context save:&error]. Here's an example:
UserData *uData = (UserData *) managedObj;
uData.itemName = nameText;
NSError __autoreleasing *error;
[context save:&error];
That's pretty much it.
As to the update, once you have selected the object(s) to be updated, they are contained in
fetchedResultsController.fetchedObjects
which is an NSArray. So, you might do something like this:
UserData *uData = (UserData *) [fetchedResultsController.fetchedObjects objectAtIndex:3];
uData.completed = YES;
NSError __autoreleasing *error;
[context save:&error];
So, this would update the field completed in the UserData entity to be == YES for the object at index 3 in the fetchedObjects array.
I know there are other methods of updating and lots of options but I haven't found any need for them. fetchedObjects is an array containing the items returned by your fetch; to update them, cast each object to the entity (which is defined as a NSManagedObject), make the change then context save..
HTH.

First of all, think is it good idea to use Core Data for your project. If your model is light and simple, maybe it will be better to use plists.
If you choose Core Data, just remember 2 rules:
Each thread owns separate NSManagedObjectContext;
Perform operations with context only in its thread.
And don't worry about optimizations now. Realize any scheme of updating your storage. Make sure it works. And then you should try some other update methods.

Related

Sqlite or Core Data to update more then 50000 records

I'm currently using coredata for my project. But when the api returns 54000 objects that the app need to update, the user has to wait almost 2 hours.
It's the major problem for the current project and I am thinking to use sqlite and not using coredata anymore to update thousands of objects.
Is it a right decision to use Sqlite or is there any suggestion for CoreData? I can't decide. Any help will be great. Thank you.
Here is what I am doing:
NSManagedObjectContext *privateObjectContext = [AppDelegate appDelegate].privateManagedObjectContext;
[privateObjectContext performBlock:^{
int i = 1;
for (NSDictionary *item in itemlist) {
i++;
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:
#"itemID == %#",[item objectForKey:#"item_id"]
]];
NSError *error;
NSMutableArray *inventories = [[NSMutableArray alloc]initWithArray:
[privateObjectContext executeFetchRequest:fetchRequest
error:&error]];
ItemManagedObject *itemMO;
if(inventories.count){
itemMO = inventories.firstObject;
}else{
itemMO = [NSEntityDescription insertNewObjectForEntityForName:#"ItemObject"
inManagedObjectContext:privateObjectContext];
}
[itemMO prepareWithDictionary:item];
}
NSError *error;
if (![privateObjectContext save:&error]) {
completionHandler(NO);
}
}
Core Data provides NSBatchUpdateRequest which allows you to make updates directly on the persistent store without involving instantiating and processing managed objects in memory.
You should run this code using the core data performance instrument as well. If itemList contains 54,000 objects then you are performing 54,000 fetches to the persistent store to check a single ID each time. It would be far faster to fetch all of the IDs up front and then check the results in memory than to perform repeated fetch requests - that code will be almost as slow in raw SQL as it is in Core Data.
This code also looks wrong:
ItemManagedObject *itemMO;
if(itemMO.count){
It's never going to pass that if test, unless you've missed a line somewhere.
2 hours is very long. That's weird.
Yet you can massage your code by having core data do less work. Much less work.
Perform a single fetch request instead of 54K fetch requests
Don't call a managed object property setter when a property value does not change, so that no object is unnecessarily flagged as dirty, and Core Data does not have to perform a costly but useless update of the object when the "save" method is invoked.
This will dramatically reduce the amount of work performed by Core Data, and the performance of your application.
The second point is easy, but very verbose: compare each individual property values with dictionary values before calling setters.
The first point requires an algorithm change:
Perform a single fetch request, sorted by id (with [NSFetchRequest setSortDescriptors:])
Sort dictionaries by id (with [NSArray sortedArray...])
Synchronize the two sorted lists (it is paramount that both lists are sorted):
NSEnumerator *itemMOEnum = [itemMOs objectEnumerator];
NSEnumerator *dicEnum = [dictionaries objectEnumerator];
ItemManagedObject *itemMO = [itemMOEnum nextObject];
NSDictionary *itemDic = [dicEnum nextObject];
while (itemDic) {
NSComparisonResult comparison = itemMO ? [itemDic[#"item_id"] compare:itemMO.itemID] : NSOrderedAscending;
switch (comparison) {
case NSOrderedSame:
// id present in both lists: update
[itemMO prepareWithDictionary:itemDic];
itemMO = [itemMOEnum nextObject];
itemDic = [dicEnum nextObject];
break;
case NSOrderedAscending: {
// id present only in dictionaries: create
itemMO = [NSEntityDescription insertNewObjectForEntityForName:#"ItemObject"
inManagedObjectContext:privateObjectContext];
[itemMO prepareWithDictionary:itemDic];
itemDic = [dicEnum nextObject];
} break;
case NSOrderedDescending:
// id present only in managed object: delete or do nothing
itemMO = [itemMOEnum nextObject];
break;
}
}
while (itemMO) {
// id present only in managed object: delete or do nothing
itemMO = [itemMOEnum nextObject];
}
And save.
Finally, maybe SQLite will be faster (see https://github.com/groue/GRDB.swift/wiki/Performance for an attempt at comparing the performance of Core Data with SQLite libraries).
But SQLite won't turn a slow algorithm into a fast one.
I've never redone a core data project in sqlite or visa versa. So I cannot tell you whether there is a performance difference or not/
However the 54k = 2 hours thing sounds very strange. You talk about an API which makes me suspect a server is involved, your question is about databases. Certainly 2 hours sounds way too long and makes me wonder whether you have issues with the core design of your database. For example, lack of indexes. Depending on your queries and database, a single update could be triggering all sorts of heavy duty processing.
Another though is why are you processing this column of data on a device. It's a lot to handle and I wonder if there are ways to reduce the volume down, selectively do updates or perhaps even better - move it to a server.
I think you need to rethink your question. Provide more context about the database, exactly what you are doing with it and why.
CoreData is not a database manager but a object graph and persistent manager. CoreData can store its objects in a sqlite database but also in XML files or binary file (the developer chooses the option best suited to its needs).
The main difference between CoreData and a database manager is that to access an object with CoreData, CoreData need to instantiate the objective-C/Swift corresponding object.
Sqlite can access part of data without having to extract the full record containing the data.
And then, CoreData need to maintain the relational graph between objects (the relationships between 2 CoreData classes, and in general, in both ways).
So, when updating 54k objects, you ask CoreData to instantiate 54k objects (in memory) and to eventually update their relationships.
That is very heavy work for CoreData on mobile.
Perhaps your CoreData model is not correctly optimized.
Perhaps you should save the CoreData context regularly and flush CoreData scratchpad (the part of memory containing actually read or updated objects).
But in my experience, CoreData is not suited to heavy data work.
Re-implementing your needs with sqlite can be quite some work if you want to be able to re-instantiate your classe objects from sqlite records and manage quite automatic relationship, but it is doable. I did it on some projects. This add the benefit to have a model object that is more shareable with other platform as Android for instance, as sqlite is available on many platforms.
One more thing: sqlite is more suited to be used from multiple threads. CoreData is more touchy about this, and need one context by thread, and eventually, some contexts synchronization.

Core Data fault after deleting managed object

Basically, I'm trying to access some properties of a after deleting it from the NSManagedObjectContext and saving the context. The problem is that, after saving the context, Core Data marks the object data as fault and, apparently, is unable to recover it.
I've created a sample project in order to replicate the issue, you can download it here. To illustrate, the following snippet:
City *city = [self.cities objectAtIndex:indexPath.row];
[self.managedObjectContext deleteObject:city];
if (![self.managedObjectContext save:&error]) {
[self.managedObjectContext rollback];
NSLog(#"Error: %#", error);
}else{
NSLog(#"%#", city);
// All properties of "city" are zeroed.
// Saved. Update data sources and animate changes...
}
produces:
<City: 0x7fe1cbd3cba0> (entity: City; id: 0xd000000000040004 <x-coredata://C1E3D3D8-188D-41DE-B701-08AF6D3E8860/City/p1> ; data: {
country = "0xd000000000080002 <x-coredata://C1E3D3D8-188D-41DE-B701-08AF6D3E8860/Country/p2>";
name = Rosario;
})
<City: 0x7fe1cbd3cba0> (entity: City; id: 0xd000000000040004 <x-coredata://C1E3D3D8-188D-41DE-B701-08AF6D3E8860/City/p1> ; data: <fault>)
The reason I'd like to access the managed object, after deleting it, is to update a NSMutableArray which acts as data source for a table view and update another data source in a previous controller in the navigation controller stack (This is not implemented in the sample project).
Wrapping up, my questions are:
After deleting a NSManagedObject from its NSManagedObjectContext and saving the context, it is no longer guaranteed that the data in the managed object will be accessible? Even if a reference to that managed object is kept?
Based on what i've researched, Core Data is getting rid of the entity data to save memory once the context is saved. Is this assumption correct? Are there other factors that might be causing this data faulting?
Thanks.
An NSManagedObject is always dynamically rendered. Hence, if it is deleted, Core Data faults out the data. It doesn't exist anymore. Your real question is how to remove an object from your various arrays? First, you should remove it before you delete the object using whichever search techniques you wish. This is the easiest and most robust path. Second, the object pointer itself is still valid and can be used with a -removeObject: call. Allow me to emphasize though, this is a fragile solution. I'm strongly encouraging you to remove the object before deleting it.
In answer to your second question,
Are there other factors that might be causing this data faulting?
No. Deleting the object is causing the faulting. If the data has heretofore been available, that is due to it being an implementation characteristic. Writing to the implementation instead of the spec, especially with database technologies, is fraught with all sorts of life cycle problems. Quoting the wise Doctor, "Don't do that."

Deleting Core Data after X amount of days

So I have a bunch of objects in Core Data and want them to auto delete after X amount of days (this would be based off of an NSDate). I did some searching and it seems that you can only delete one core data object at a time, not a group of them, let alone ones that are based off of a certain date. I'm thinking maybe to have a loop running going through each object - but that seems like it would be very processor heavy. Any ideas on where I should be looking to do this? Thanks.
A loop deleting objects one by one is the correct approach.
Deleting objects in Core Data is extremely processor heavy. If that's a problem, then Core Data is not suitable for your project, and you should use something else. I recommend FCModel, as a light weight alternative that is very efficient.
If you are going to stick with Core Data, it's a good idea to perform large operations on a background NSOperationQueue, so the main application is not locked up while deleting the objects. You need to be very careful with Core Data across multiple threads, the approach is to have a separate managed object context for each thread, both using the same persistent store coordinator. Do not ever share a managed object across threads, but you can share the objectID, to fetch a second copy of the same database record on the other managed object context.
Basically your background thread creates a new context, deletes all the objects in a loop, then (on the main thread preferably, see documentation) save the background thread context. This will merge your changes unless there is a conflict (both contexts modify the same object) — in that scenario you have a few options, I'd just abort the entire delete operation and start again.
Apple has good documentation available for all the issues and sample code available here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdConcurrency.html
It's a bit daunting, you need to do some serious homework, but the actual code is very simple once you've got your head around how everything works. Or just use FCModel, which is designed for fast batch operations.
It's not as processor heavy as you may think :) (of course it depends of data amount)
Feel free to use loop
- (void)deleteAllObjects
{
NSArray *allEntities = self.managedObjectModel.entities;
for (NSEntityDescription *entityDescription in allEntities)
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entityDescription];
fetchRequest.includesPropertyValues = NO;
fetchRequest.includesSubentities = NO;
NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items) {
[self.managedObjectContext deleteObject:managedObject];
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error occurred");
}
}
}
As others have noted, iterating over the objects is the only way to actually delete the objects in Core Data. This is one of those use cases where Core Data's approach kind of falls down, because it's just not optimized for that kind of use.
But there are ways to deal with it to avoid unwanted delays in your app, so that the user doesn't have to wait while your code chugs over a ton of delete requests.
If you have a lot of objects that need to be deleted and you don't want to have to wait until the process is complete, you can fake the initial delete at first and then later do the actual delete when it's convenient. Something like:
Add a custom boolean attribute to the entity type called something like toBeDeleted with a default value of NO.
When you have a bunch of objects to delete, set toBeDeleted to YES on all of them in a single step by using NSBatchUpdateRequest (new in iOS 8). This class is mostly undocumented, so look at the header file or at the BNR blog post about it. You'll specify the property name and the new attribute value, and Core Data will do a mass, quick update.
Make sure your fetch requests all check that toBeDeleted is NO. Now objects marked for deletion will be excluded when fetching even though they still exist.
At some point-- later on, but soon-- run some code in the background that fetches and deletes objects that have toBeDeleted set to YES.

How to perform reodering of cells in One-to-many relationship in CoreData

I am learning coreData and I am new it, I have created a one-to-many relationship of boss and employee, (i.e one boss and many employees). So I am showing all the bosses on firstTableView and when the user clicks on the cells, he can view the employees assigned to each boss and also he can add employees to any particular boss. Now I want to reorder the boss cells. So how it should be done?
Edited based on the discussion below
- (void)insertNewObject:(NSString *)fileName
{
Boss *bossName = [NSEntityDescription insertNewObjectForEntityForName:#"Boss" inManagedObjectContext:self.managedObjectContext];
[bossName setName:fileName];
NSManagedObject *lastObject = [self.controller.fetchedObjects lastObject];
float lastObjectDisplayOrder = [[lastObject valueForKey:#"displayOrder"] floatValue];
NSLog(#"%f",lastObjectDisplayOrder);
[bossName setValue:[NSNumber numberWithDouble:lastObjectDisplayOrder + 1.0] forKey:#"displayOrder"];
// Save the context.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[Specific Answer To Updated Question]
It would be either ....
[self.boss insertObject:newEmployeeObject inEmployeesAtIndex:0];
Which is a core-data generated method that is part of your Boss subclass if you choose to create subclasses from your model. Or....
NSMutableOrderedSet *employees = [self.boss mutableOrderedSetValueForKey:#"employees"];
[employees insertObject:newEmployee atIndex:0]
It's not that intuitive I know, you can't just make a mutable copy, you have to get a special proxy object from mutableOrderedSetValueForKey.
[Original General Answer]...
Core-data now has the ability to use "Ordered Relationships" which you can specify in your model. If you do so, relationships in your object model will be represented by a new class NSOrderedSet which is a hybrid of an NSArray and an NSSet. By re-ordering the objects in this relationship object and saving the context you will reorder the objects in the database and they will maintain their new order. This kind of ordered relationship tends to be used when there isn't natural ordering attribute on the object. For instance the order simply represents the users preference for ordering a list in the UI.
If on the other hand you have an attribute on one of your objects that describes the order for a collection then you can use that attribute to order the results of an NSFetchRequest by specifying the Sort Descriptors. The value of the attribute would specify the position the object would be in in the results of the NSFetchRequest.
If you are using Ordered Relationships you would need keep the order of the NSOrderedSet for that relationship and the UITableView in sync. If the change was driven from the UI then you respond to the UITableViewDataSource delegate methods such as - (void)moveRowAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex and use the information provided to move the corresponding object to it's new position in the core-data relationship either by using the proxy object from mutableOrderedSetValueForKey: or the Core-data generated accessors of a generated subclass.
If the change to order were driven from the data side you would use the methods on UITableView such as insertRowsAtIndexPaths:withRowAnimation: and moveRowAtIndexPath:toIndexPath: to sync the rows in the UITableView with the changes you were making in the data.
If you are using NSFetchRequests you have a similar task. In this case you respond to user driven changes in the order by updating the sort attributes on your objects to match the new order that is described by the UITableView through the UITableViewDataSource delegate. Or if the ordering changes are starting at the data side you update the UITableView through it's methods to match the changes you are making to the sort attributes on the data. In this case you will be working with the results from the NSFetchResults as an NSArray, you would also have to keep that object in sync until the next time you ran the NSFetchRequest. You could use the same sort descriptor to sort the array, or create an NSMutableArray and use it's methods to move the data to match the table.
Finally if you are using NSFetchRequest you may like to look at NSFetchedResultsController It's job it is to simplify task of syncing a sorted NSFetchRequest and a UITableView. There is good sample code for this in the documentation. In this case you may find that the ordering of the data will take care of itself. For instance say your table is ordered by "assignment date" (i.e. the date at which an employee was assigned to a boss) then simply creating the objects with the correct information would trigger the correct results in the table.
Please note that ordered relationships do not work with iCloud. However in my opinion iCloud doesn't work anyway so that's not a problem.

RestKit, Core Data, and Relationship problems. Parent object not recognizing child set

I am trying to map a relationship between Articles and the Photos that belong to them using RestKit. Both objects get stored properly when requesting the resource, but it seems the relationship does not persist. In fact, the Article model seems to not even respond to the Photos selector (This may be the 'duh' spot, but I will provide full code to be through).
I've provided all code in a gist, as I find it easier to look through and format then on StackOverflow. Sorry if this is actually an inconvenience.
https://gist.github.com/3733334
And here is the image of the core data model and the relationships set up (sorry, I had to combine them since I can only post 2 hyperlinks currently):
http://imageshack.us/a/img33/5039/stackoverflowissue.jpg
Everything seems to be working properlly except the relationship between the objects when I try to access photos via anArticle.photos. The selector is unrecognized. I set up a convience method in the Photo model to return all photos with a matching article ID, but I feel this is an un-ideal solution as it sort of removes the whole idea of creating a relationship.
I feel there may be something simple I am missing and any help would be greatly appreciated.
Thanks!
So of course it was a "Duh" error. After some help from a local developer, he pointed out that my Photos variable in my Article.h file was an NSArray, and needed to be changed to an NSSet to store objects mapped by RestKit.
Theres some inconsistency between different versions of RestKit. If you are using the latest one mappings should be set up as shown here: https://github.com/RestKit/RestKit/wiki/Object-mapping. If you want to use entity classes for model specific methods make categories on your NSManagedObjects so that when you change your data model you can regenerate them (Do this only after you extract your methods to a category! Select an entity in your .xcdatamodeld and go to Editor -> Create NSManagedObject Subclass...).
I moved my mappings to the controller that is responsible for syncing with the remote API.
This shuld be helpful too: http://andriyadi.me/logging-in-restkit/.
Also Core Data guidelines stress that you should set up inverse relations (but it's not obligatory).
Fetching entities can also be done better in my opinion. In my project I have an NSObject subclass singleton that (among some other Core Data convenience functionalities) fetches by entity and predicate:
- (NSArray *)fetchEntities:(NSString *)entity usingPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
RLog(#"Error fetching entity %# using predicate %#", entity, predicate);
abort();
}
if ([results count] >= 1) {
return results;
}
return nil;
}
You can alter it to pass predicates as NSStrings too.
Side note:
Currently I'm also struggling with RestKit and object mapping myself :).

Resources