Core Data sqlite file embedded in my app? - objective-c

I'm trying to make a little dictionary on my app by using Core Data.When you use a Master-Detail application, xxx.sqlite file gets created in the user's Documents folder. Now before I start the application for the first time, I changed the code as below because I wanted the xxx.sqlite file in the app:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
//NSURL *storeURL = [[self applicationDocumentsDirectory]
URLByAppendingPathComponent:#"CoreDataFileTest.sqlite"];
NSString* path= [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"CoreDataTest.sqlite"];
NSURL* storeURL = [[NSURL alloc] initFileURLWithPath:path];
I thought the last two line would make a xxx.sqlite file in my app project folder.
Actually, it didn't. But the app works fine. This means that the xxx.sqlite file is embedded in the app itself? Thank you for your time.

If, as I understand your question, you would like to include a default database with your app (i.e. the .sqlite file will be included as an asset of your project, then you need to copy it from the app bundle into your documents directory at startup).
Then call this function from your didFinishLaunchingWithOptions method in your app delegate.
- (void) copyDefaultDB
{
// If we are running the app for the first time, then copy our default database across from our app directory
// into our user directory
NSString *filePath;
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex: 0];
NSFileManager *fileManager = [NSFileManager defaultManager];
filePath = [documentsDir stringByAppendingPathComponent: #"CoreDataFileTest.sqlite"];
// If the database already exists then return without doing anything
if (![fileManager fileExistsAtPath: filePath])
{
// Now copy the new shiny one in
NSString *filePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: #"CoreDataFileTest.sqlite"];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath: filePathFromApp
toPath: filePath
error: nil];
}
}

Try with this code. Write this inside AppDelegate.m
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"ProjectManagement.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:nil error:&error]){
/*Error for store creation should be handled in here*/
}
return persistentStoreCoordinator;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
- (NSString*)applicationDocumentsDirectory{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
}

Related

How to get list of contents from folder using contentsOfDirectoryAtPath in cocoa?

I want to fetch all contents of the folder and add to a submenu of NSMenuItems.
For achieving this, I'm using code given below:
NSArray *dirContents = [[NSArray alloc]init];
dirContents=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:updated error:nil];
This code is working but only in one folder. Strange but it is true. I have tried the same code for other folders but it gives nil value in dirContents.
So how can I access the list of all the contents of a selected folder?
Try this to track the access error.
NSError *error;
NSArray *dirContents = [[NSArray alloc]init];
dirContents=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:updated error:&error];
if(error){
NSLog(#"%#",error);
}
Make sure you use relative path to the directory, if the app is sandboxed, not absolute path.
NSURL *containerUrl =[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"Your.app.container.id"];
path = [[containerUrl URLByAppendingPathComponent:#"your/folder/path"] path];
+(NSArray*)allURLs:(NSURL*)url{
NSFileManager *fileManager = [NSFileManager defaultManager];
//NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
NSURL *bundleURL = url;
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:bundleURL
includingPropertiesForKeys:#[NSURLNameKey, NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:^BOOL(NSURL *url, NSError *error)
{
NSLog(#"[Error] %# (%#)", error, url);
return url;
}];
NSMutableArray *mutableFileURLs = [NSMutableArray array];
for (NSURL *fileURL in enumerator) {
NSString *filename;
[fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil];
NSNumber *isDirectory;
[fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
// Skip directories with '_' prefix, for example
if ([filename hasPrefix:#"_"] && [isDirectory boolValue]) {
[enumerator skipDescendants];
continue;
}
if (![isDirectory boolValue]) {
[mutableFileURLs addObject:fileURL];
}
}
return mutableFileURLs;
}

Core Data Model path vs Store path

Previously, I have an app that uses core data. I use same store url to init NSManagedObjectModel and create NSPersistentStoreCoordinator. However, in the new app, I tried to use the same way, the model can not be created. So I have to use a model url (I found it in this forum) to be able to create NSManagedObjectModel. What is the issue?
Here is from OLD app:
- (NSString *)storeName
{
return #"ABC.storedata";
}
- (NSURL *)storeUrl
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:self.storeName];
return storeURL;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel == nil) {
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[self storeUrl]];
}
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeUrl] options:nil error:&error]) {
abort();
}
return _persistentStoreCoordinator;
}
Here is from NEW app:
- (NSString *)storeName
{
return #"DEF.sqlite";
}
- (NSURL *)storeUrl
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:self.storeName];
return storeURL;
}
- (NSURL *)modelUrl
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"DEF" withExtension:#"momd"];
return modelURL;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel == nil) {
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[self modelUrl]];
}
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeUrl] options:nil error:&error]) {
abort();
}
return _persistentStoreCoordinator;
}
You can never use the same URL for both model and persistent store because they are two very different things.
Model URL must point to the actual model resource included in your Xcode project which is a .momd file package. Persistent store is in your case a database, a .sqlite file in documents directory.
I cannot imagine how it could have worked in the past. One possibility is that since your ABC.storedata did not have a trailing .sqlite, Core Data must have added a .sqlite to it behind the scenes and DEF.storedata could have matched your model name somehow?
This is the proper way to initialize a model where you replace "Model" with the name you have in the Xcode project for model resource:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
As for persistent store, there are no restrictions. In most cases it should be in application documents directory though.

