iOS Core Data data insert - ios

I have an app which asynchronously downloads a JSON file and then it should insert those objects within Core Data for persistent storage. Regarding the insert, is it a good idea to do it from the main thread? What if there are thousands of objects? Should I do the inserts on a different thread? Could you provide me with some snippets regarding this matter? Regarding the fetching of the objects after I've saved them, should I also use a different thread?
My code for inserting into Core Data is:
- (void) insertObjects:(NSArray*)objects ofEntity:(NSString *)entityName
{
NSString *key;
NSManagedObject *managedObject;
NSError *error;
for(NSDictionary *dict in objects){
managedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:_managedObjectContext];
for(key in dict){
[managedObject setValue:dict[key] forKey:key];
}
}
[_managedObjectContext save:&error];
}
PS: The objects are of the same entity. The project runs on iOS 7.0 or higher.

Since I can't comment yet..
What iOS Versions do you plan to support? If 5 and higher, this might help Concurrency stack
Summary of the link:
you create a context of private concurreny type to access your physical data
based on this you create a context of main concurreny type
on top of this you use private concurrency type stores again.
Don't forget so save in every store, otherwise, the data seems to be saved while the app is running, but after restart it is lost.
And yes, you want to do it an extra thread, since it would otherwise block the UI if there are to many items.

Related

IOS Coredata. Is it possible to see saved data without fetching it

