I am implementing a navigation-based application. The user will drill down to the node of interest. I am using Core Data, mostly because I want to try it out. How do I load the database initially? Should I write custom code that loads the database initially, or is there some tool to do it for me ?
Here's a simple way to preload the Core Data store using plists.
Make a property list containing an array of dictionaries. Make the keys of each dictionary correspond to the keys of your managed object.
Then, call this method the first time the app launches:
- (void)loadDataFromPropertyList {
NSString *path = [[NSBundle mainBundle] pathForResource:#"someFile" ofType:#"plist"];
NSArray *items = [NSArray arrayWithContentsOfFile:path];
NSManagedObjectContext *ctx = self.managedObjectContext;
for (NSDictionary *dict in items) {
NSManagedObject *m = [NSEntityDescription insertNewObjectForEntityForName:#"TheNameOfYourEntity" inManagedObjectContext:ctx];
[m setValuesForKeysWithDictionary:dict];
}
NSError *err = nil;
[ctx save:&err];
if (err != nil) {
NSLog(#"error saving managed object context: %#", err);
}
}
Call loadDataFromPropertyList the first time the app launches by including the following code in the implementation of application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:#"firstRun"])
{
[defaults setObject:[NSDate date] forKey:#"firstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self loadDataFromPropertyList];
}
There is no automatic (i.e. built-in) method for importing data into a Core Data context.
I suspect that this is because Core Data is really an object graph management framework (that just happens to be able to persist that object graph to disk) and the mapping between data and object instances depends on the schema (and so will require at least some code).
If you already have the data in another format, you should read the section of the Core Data Programming Guide on importing data into a Core Data context. If you don't already have the data in an other format, you will have to write code either way (to generate an intermediate format or to populate the context directly).
Finally, although it is not really a public API, the Core Data XML format is pretty easy to work with by hand or using any number of XML-based tools. Depending on the complexity of your data, you may be able to reverse-engineer the XML schema enough to generate an XML-backed persistent store. You could then migrate this store to an SQLite persistent store and you're on your way.
Currently as far as I know you have to write custom code to populate the database.
This does seem like a downside, I'd really like to see a tool for batch (shell) pre-population of data sources meant to be used by CoreData.
Related
I'm trying to delete all files that my application has persisted using NSCoding. Seems like it should be a simple thing to do, but I can't find an answer, whilst I'm showing just one class here, I have several and I'd like the delete function to save all instances of all classes my app has archived. I'm not doing anything different to NSCoder:
So my interface is defined
Details : NSObject <NSCoding>
My implementation has:
- (id)initWithCoder:(NSCoder *)decoder
{
NSLog(#"initWithCoder 'Details'");
if (self = [super init])
{
NSLog(#"Decoding 'Details'");
self.name = [decoder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
NSLog(#"Encoding 'Details'");
[encoder encodeObject:self.name forKey:#"name"];
}
When I write it I'm using:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:data forKey:identifier];
I did start off trying to look in the documents directory, but I think as I'm writing this and from my research that it will be written to NSUserDefaults (and that might not be the best thing). So at the risk of asking two questions, how do I delete all my objects that are saved/archived using NSCoder to NSUserDefaults (and potentially, should I be setting this to something else (i.e a sub-folder inside the documents folder....if yes, how would I do that).
NSCoding provides a way to transform objects into NSData for easy archiving. What you do with those NSData objects afterward are your responsibility. You may or may not save them to individual files, but NSCoding is not aware of that.
It seems like you have been dumping those NSData into NSUserDefaults using [userDefaults setObject:data forKey:identifier];
You need to delete all entries for every identifier you have used by doing [userDefaults removeObjectForKey:identifier];.
You can't automatically determine what those entries are.
Additionally, you might want to consider not hogging NSUserDefaults and save those to individual files. (look at [NSData writeToFile:]) This will additionally help you track what those files are (you could for example all place them in the same folder/subfolder).
I have found these Stanford tutorials https://itunes.apple.com/us/course/ipad-iphone-app-development/id495052415, and have been listening a lecture about core data, which is really great.
It shows how to access Core Data via UIManagedDocument... Only thing I don't understand how UIManagedDocument knows which model should it use, because I don't see it set anywhere?
To sum it when using UIManagedDocument, how do you define database model that is gonna be used?
I found the answer:
'UIManagedDocument' takes all the models from your application's main bundle, and makes union of these models. If you only have one model that model is used.
This can be changed by overriding 'UIManagedDocument' class.
Loading a Data Model
In some cases, you do not have to write any code to load a model. If you use a document-based application on OS X, NSPersistentDocument manages the task of finding and loading your application’s model for you. If you use Xcode to create a non-document application that uses Core Data (for OS X or for iOS), the application delegate includes code to retrieve the model. The name of a model—as represented by the filename used to store it on disk—is not relevant at runtime. Once the model is loaded by Core Data, the filename is meaningless and has no use, so you can name the model file whatever you like.
If you want to load a model yourself, there are two mechanisms you can use:
You can load a single model from a specific URL, using the instance method initWithContentsOfURL:.
This is the generally-preferred technique. Typically an application has a single model, and using this method you ensure that you load only that model. You can also load individual models via URLs and then unify them using modelByMergingModels: before instantiating a coordinator with them.
In cases where you have more than one model—and particularly in cases where the models represent different versions of the same schema—knowing which model to load is essential (merging together models with the same entities at runtime into a single collection would cause naming collisions and errors). This method is also useful if you want to store the model outside of the bundle for your application, and so need to reference it via a file-system URL.
You can create a merged model from a specific collection of bundles, using the class method mergedModelFromBundles:.
This method may be useful in cases where segregation of models is not important—for example, you may know your application and a framework it links to both have models you need or want to load. The class method allows you to easily load all of the models at once without having to consider what the names are, or put in specialized initialization code to ensure all of your models are found.
Accessing and Using a Managed Object Model at Runtime
NSManagedObjectModel *model = <#Get a model#>;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey:#"Publication"];
[requestTemplate setEntity:publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
#"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
[model setFetchRequestTemplate:requestTemplate
forName:#"PublicationsForAuthorSinceDate"];
Using a fetch request template
NSManagedObjectModel *model = <#Get a model#>;
NSError *error = nil;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
#"Fiona", #"FIRST_NAME", #"Verde", #"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow:-31356000], #"DATE", nil];
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName:#"PublicationsForAuthorSinceDate"
substitutionVariables:substitutionDictionary];
NSArray *results =
[aManagedObjectContext executeFetchRequest:fetchRequest error:&error];
I am new to this development.
I want to store complete object of a class into my database.
Actually I am creating application where user can add multiple views to parent view and want to save it, so that next time when user fetches it, he will get what ever he has saved i.e. views to parent view previously.
Any logic or suggestion on same will really be helpful,
Thanks in advance
Any object you want to save will need to conform to the NSCoder protocol. Keep in mind that if you have custom objects within your parent object that they to will need to conform to NSCoder. Add the following methods to your custom class(es).
- (id)initWithCoder:(NSCoder *)coder {
_inventory = [[coder decodeObjectForKey:#"inventory"] retain];
return self;
}
- (void)encodeWithCoder:(NSCoder *) encoder{
[encoder encodeObject:_inventory forKey:#"inventory"];
}
In the example above I want to encode a player's inventory. If the inventory contains custom objects (as opposed to a bunch of NSStrings for example) they'll also need their own NSCoder methods.
Below I turn it into something you can save out to NSUserDefaults. Adjust appropriately to store in a DB. Keep in mind if you want to send NSData over the wire to store in a DB you'll want to convert it to Base64 and possibly compress it.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData * encodedObject = [NSKeyedArchiver archivedDataWithRootObject:rootObject];
[defaults setObject:encodedObject forKey:kSaveArchiveKey];
[defaults synchronize];
To go the other way, you'll want to grab your NSData, do whatever magic on it as I described above (base64, compression) and unarchive.
PlayerInventory *inventory = [NSKeyedUnarchiver unarchiveObjectWithData:playerInventoryData]
You should choose between NSCoding and Core Data depending on your exact needs. See this post for more info: NSCoding VS Core data
You can store state(value of any attributes) of any object in database. You should use NSCoding.Example here
As all who usually ask such questions I'm newbie in iPhone SDK programming and I've spend realy a lot of time to find the solution by my self.
So, I need to save a lot of app data of diff datatypes (bool,string,int,float,double,int arrays,double arrays and arrays of pointers) to file. There're many forms with fields, app settings etc. All that I need to save either user quit the app, or it was terminated unexpectedly. User could work with as much forms with fields, as it's possible. File is needed to open project with all filled forms and app sets on other device.
My trouble is that I couldn't find in what way I should wrilte all that data to file. I tried two diff ways. 1st: fill array with form's data, fill NSMutableArray with such arrays for every form. But for NSMutableArray there's a method (void)addObject:(id)anObject, and I didn't find how to get that (id)anObject from every array(or anything else) to add items.
The 2nd I've tried to use: NSMutableData. The same troubles: to fill with my data I need to convert them to Data ((void)appendData:(NSData *)otherData). In both cases objecs of that classes couldn't be filled correctly with my data. And I can't save it to file.
Maybe there's some better solution? Will be very apreciative for any help.
NSNumber is usually the way to go for basic stuff (integers, bools, and doubles/floats/etc). For example:
NSNumber *aBool = [NSNumber numberWithBool:YES];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0f];
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
[settings setValue:aBool forKey:#"some_key"];
The strings you can just add to NSUserDefaults because they are already objects (that conform to NSCoder, I believe), and for the NSArrays, just use the writeToFile: method:
NSArray *someArray = [NSArray arrayWithObjects:#"foo",#"bar",#"etc",nil];
[someArray writeToFile:#"filename" atomically:YES];
I've been experiencing very inconsistent results while developing an iPhone app and trying to save preferences via the standard NSUserDefaults mechanism. I am using code almost straight out of the iPhone Developer's Cookbook by Erica Sadun (fantastic book btw), it looks like this:
(void) updateDefaults
{
NSMutableArray *spells = [[NSMutableArray alloc] init];
NSMutableArray *locs = [[NSMutableArray alloc] init];
for (DragView *dv in [boardView subviews])
{
[spells addObject:[dv whichSpell]];
[locs addObject:NSStringFromCGRect([dv frame])];
}
[[NSUserDefaults standardUserDefaults] setObject:spells forKey:#"spells"];
[[NSUserDefaults standardUserDefaults] setObject:locs forKey:#"locs"];
[[NSUserDefaults standardUserDefaults] synchronize];
[spells release];
[locs release];
}
The values are saved, sometimes...and restored, sometimes. I can't get an exact bead on what does or does not make it work.
Does anyone else have any similar experiences? Any suggestions on what might make it work? Is the synchronize method the best way to force a disk write and make the values save, or is there something better (both for production, as well as simulator).
Thanks
Ryan
You should be using an NSKeyedArchiver for saving your arrays, such as:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:spells] forKey:#"spells"];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:locs] forKey:#"locs"];
[[NSUserDefaults standardUserDefaults] synchronize];
You should also make sure your spells class implements the NSCoding protocol (encodeWithCoder: and initWithCoder:), if it's a custom class. It looks like your locs are NSStrings, which will archive just fine.
You'll also need to do something like
NSData *dataRepresentingSavedSpells = [currentDefaults objectForKey:#"spells"];
if (dataRepresentingSavedSpells != nil)
{
NSArray *oldSpells = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedSpells];
}
To load the old values from the defaults.
I use synchronize to write to disk on exit, and it's been very reliable in my experience.
On Mac OS X, and probably also on iPhone OS, the defaults database can only contain property list objects: NSString, NSNumber, NSArray, NSDictionary, and NSData.
It's not clear from your code snippet what type a spell is, but if it isn't one of the above, it won't get stored in the defaults database. Your options are:
Write a method that will convert your object to and from a plist representation. In other words, a method to create an NSDictionary from your object and another method to initialize the object using an NSDictionary. You could also choose to store it as an NSArray or NSString.
Make your object implement the NSCoding protocol and then used NSKeyedArchiver to convert the object to an NSData object, and NSKeyedUnarchiver to convert it back.