iPad splitview application with core data - ios

I am new in app development and I have really big issues with my split view core data iPad application. Even though I made sure that I have built all core data structure very well, I have problems when I pass objects between view and saving them via core data. Basically my problem is in this way:
I have peopleviewcontroller where I list all people in list. And I have addpersonviewcontroller where I create new person and save it to coredata. However, even though saving new object in new class seems successful in save-error structure, no data is written to core data. I debuted the code and figured out that managedjectcontext of created person is always null. Hence, it is not written to core data.
I have spent really lots and lots of time for solution, including days of reading stackverflow, but no solution.
Any help about this issue is deeply appreciated. Thabk you in advance.

Your Person is a subclass of NSManagedObject. In your AddPersonViewController, you never created the object. You have to use insert that entity into core data before you save it.
I recommend having some store/repository that handles all of this.
For instance, I have this in my code:
- (NSManagedObject *)createManagedObject:(NSString *)entityName
{
NSManagedObject *objectCreated = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.managedObjectContext];
[self saveContext];
return objectCreated;
}
I then call it with the entity type and it returns a NSManagedObject. You can then cast it to your Person class with
Person *addPerson = (Person *)managedObject;
Now you can make your changes and save it.
A NSManagedObject must be created by the store before it can be saved. You cannot just new it up and expect it to work.

Related

Core Data - Relationships between Multiple Core Data Models