Prepopulate Core Data in iOS 5

There seem to be some modifications to the NSPersistentStoreCoordinator method is iOS 5.
I am trying to get a prepopulated database ... it doesn't seem to work, no crash but none of the data seems to exist... Any suggestions?
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storePath = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DataModel.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:[storePath absoluteString]]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"DataModel" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:[storePath absoluteString] error:NULL];
}
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DataModel.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
The problem is in the applicationDocumentsDirectory method that gives wrong absolute string. It is not really wrong, but is not suited for NSFileManager class.
The solution is simple. You need to write another method which I called applicationDocumentsDirectory2.
So first, go to header file of your AppDelegate and declare method:
- (NSString *)applicationDocumentsDirectory2;
Now, go to the .m file, and define a method:
- (NSString *)applicationDocumentsDirectory2 {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
And finally, just change persistentStoreCoordinator method into:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSError *error = nil;
NSString *storePath = [[self applicationDocumentsDirectory2] stringByAppendingPathComponent: #"DataModel.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"DataModel" ofType:#"sqlite"];
if (defaultStorePath) {
if(![fileManager copyItemAtPath:defaultStorePath toPath:storePath error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DataModel.sqlite"];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
That's it! :)

Error after adding a new core data model version

I added a new model version, and I set the core data model to use that new version, but I get this error when the application tries to start.
"The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store."
I'm guessing the problem is that the current persistent store is the old version of the model. Is there a way to just delete it so it makes a new one? I don't care about saving any of that data.
You have to migrate between versions. According to Apple's docs, if the changes are simple, you can do lightweight migration.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweight.html#//apple_ref/doc/uid/TP40008426-SW1
Adding these options to the NSPersistentStoreCoordinator seemed to work.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:#"YOURAPP.storedata"];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:options error:&error]) {
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
return persistentStoreCoordinator;
In answer to your question, "Is there a way to delete it so it just makes a new one ?"
Yes.
Just change the persistentStoreCoordinator getter in your App Delegate as follows:
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
if (persistentStoreCoordinator) return persistentStoreCoordinator;
NSManagedObjectModel *mom = [self managedObjectModel];
if (!mom) {
NSAssert(NO, #"Managed object model is nil");
NSLog(#"%#:%s No model to generate a store from", [self class], (char *)_cmd);
return nil;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *applicationSupportDirectory = [self applicationSupportDirectory];
NSError *error = nil;
if ( ![fileManager fileExistsAtPath:applicationSupportDirectory isDirectory:NULL] ) {
if (![fileManager createDirectoryAtPath:applicationSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]) {
NSAssert(NO, ([NSString stringWithFormat:#"Failed to create App Support directory %# : %#", applicationSupportDirectory,error]));
NSLog(#"Error creating application support directory at %# : %#",applicationSupportDirectory,error);
return nil;
}
}
NSURL *url = [NSURL fileURLWithPath: [applicationSupportDirectory stringByAppendingPathComponent: #"storedata"]];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: mom];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType
configuration:nil
URL:url
options:nil
error:&error]){
// EDIT: if error opening persistent store, remove it and create a new one
if([[error domain] isEqualToString:#"NSCocoaErrorDomain"] && [error code] == 134100) {
NSLog(#"Core Data model was updated. Deleting old persistent store.");
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType
configuration:nil
URL:url
options:nil
error:&error]){
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
} else {
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
//
}
return persistentStoreCoordinator;
}
Figure out where your app stored the document and put it in the trash.
But as a extended comment you may wish to examine the possibilities around both explicit and implicit migration in NSPersistentStoreCoordinator and the options in.
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error
Depending how different the versions are you can get it to happen automagically by passing NSMigratePersistentStoresAutomaticallyOption & NSInferMappingModelAutomaticallyOption
theres also
- (NSPersistentStore *)migratePersistentStore:(NSPersistentStore *)store toURL:(NSURL *)URL options:(NSDictionary *)options withType:(NSString *)storeType error:(NSError **)error

how to remove all objects from Core Data

how can I remove all objects? I know I can remove one by
[managedObjectContext deleteObject:objToDelete];
is it possible to delete all without iterating all array?
thanks
This function removes the current SQLite db file from disk and creates a new one. It's much faster than any iterative delete.
-(void)deleteAndRecreateStore{
NSPersistentStore * store = [[self.persistentStoreCoordinator persistentStores] lastObject];
NSError * error;
[self.persistentStoreCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtURL:[store URL] error:&error];
__managedObjectContext = nil;
__persistentStoreCoordinator = nil;
[self managedObjectContext];//Rebuild The CoreData Stack
}
If you want to call this outside Application Delegate (assuming boilerplate CoreData integration) you can use this to get a reference to your app delegate:
YourAppDelegate *appDelegate = (YourAppDelegate *)[[UIApplication sharedApplication] delegate];
Don't forget to import the header.
Marking objects for deletion and then saving works the way it does because Core Data still needs to run the validation rules for all of the objects being deleted. After all, an object can refuse deletion based on how it responds to -validateForDelete:.
If:
you truly want to delete everything in a persistent store
and you don't care about whether the objects in that persistent store say they're valid for deletion
Then:
tear down the Core Data stack that's using that persistent store
and delete the persistent store's file.
this is what I do to "reset" my data store:
- (BOOL)resetDatastore
{
[[self managedObjectContext] lock];
[[self managedObjectContext] reset];
NSPersistentStore *store = [[[self persistentStoreCoordinator] persistentStores] lastObject];
BOOL resetOk = NO;
if (store)
{
NSURL *storeUrl = store.URL;
NSError *error;
if ([[self persistentStoreCoordinator] removePersistentStore:store error:&error])
{
[[self persistentStoreCoordinator] release];
__persistentStoreCoordinator = nil;
[[self managedObjectContext] release];
__managedObjectContext = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:storeUrl.path error:&error])
{
NSLog(#"\nresetDatastore. Error removing file of persistent store: %#",
[error localizedDescription]);
resetOk = NO;
}
else
{
//now recreate persistent store
[self persistentStoreCoordinator];
[[self managedObjectContext] unlock];
resetOk = YES;
}
}
else
{
NSLog(#"\nresetDatastore. Error removing persistent store: %#",
[error localizedDescription]);
resetOk = NO;
}
return resetOk;
}
else
{
NSLog(#"\nresetDatastore. Could not find the persistent store");
return resetOk;
}
}
You can also just tear down the stack (releasing the NSManagedObjectContext, NSPersistentStore and NSManagedObjectModel) and delete the file. Probably would be faster than iterating over your entire database and deleting each object individually.
Also, it is unlikely they will provide this functionality in the future because it is easy to delete the file. However if you feel it is important then file a radar and let Apple know. Otherwise they won't know how many people want this feature.
Just iterate the array and delete them. There isn't a defined method for deleting them all.
When you remove all the cache and documents, you are deleting the database. Not is necesary call to managedObjectContext
NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSMutableArray *paths = [NSMutableArray array];
[paths addObjectsFromArray:documents];
[paths addObjectsFromArray:caches];
for (NSUInteger i = 0; i < [paths count]; i++) {
NSString *folderPath = [paths objectAtIndex:i];
NSLog(#"Attempting to remove contents for: %#", folderPath);
//Remove all cached data in the local app directory
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:&error];
for (NSString *strName in dirContents) {
[[NSFileManager defaultManager] removeItemAtPath:[folderPath stringByAppendingPathComponent:strName] error:&error];
if (error != nil) {
NSLog(#"Error removing item: %# : %#", strName, error.description);
} else {
NSLog(#"Removed item: %#", strName);
}
}
}
I used stifin's code and updated it to use -performBlockAndWait:
- (BOOL)reset
{
__block BOOL result = YES;
[[self mainContext] performBlockAndWait:^{
[[self mainContext] reset];
NSArray* stores = [[self persistentStoreCoordinator] persistentStores];
_mainContext = nil;
_persistedContext = nil;
for(NSPersistentStore* store in stores) {
NSError* error;
if(![[self persistentStoreCoordinator] removePersistentStore:store error:&error]) {
debuglog(#"Error removing persistent store: %#", [error localizedDescription]);
result = NO;
}
else {
if(![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
debuglog(#"Error removing file of persistent store: %#", [error localizedDescription]);
result = NO;
}
}
}
_persistentStoreCoordinator = nil;
}];
return result;
}