I am working on core-data base project. I want to know that is it possible to see which data are saved in coredata without fetching it ?
For example:
I am using this Tutorial to learn coredata. **
Core Data demo from AppCoda
I have implement below method to save the data in Data-model.(coredata)
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Device" inManagedObjectContext:context];
[newDevice setValue:self.nameTextField.text forKey:#"name"];
[newDevice setValue:self.versionTextField.text forKey:#"version"];
[newDevice setValue:self.companyTextField.text forKey:#"company"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
but i want to know that, is it possible or is there any way to see what data are saved in my data model without Implement it's fetching method ?
Fetch From Coredata:
Yes. We can see saved data without fetching is .
After implement Save method core-data save sql file in Document Directory.
You can print it in nslog with using this Line.
NSLog(#"%#",[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]);
and you can see full path of document folder printed in log like this.
2015-10-13 12:40:51.253 MyStore[1860:69256] file:///Users/username/Library/Developer/CoreSimulator/Devices/C548BFA2-7B92-42E6-9D64-E16AFF0645D9/data/Containers/Data/Application/9C24913C-B295-4AA1-8DE9-A261CAA21624/Documents/
then you have to go in this folder. with selecting G0-> Go to folder... option.
Then print your Document path in go to folder window.
Note:- you have to write only this code in go to folder. (From ~/Library )
~/Library/Developer/CoreSimulator/Devices/C548BFA2-7B92-42E6-9D64-E16AFF0645D9/data/Containers/Data/Application/9C24913C-B295-4AA1-8DE9-A261CAA21624/Documents
and press GO button.
you will find your sqlfile.
open it with using some sqllite reader software.
SQL Lite pro this is the best software to read .sql file. open your file using it.
and you will see your saved data in it.
During runtime: No.
That is due the nature of Core Data. It is an object graph, not a data persistence solution. A fetch doesn't return an object if he can choose.
Outside of your app it depends on the persistence if any. If you decided to go for a sql store, then consider the sql answer. If you did select a different store type, then it depends on the type you went for.
For most projects are basic foundation objects fine since they can be serialized. If you parse an JSON you get foundation objects back. Consider Core Data when you deal with bigger data that need to be searchable even when you don't have the data in memory.

WatchKit Core Data Sync Up

I have an app structured as follows
iOS App Writes data to Core Data which has a persistent store stored in a shared app group.
The Watch Kit extension is able to read data from Core Data that was written by the iOS app.
The issue I am having is if my iOS app writes data while my watch kit app is open I am not getting updates because the object context is not syncing with the data on the disk.
Is there a way that since my watch kit extension is only reading data to be able to refresh the context and force it to load again from the data on the disk?
My working solution was using MMWormhole to send notification (NSManagedObjectContextDidSaveNotification) from iPhone app to my watch app. In the watch app's controller i used mergeChangesFromContextDidSaveNotification: method of NSManagedObjectContext.
// in iPhone app's notification handler
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"your.group.container.identifier" optionalDirectory:nil];
[wormhole passMessageObject:notification identifier:#"your notification identifier"];
// in WKInterfaceController's awakeWithContext: method
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"your.group.container.identifier" optionalDirectory:nil];
[wormhole listenForMessageWithIdentifier:#"your notification identifier" listener:^(id messageObject) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:messageObject];
}];
Then NSFetchedResultsController did all other work with UI updates.
You must implement initWithCoder: and encodeWithCoder: methods from NSCoding protocol for your NSManagedObject subclasses because MMWormhole uses NSKeyedArchiver as a serialization medium.
- (id)initWithCoder:(NSCoder *)decoder {
NSManagedObjectContext *context = ... // use your NSManagedObjectContext
NSPersistentStoreCoordinator *coordinator = ...; //use your NSPersistentStoreCoordinator
NSURL *url = (NSURL *)[decoder decodeObjectForKey:#"URIRepresentation"];
NSManagedObjectID *managedObjectID = [coordinator managedObjectIDForURIRepresentation:url];
self = [context existingObjectWithID:managedObjectID error:nil];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:[[self objectID] URIRepresentation] forKey:#"URIRepresentation"];
}
I ran into the same issue. I used - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag in the NSManagedObjectContext to get the latest data for the managed object.
Ran into similar issues. Despite creating a shared Fetched Results Controller in the App Group observing Managed Object Context changes and refreshing Managed Object Context were not feasible.
Managed Object Contexts cache a certain level of the object graph for retrieval without reading from the actual SQLite store on the disk. The only potential way to actually get live sync between the two would be sending messages across iOS app to Extension when the MOC changes and destroying/rebuilding the Core Data stack from the disk every single time, not a great solution at all.
I believe the use case for live sync between iOS and Extension at initial launch isn't a necessity. Hopefully we get a more deliberate solution to this problem in a future release.

Magical Record, multiple configurations and Restkit

I am using Magical Record in a fairly large IOS project. I use configurations to separate a large seed database from user data. Since Magical Record doesn't support configurations, I deconstructed Magical Record's setupCoreDataStackWithAutoMigratingSqliteStoreNamed method and replaced it with the following:
+(void) RB_setupMultipleStores:(NSString *) seedStoreName userStore:(NSString *) userStoreName
/* change persistent store to one with multiple configurations. Assumes Magical Record is initialized to perform auto migration. */
{
NSError * error= nil;
[MagicalRecord cleanUp]; //Tear down Magical Record
NSManagedObjectModel * model = [NSManagedObjectModel MR_defaultManagedObjectModel];
NSURL *seedURL = [NSPersistentStore MR_urlForStoreName:[seedStoreName stringByAppendingString:#".sqlite"]];
NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSPersistentStore * seedStore =[coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Seed"
URL:seedURL
options:options
error:&error];
if (!seedStore || error)
{
NSLog(#"Error setting up seed store:%# for %#", [error localizedDescription], seedURL);
exit(-1);
}
NSURL *userURL = [NSURL URLForDocumentDirectoryWithAppendedPath:[userStoreName stringByAppendingString:#".sqlite"]];
NSPersistentStore * userStore = [coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"User"
URL:userURL
options:options
error:&error];
if (!userStore || error)
{
NSLog(#"Error setting up user store:%# for %#", [error localizedDescription], userURL);
exit (-1);
}
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
//Bring back Magical Record with updated coordinator.
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
[[NSManagedObjectContext MR_defaultContext] setUndoManager:nil];
}
Now I will be adding Restkit to the mix. I'd need to share the object model and persistent store, and I'd rather use one set of contexts rather than have two different stacks.
I see five potential approaches:
Modify https://github.com/blakewatters/RKMagicalRecord to support multiple configurations. This looks trivial but it requires I use a category to surface some private methods, and the good MR developers recommend against explicitly setting MR default and root saving contexts.
Create the Magical Record contexts first, and then assign them to Restkit. Will this even work? Does it make sense at all?
Initialize Restkit and Magical Record from the same NSPersistentStoreCoordinator. Does this make sense?
Create two separate stacks with different but similar NSPersistentStoreCoordinators.
Create my own stack and contexts, and make all calls to Restkit and MR using those contexts.
Can anyone recommend any of these or any other approaches? Each requires significant effort to even test. I'm about to head down the #1 path.
Thanks...
Fundamentally, CoreData has ways to deal with this already. You can have multiple coordinators point to the same store, or whatever your scenario is. It's up to each framework to allow you to be used against a stack that framework itself did not create or setup.
MagicalRecord boils down to a collection of helper methods for fetching and saving. You don't need to use ANY of the setup methods for MagicalRecord to work its magic on your fetches. The setup methods are there to help you get going faster on new projects, as well as providing a single access point for a "default" contexts for use when you perform fetches on the main thread/queue. For all other use, use the explicit inContext: parameter every time. Using that parameter, you can use whatever MOC you want, everything will work. This is by design. MagicalRecord was never written to replace CoreData. It was written to make the simple tasks easier.
With that, you could just let RestKit deal with your entire CoreData stack and use MagicalRecord only for the convenience APIs for fetching and saving. This would save you having to do any of those solutions and just get back to tackling your app specific issues...

Magical Record appears to save, but changes are lost if app is terminated

I'm using Magical Record 2.1 to handle the data persistence in my app. If I create a new entity, set some it's attributes and save, it works fine. However, later, if I fetch that entity, update it's attributes and save, subsequent fetches have the new data until I terminate the app and restart. During the new app session the old data reappears.
This is how I create a new entity:
self.localContext = [NSManagedObjectContext MR_defaultContext];
self.theNewListing = [Listing MR_createInContext:self.localContext];
I'm using MRDefaultContext having read this blog post: http://saulmora.com/2013/09/15/why-contextforcurrentthread-doesn-t-work-in-magicalrecord/
In this case my main attribute is a dictionary, and I set it like this:
NSMutableDictionary *tempDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"A description", #"slFieldDescription", etc, etc, nil];
self.theNewListing.dataDictionary = tempDictionary;
This is how I save it:
[self.presentingViewController dismissViewControllerAnimated:YES completion:^(void) {
[self.localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){
if(!success) {
NSLog(#"%#", error);
}
else {
[self.thePresentingVC refreshCollectionViews:nil];
}
}];
}];
I display my data in a collection view, and at this point everything looks fine. If I terminate and restart the data is still there.
If I fetch the entity again and update the attributes like this:
NSMutableDictionary *newTempDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"A new description", #"slFieldDescription", etc, etc, nil];
self.theNewListing.dataDictionary = newTempDictionary;
Then save using the same save code as above, and then update my collection view using the code below, all looks good.
self.listingsArray = [[NSMutableArray alloc] initWithArray:[Listing MR_findAllSortedBy:#"dateListed" ascending:NO]];
[self.mainCollectionView reloadData];
That is, until I quit the app and restart.
If you're wondering, I'm using FTASync, and this only supports MR 2.1, which is why I haven't upgraded to the latest version.
Thanks!
Not sure about MR, nor why you would need it. If that framework can give you the main context, just call the native Core Data save.
[context save:nil];
Cracked it!!
I noticed that my other attributes were saving, just not this one (this one holds all the data that is presented in the UI) and this led me on another line of investigation.
So, it seems that I needed to be working with immutable dictionaries to store this data as explained here:
Core Data saving problem: can't update transformable attribute (NSArray)

Switching between Core Data stores

I am currently updating an app to use Core Data. The app you could say is a "database viewer", only one database is able to be viewed at a time. Each database is kept in its own separate folder. Currently the data is downloaded and stored as a set of plist files.
In the new version I need to convert these plist databases into Core Data stores (one store for each database.) I've already setup the methods that create the separate store files, and crete the entities. The problem is that all the entities are saved to the first database I created, not to the "current" or "lastly created" file.
The basic process I'm using is:
//For each database {
//Create the sqlite file and set up NSManagedObjectContext
[MagicalRecord setupCoreDataStackWithStoreNamed:
[NSURL fileURLWithPath:
[NSString stringWithFormat:#"%#/%#/%#.sqlite",
dirPath, directory, directory]]];
NSManagedObjectContext *managedObjectContext =
[NSManagedObjectContext MR_contextForCurrentThread];
//Iterate through all the plist files and create the necessary entities.
//Save new entities to file
[managedObjectContext MR_save];
//Clean up all cashes
[MagicalRecord cleanUp];
}
How would one properly switch between stores, essentially "reseting" everything between each switch. Preferably (if possible) using magical record.
EDIT:
I've found out a portion of the problem, and removed most of the unwanted behavior. It turns out, you can't reliably call [MagicalRecord cleanUp] on a background thread. Also, It isn't doing what I think it should (see below). I ended up calling back to the main thread after each "save" to reset the Core Data stack. Doing this creates a new context for the first three databases. after that, it duplicates the context from the database three databases ago. So the same three contexts are used in a loop.
This is what I currently have;
I start the process by creating a background thread and run the code to create a single database in the background:
backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self createSQLiteDatabase:updateList];
});
Then creating the stack and database:
- (void)createSQLiteDatabase:(NSArray *)updateList
{
NSString *directory = [updateList objectAtIndex:0];
[MagicalRecord setupCoreDataStackWithStoreNamed:
[NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#/%#.sqlite",
dirPath, directory, directory]]];
NSManagedObjectContext *managedObjectContext =
[NSManagedObjectContext MR_contextForCurrentThread];
//Check to see if the stack has reset
NSLog(#"Before:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);
//Create and add entities to context...
//Prepare for next loop
NSLog(#"After:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);
[managedObjectContext MR_saveNestedContexts];
[NSManagedObjectContext MR_resetContextForCurrentThread];
NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:updateList];
[temp removeObjectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^(void){
[self shouldContinueUpdating:temp];
});
Then reset everything and repeat for all databases:
- (void)shouldContinueUpdating:(NSArray *)databases
{
//preform cleanup on main thread and release background thread
[MagicalRecord cleanUp];
dispatch_release(backgroundQueue);
if ([databases count] != 0) {
backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self createSQLiteDatabase:databases];
});
}
}
With the two NSLogs, I get this in the console: (using six databases, the pattern is the same no matter how many databases I convert.)
//First Loop
Before:0
After:308
//Second Loop
Before:0
After:257
//Third Loop
Before:0
After:37
//Fourth Loop
Before:308
After:541
//Fifth Loop
Before:257
After:490
//Sixth Loop
Before:37
After:270
... Keep adding to each of the three contexts.
And [MagicalRecord cleanUp] isn't doing what It say it's doing. Here is what the method is supposed to do.
+ (void) cleanUpStack;
{
[NSManagedObjectContext MR_cleanUp];
[NSManagedObjectModel MR_setDefaultManagedObjectModel:nil];
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:nil];
[NSPersistentStore MR_setDefaultPersistentStore:nil];
}
But It turns out that the NSStoreCoordinator every time I save, is the same coordinator, in the same memory location, and each store is hanging around. Something is not working right...
MagicalRecord may not be the best tool for this job for you...
First, let's correct your usage of the setupCoreDataStackWithStoreNamed: method. The parameter takes an NSString, not a URL, nor a file path. MagicalRecord will pick the proper path for you and create your store there. your resulting sqlite file is likely to be named with the path you intended it to be.
Next thing, you'll need to dynamically create your CoreData model for this file. This is kind of tough, but possible. You'll need to traverse these plist files, and interpret entities, attributes and relationships, and create corresponding NSEntityDescriptions, NSAttributeDescriptions and NSRelationshipDesctiptions and populate an NSManagedObjectModel "manually". Youll want to look for the method
- [NSManagedObjectModel setEntities:(NSArray *)]
as well as the creation methods for NSEntityDescription, NSAttributeDescription and NSRelationshipDescription.
You'll also want to save this model somewhere so you don't have to recreate it every time. Luckily, it conforms to NSCoding, so you should just be able to save it to disk.
After that, you'll probably want to populate your data. From here, MagicalRecord can help you. I suggest looking at the Importing Data Made Easy blog post I wrote for Cocoa is My Girlfriend
If you want to "switch stores", which I guess means you want to create a new store for each plist file you've got, then you're going to have to tear down the entire Core Data stack for each file. If you manage to use MagicalRecord for this project, you'll need to look at [MagicalRecord cleanUp], and start over. If each model was the same, you could get by with releasing your Persistent Store Coordinator, and creating a new one to your store. But since your "schemas" will probably be different, you'll just want to scratch everything and start over.
Turns out The problem I'm having is because of a bug in MagicalRecord. I've submitted a git issue here: https://github.com/magicalpanda/MagicalRecord/issues/270

Resources