Platform
iOS 10, Xcode 8.3.3
Background
I have built a Notes application that takes advantage of Core Data and I'd like to use this app in my next application, which will also use Core Data. For simplicity, lets call my next application, "ListApp", and my notes application, "NotesApp". This ListApp has list items each of which can have one or more notes.
Here's what I've done so far:
Removed all unnecessary files from the NotesApp and compiled a "NotesApp Framework".
Linked the NotesApp Framework to the ListApp.
Designed the Core Data Model for ListApp. Specifically, I created an entity called "ListItem" and an entity called "Note". The ListItem has a to-many relationship with the Note (one list item can have multiple notes). The Note entity contains a "noteID" field to reference the note in the NotesApp model and, of course, the inverse relationship.
Problem
I need to form a "relationship" between an entity in the ListApp model and an entity in the NotesApp model.
I've researched configurations and that seems to be more for storing objects in the same model to different persistent stores unless there's something I'm missing. So, that doesn't help.
Then, I found that fetched properties can be used to form weak relationships between multiple stores. So, that doesn't help either.
Next, I found in the documentation that there's a method called NSManagedObjectModel.mergedModel(from:) so I'm assuming this is possible. Or maybe that's only for migration?
That's where I'm stuck.
Reason
I'd rather not redesign everything in the NotesApp model in to the ListApp model. I prefer to keep everything separate.
Questions
Is there a way to form a relationship between two entities in different models? Should I just add a function in the ListItem entity class to fetch the notes in the NotesApp model manually? Am I even going down the right path or is there a better option?
NOTE: What I mean by "relationship" is the ability to call on a property in the ListItem entity to fetch the notes and somehow "relate" specific notes to a specific ListItem.
P.S. If you know of any pitfalls, have any general advice, or know of any reading material please feel free to let me know.
Also, I've been researching this topic for a couple of hours and I can't seem to find anything about it. I'm assuming that's either because it's not possible, it's a terrible practice, or I'm not using the right keywords.
If anyone needs any more information feel free to let me know!
I think you're saying your bundle will have one model that contains the List entity and another that contains the Note entity. You can merge and tweak managed object models, as you suggested, and use the resulting managed object model which you have in code.
If you're creating your Core Data stack in code (that is, you are not using the new NSPersistentContainer), it is easy to splice in a custom managed object model.
If you are using NSPersistentContainer, you'd have to subclass it and override managedObjectModel(). I can't find any documentation saying you can't do that, but I wouldn't bet on that.
If you're document-based, overriding UIDocument's managedObjectModel should work.
To create your custom managed object model, merge your models using NSManagedObjectModel.mergedModel(from:). Then, get the Note and List entity decriptions, get the properties of each, mutate, add your new relationships, then set them back into the model. You would only do this on the first run; cache your custom managed model to a ivar for subsequent runs.
Hmmmm. What I've just described, essentially tearing apart, tweaking and reassembling that managed object model, is going to be quite a few lines of code. If this is a really just a simple "notes and lists" app, and if these are the only these two entities, it would probably be less code to ditch those .mom files and create the whole managed object model from scratch, in code. It's not that hard. Put on your Objective-C glasses and look at the managedObjectModel() function in main.m of Apple's old Core Data Utility sample project.
Alright, so turns out I had a slight misunderstanding of the Core Data Stack but, this is an extremely simple task. I was able to get this to work very easily based on some research and #Jerry Krinock's answer.
Create a framework containing the needed files from the NotesApp.
Link the framework to the ListApp.
Grab mutable references to the ListApp and NotesApp NSManagedObjectModel.
Programmatically add a NSRelationshipDescription between the ListItem entity in the ListApp Model and the Note entity in the NotesApp Model (and vice versa for the inverse).
Create a NSManagedObjectModel by merging the ListApp and NotesApp models.
NOTE: As #Jerry Krinock mentioned this only needs to be done once since we are merging the two models together and storing them in the same persistent store. This is the same as doing it through the CoreData Model Builder UI except programmatically since it doesn't support referencing entities from separate models (at least not that I know of or could find).
References:
Core Data Programming Guide
Core Data stack
Universal Cocoa Touch Frameworks for iOS8 – (Remix)
Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription
Objective-C:
NSManagedObjectModel * listModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self listModelURL]] mutableCopy];
NSManagedObjectModel * notesModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self notesModelURL]] mutableCopy];
NSEntityDescription * listEntity = [listModel.entitiesByName objectForKey:NSStringFromClass([JBListItemMO class])];
// The framework name is prepended to the class name. Remove it before getting the note's entityDescription.
NSString * noteClassName = [NSStringFromClass([JPSNoteMO class]) componentsSeparatedByString:#"."].lastObject;
NSEntityDescription * noteEntity = [notesModel.entitiesByName objectForKey:noteClassName];
NSRelationshipDescription * whichListRelationship = [[NSRelationshipDescription alloc] init];
whichListRelationship.minCount = 0;
whichListRelationship.maxCount = 1;
whichListRelationship.optional = NO;
whichListRelationship.name = #"whichList";
whichListRelationship.destinationEntity = listEntity;
whichListRelationship.deleteRule = NSNullifyDeleteRule;
NSRelationshipDescription * notesRelationship = [[NSRelationshipDescription alloc] init];
notesRelationship.ordered = NO;
notesRelationship.maxCount = 0;
notesRelationship.minCount = 0;
notesRelationship.optional = YES;
notesRelationship.name = #"notes";
notesRelationship.destinationEntity = noteEntity;
notesRelationship.deleteRule = NSCascadeDeleteRule;
notesRelationship.inverseRelationship = whichListRelationship;
whichListRelationship.inverseRelationship = notesRelationship;
listEntity.properties = [listEntity.properties arrayByAddingObject: notesRelationship];
noteEntity.properties = [noteEntity.properties arrayByAddingObject: whichListRelationship];
self.managedObjectModel = [NSManagedObjectModel modelByMergingModels:#[listModel, notesModel]];
I'll post the Swift 3 code when I've finished converting my CoreDataStack class.

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.

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.

Keeping references for entities from 2 different Core Data models at the same time

I have 2 distinct core data models in my iOS app. In the first VC, I create a reference to an entity UserDetail in a singleton class I've designed.
After the user leave this VC, I use another core data model, completly different from the first one. I change objectModel, objectContext and persistentStore.
And it seems that after I change to this second core data model I lose the reference to the entity UserDetail that I've created in the beginning.
After the fetch I save the reference like this:
UserAccount *account = [array objectAtIndex:0];
sharedInstance.account = account;
and after changing the core data model whenever I try to access sharedInstance.account it's nil. Should the property be (copy)?
Am I missing something?
Thanks

Implementing CoreData using the parent/child approach with temporary objects

Question: What will be the best way to handle bunch of data that just some of the objects should be saved to disk in coredata?
This is the app/problem:
The app will provide users with the possibility to search for different items on the internet.
The search will return a number of objects that will be displayed to the user.
The user should be able to favorites any of these object at any time. Objects that has been favored should be connected to the current logged in user and live after the app has quit.
The app will have iOS6 as base.
I have been using using these resources
Apple's Core Data Programming Guide: Efficiently Importing Data
Implementing Fast and Efficient Core Data Import on iOS 5
iDeveloperTV CoreData performance course
Im currently looking into the parent/child approach with use of 3 Contexts: Master, Main and Confinement context types.
Current possible solution:
MasterContext that perform save on disk (Has the persistentStoreCoordinator)
MainContext that is used by the UI (child of the masterContext)
BackgroundContext to handle new objects from searches. (child of the mainContext)
So the user may do a search that will return 100 objects (imported on the background context and saved up to the main context).
2 of these objects are favored by the user (on the maincontext). The object will be added to the user and set as "should be saved". (On a save the objects will be pushed up to the master context)
When I save the mastercontext I dont want to save all the 100 objects to disk. Just the two objects the user have favored.
So im think about deleting the object that should not be saved to disk just before I do a save on the mastercontext.
- (void) mainContextHasSaved: (NSNotification *) notification {
NSLog(#"Lets save the master");
[_masterManagedObjectContext performBlock:^{
//Loop through all inserted object and check if they should be saved to disk
[self removeObjectThatShouldNotBeSavedToDisk];
NSError *error = nil;
BOOL saveSuccess = [_masterManagedObjectContext save:&error];
if(saveSuccess) {
//Do something
}
}];
}
But after what I understood is that when a save is performed on a parent context, all the changes will be propagated to the children. Then I will loose all the objects except the two that has been stored.
So does anyone know how to solve this kind of problem? Is it something I can do in the example presented above? Or should I create multiple persistentStores and move objects between contexts?
Thanks to all that is willing to help and if more information is needed just ask :)
In a similar project I used this solution which was also favored by the users:
Keep a time stamp attribute in the downloaded items and delete them when the time stamp is older than a certain threshold and they are not marked as favorite.

Resources