Reuse core data created .db file in FMDB - ios

I'm using Core Data to manage local DB in my iOS app. But then I have a requirement to insert bulk of records to local DB. So I choose FMDB to perform bulk insertion. But when I tried to insert the record I'm getting this DB Error: 1 "no such table: Table Name.
But when I cross checked, the local DB exist there with all the tables that I created in xcdatamodel.
- (FMDatabase *)dbInMainThread{
if (_dbInMainThread) {
return _dbInMainThread;
}
NSURL *storeURL = [[[CoreDataManager manager] applicationLibraryDirectory] URLByAppendingPathComponent:[DATABASE_NAME stringByAppendingString:#".sqlite"]];
[self checkDB:[storeURL relativePath]];
_dbInMainThread = [FMDatabase databaseWithPath:[storeURL relativePath]];
return _dbInMainThread;
}
-(void) checkDB:(NSString *)dbpath{
BOOL success;
NSFileManager * fm = [NSFileManager defaultManager];
success = [fm fileExistsAtPath:dbpath];
if (success) return;
}
Success bool in the above code snippet is always returning true.
I followed the steps in this SO answer.
Since I am using Core Data, I don't need to add the DB file to the project. I uninstalled the app, resets the simulator and verified the DB file was in the correct folder before starting insertion. But nothing helps.
What am I missing here
Thanks

Related

How to view the sqlite db in the application installed in iOS for testing

I have installed my application in the simulator and need to view the DB. Please tell me the application to view this or can I view it from Xcode itself.
DATABASE
//database connection
con = [[DataBase alloc]init];
NSError *error = [[NSError alloc]init];
NSFileManager *filemanager = [NSFileManager defaultManager];
NSArray *arryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *DocumentPath = [arryPath objectAtIndex:0];
NSString *strDocumentPath = [DocumentPath stringByAppendingPathComponent:#"School.sqlite"];
// check file is exist or not
int success = [filemanager fileExistsAtPath:strDocumentPath];
//if file not exist at path
if (!success) {
NSLog(#"SchoolName is: %#",#"No Database exist");
NSString *strDefaultPath = [[[NSBundle mainBundle]resourcePath]stringByAppendingPathComponent:#"School.sqlite"];
success = [filemanager copyItemAtPath:strDefaultPath toPath:strDocumentPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
//file exist at path
if (success) {
NSLog(#"SchoolName is: %#",#" Database exist");
if (sqlite3_open([strDocumentPath UTF8String],&database) == SQLITE_OK) {
} else {
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}
Install DB Browser for Sqlite in Mac.
Provide path of .sqlite in documents directory of app to DB browser
It'll open live db from simulator
All entities of Core Data will have ZTablename naming convention
Keep refreshing DB browser for updated data during testing
Try to use the client e.g. from here http://sqlitebrowser.org/ and open sqlite file.
If you installed firefox on your PC, use "SQLite Manager" add-on is quite good, it's easy, lightly, and free. I've used it before to manage my sqlite db with ~40k records with no problem.
https://addons.mozilla.org/en-US/firefox/addon/sqlite-manager/
For live database Changes please try this:
1.Install DBBrowser for SQLite.
2.Log your strDocumentPath in console using NSLog And copy the path.
3.Open DBBrowser and open database and in finder press CMD+SHIFT+G and paste your link there and press ENTER. You will be redirected to that location.
4.Select your SQLite file and it will be opened in DBBrowser. And keep it there until you are done with your DB works.
Now whenever you refresh the database from DBBrowser you will get the recent changes in DB also any manual changes in DB will be effected in your Simulator.
Here is a way I do that
init() {
do {
// Get database path
if let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
// Connect to database
db = try Connection("\(path)/db.sqlite3")
// Initialize tables
try db?.run(rssNewspaper.create(ifNotExists: true) { table in
table.column(rssNewspaperId, primaryKey: .autoincrement)
table.column(rssNewspaperName)
table.column(rssIsActive)
})
}
} catch {
print(error.localizedDescription)
}
}
point debuger point in path and when deice is locked there print to path then open terminal and use this command "open (path/of/printed/path)" in between( ) past the printed value it will be open

Core data crashes the app [duplicate]

I created a Core Data model in xcode 3.2 and after upgrading in Xcode 4.2, I then added a new entity of the NSManagedObject subclass (refer to the new entity).
First thing, it looks weird because it's not in the same group as the old one. Here is the picture on my xcode 4.2 (AlkitabDB is the one i created in xcode 3.2, EndeDB is the new one from current xcode version(4.2):
Second thing, I let it as it is, then I accessed the second entity (the new one) the same way as the first entity (the old one), and the error as titled appears.
Here is the error:
2012-01-16 21:13:38.496 iHuria[55953:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x8829cd0 {metadata=<CFBasicHash 0x882a370 [0x1839b38]>{type = immutable dict, count = 7,
entries =>
2 : <CFString 0x8829b90 [0x1839b38]>{contents = "NSStoreModelVersionIdentifiers"} = <CFArray 0x8829ff0 [0x1839b38]>{type = immutable, count = 0, values = ()}
4 : <CFString 0x8829bc0 [0x1839b38]>{contents = "NSPersistenceFrameworkVersion"} = <CFNumber 0x8829770 [0x1839b38]>{value = +320, type = kCFNumberSInt64Type}
6 : <CFString 0x8829bf0 [0x1839b38]>{contents = "NSStoreModelVersionHashes"} = <CFBasicHash 0x882a080 [0x1839b38]>{type = immutable dict, count = 1,
entries =>
0 : <CFString 0x882a010 [0x1839b38]>{contents = "AlkitabDB"} = <CFData 0x882a030 [0x1839b38]>{length = 32, capacity = 32, bytes = 0xd02ac5f8be6ab0b39add450aca202ac0 ... 3d45d462998d2ccd}
}
7 : <CFString 0x10e3aa8 [0x1839b38]>{contents = "NSStoreUUID"} = <CFString 0x8829e60 [0x1839b38]>{contents = "4F2EE7FF-463B-4055-BBED-8E603CDBDF59"}
8 : <CFString 0x10e3948 [0x1839b38]>{contents = "NSStoreType"} = <CFString 0x10e3958 [0x1839b38]>{contents = "SQLite"}
9 : <CFString 0x8829c40 [0x1839b38]>{contents = "NSStoreModelVersionHashesVersion"} = <CFNumber 0x6b1c7c0 [0x1839b38]>{value = +3, type = kCFNumberSInt32Type}
10 : <CFString 0x8829c70 [0x1839b38]>{contents = "_NSAutoVacuumLevel"} = <CFString 0x882a0c0 [0x1839b38]>{contents = "2"}
}
, reason=The model used to open the store is incompatible with the one used to create the store}, {
metadata = {
NSPersistenceFrameworkVersion = 320;
NSStoreModelVersionHashes = {
AlkitabDB = <d02ac5f8 be6ab0b3 9add450a ca202ac0 ebd1e860 cbb578c2 3d45d462 998d2ccd>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "4F2EE7FF-463B-4055-BBED-8E603CDBDF59";
"_NSAutoVacuumLevel" = 2;
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
I looked for the solution before and discovered that I should remove the appliation from simulator and rerun the app, and it didn't work.
Does anyone know a solution for this issue?
Please help.
Deleting the app is sometimes not the case! Suggest, your app has already been published! You can't just add new entity to the data base and go ahead - you need to perform migration!
For those who doesn't want to dig into documentation and is searching for a quick fix:
Open your .xcdatamodeld file
click on Editor
select Add model version...
Add a new version of your model (the new group of datamodels added)
select the main file, open file inspector (right-hand panel)
and under Versioned core data model select your new version of data model for current data model
THAT'S NOT ALL ) You should perform so called "light migration".
Go to your AppDelegate and find where the persistentStoreCoordinator is being created
Find this line if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
Replace nil options with #{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} (actually provided in the commented code in that method)
Here you go, have fun!
P.S. This only applies for lightweight migration. For your migration to qualify as a lightweight migration, your changes must be confined
to this narrow band:
Add or remove a property (attribute or relationship).
Make a nonoptional property optional.
Make an optional attribute nonoptional, as long as you provide a default value.
Add or remove an entity.
Rename a property.
Rename an entity.
For Swift 4
coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true])
Remove the app from the simulator and perform a clean on your project. That should clear those issues up. Make sure that you are not running in the debugger when you delete the app or else it won't actually delete it properly.
If you want to be sure its gone, check this directory Users/INSERT_YOUR_USER_HERE/Library/Application Support/iPhone Simulator/ for your app's folder, under the version you're running.
Note: This is for development only. For production, you need to implement some sort of migration. Google "Core Data Migration", with lightweight migration being the simplest.
Just add Options attribute while creating persistentStoreCoordinator in AppDelegate.m file for the core data method as below
OBJECTIVE-C
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil)
{
return _persistentStoreCoordinator;
}
NSLog(#"persistentStoreCoordinator___");
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyApp.sqlite"];
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"persistentStoreCoordinator___2");
return _persistentStoreCoordinator;
}
SWIFT
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
// MAIN LINE OF CODE TO ADD
let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: mOptions)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this 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 \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}
It had solved my problem..
Answer : Remove the app from the Simulator , Perform a Clean and Re-Build your Project.
Note : Whenever you perform changes to the Core Data definition, Delete the app installed on the Physical Device or Simulator, Clean the Project and Re-Build again.
Yes. Once you delete app on physical device and rebuild it works.
For swift, in AppDelegate.swift find the line
try coordinator!.addPersistentStoreWithType(NSXMLStoreType, configuration: nil, URL: url, options: nil )
and replace it with
try coordinator!.addPersistentStoreWithType(NSXMLStoreType, configuration: nil, URL: url, options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true])
I just spent several days fighting this error, as well as mergedModelFromBundles crashes, and getting the "Can't merge models with two different entities named *" error.
It turns out the root problem was that Xcode doesn't remove old resources from devices and I had old versions of my data model (.mom files) that were causing conflicts. This is why deleting the app fixed the problem on one of my devices.
After finding this blog post via another SO answer I made my app more tolerant of old models by changing this line which looks for ALL .mom files:
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
to this, which only looks in the Filters directory:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Filters" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
I used recursivePathsForResourcesOfType from this so question: to help figure this out by logging all of the .mom files in the app:
NSArray *momPaths = [self recursivePathsForResourcesOfType:#"mom" inDirectory:[[NSBundle mainBundle] resourcePath]];
NSLog(#"All .mom files:%#",momPaths);
I also used iExplorer to look at the extraneous .mom files (I didn't try deleting them yet).
The method below was also helpful. It showed that an entity was in the merged model returned by [psc managedObjectModel] that didn't exist any more in any of my models or in the store itself. This was what let me to believe an old model was being cached on the device itself that clean building didn't remove. The method logs each entity that is the same, been changed, or added to, or removed from the model. (written with this SO answer as a starting point):
- (BOOL)comparePersistentStore:(NSPersistentStoreCoordinator *)psc withStoreURL: (NSURL *)storeURL {
NSError *error = nil;
// Get the entities & keys from the persistent store coordinator
NSManagedObjectModel *pscModel = [psc managedObjectModel];
NSDictionary *pscEntities = [pscModel entitiesByName];
NSSet *pscKeys = [NSSet setWithArray:[pscEntities allKeys]];
//NSLog(#"psc model:%#", pscModel);
//NSLog(#"psc keys:%#", pscKeys);
NSLog(#"psc contains %d entities", [pscModel.entities count]);
// Get the entity hashes from the storeURL
NSDictionary *storeMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeURL
error:&error];
NSDictionary *storeHashes = [storeMetadata objectForKey:#"NSStoreModelVersionHashes"];
//NSLog(#"store metadata:%#", sourceMetadata);
NSLog(#"store URL:%#", storeURL);
NSLog(#"store NSStoreUUID:%#", [storeMetadata objectForKey:#"NSStoreUUID"]);
NSLog(#"store NSStoreType:%#", [storeMetadata objectForKey:#"NSStoreType"]);
NSSet *storeKeys = [NSSet setWithArray:[storeHashes allKeys]];
// Determine store entities that were added, removed, and in common (to/with psc)
NSMutableSet *addedEntities = [NSMutableSet setWithSet:pscKeys];
NSMutableSet *removedEntities = [NSMutableSet setWithSet:storeKeys];
NSMutableSet *commonEntities = [NSMutableSet setWithSet:pscKeys];
NSMutableSet *changedEntities = [NSMutableSet new];
[addedEntities minusSet:storeKeys];
[removedEntities minusSet:pscKeys];
[commonEntities minusSet:removedEntities];
[commonEntities minusSet:addedEntities];
// Determine entities that have changed (with different hashes)
[commonEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
if ( ! [pscDescrip.versionHash isEqualToData:storeHash]) {
if (storeHash != nil && pscDescrip.versionHash != nil) {
[changedEntities addObject:key];
}
}
}];
// Remove changed entities from common list
[commonEntities minusSet:changedEntities];
if ([commonEntities count] > 0) {
NSLog(#"Common entities:");
[commonEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\t%#:\t%#", key, pscDescrip.versionHash);
}];
}
if ([changedEntities count] > 0) {
NSLog(#"Changed entities:");
[changedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\tpsc %#:\t%#", key, pscDescrip.versionHash);
NSLog(#"\tstore %#:\t%#", key, storeHash);
}];
}
if ([addedEntities count] > 0) {
NSLog(#"Added entities to psc model (not in store):");
[addedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSEntityDescription *pscDescrip = [pscEntities objectForKey:key];
NSLog(#"\t%#:\t%#", key, pscDescrip.versionHash);
}];
}
if ([removedEntities count] > 0) {
NSLog(#"Removed entities from psc model (exist in store):");
[removedEntities enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
NSData *storeHash = [storeHashes objectForKey:key];
NSLog(#"\t%#:\t%#", key, storeHash);
}];
}
BOOL pscCompatibile = [pscModel isConfiguration:nil compatibleWithStoreMetadata:storeMetadata];
NSLog(#"Migration needed? %#", pscCompatibile?#"no":#"yes");
return pscCompatibile;
}
usage: called before adding each store to NSPersistentStoreCoordinator :
[self comparePersistentStore:self.psc withStoreURL:self.iCloudStoreURL];
_iCloudStore = [self.psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.iCloudStoreURL
options:options
error:&localError];
Every time you making change to the Core Date definition, you should delete the apps installed on the physical device or simulator.
Stop app from running.
Delete app on simulator.
Product - > Clean
Build, run.
The simplest solution that worked for me in Swift 2.1, Xcode 7 is :
Delete the app from the Simulator ( Cmd + Shift + H to go to the Home Screen. Long Press the app, Click cross, just the usual way you delete an app from your phone)
Cmd + Shift + H again to stop the dancing of apps
Go back to your project and rerun
I had this issue while writing/reading from Core Data with 2 entities set up. Deleting the app and rerunning the program fixed the issue
I just deleted [Simulator App Folder]/Document/*.sqlite file after making changes in entities and it worked.
And of course, .sqlite file contains all stored data and structures which will be lost.
Please Delete a application from simulator and clean a code and run .its work fine .do it may be its help YOU.
If you are using Swift.
Follow the answer by #Stas and insert options, in place of nil, in your App Delegate:
let myOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: myOptions, error: &error) == nil {
Try "Reset Content & Settings" in the simulator. Worked for me after deleting app and Clean build
I experienced the same issue with my app (not yet released in App Store).
Here's how I fixed it:
Run Clean (Cmd+Shift+K)
Restart iOS Simulator
iOS Simulator -> Reset Content and Settings (from navbar)
(3) was the step that finally got it to run properly. Hope this helps!
In my case, I had two persistent stores, one local store for user specific data, and one CoreData+CloudKit store for common data that syncs automatically with iCloud. Thus the data model has two configurations, and the entities are assigned to both configurations as required.
Due to a bug during development, I tried to store an entity that was no longer assigned to any configuration. So when the context was saved, CoreData realized the incompatibility, and crashed with this error.
Of course, deleting the app does not help in such a case. One has to ensure that only assigned entities are stored in a persistent store.
Although sometimes you can just remove the app from the device when changing schema in managed object model, in some scenarios this is not possible e.g. because you already published your app with an old schema.
If this is the case, you have to take care of migrating old data to the new schema:
Core Data Model Versioning and Data Migration
You'll need to migrate the Core Data model using migration. Any time you change the model, you make it incompatible without versioning. Strap yourself in, it's a bit of a hairy topic.
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html
If you make changes to your Core Data model, you have to provide a migration policy that tells Core Data how to adopt existing persisted objects (that your users created with the currently released version) to the new model.
For some scenarios, Core Data is able to automatically infer the mapping from the old model to the new one. For more complex changes, you might have to implement some logic that performs the migration.
Details can be found in the Core Data Model Versioning and Data Migration Programming Guide.
Update
This answer here on Stack Overflow covers the basics of Core Data's lightweight migration and also has some code to get you started.
First, the only things that should be in the xcdatamodeld bundle are xcdatamodel files. Your subclasses should NOT be in the xcdatamodeld. Move those out of there. There is a fair chance they are confusing the compiler.
Second, the error indicates that Core Data cannot find your model. Have you created data and then touched the model? If so you are in an inconsistent state and need to fix that either by deleting the data (which Philippe suggested) or by rolling your changes of the model BACK.
This issue generally occurs due to incompatibility between the version on which DB has been created. General approach to this problem is to delete the app and reinstall it. But in your mentioned case the version of DB are completely different on Xcode 3.2 and 4.2. So better use same version of Xcode for DB.
I was getting the error but the reason I was getting the error was because of the following.
I originally had one Entity named "Entry" and had one row saved for that entity in the database. I then added another Entity named "Person" and after adding that went to build and got the error. So I solved the issue by deleting "Person" Entity and then building the app, deleted the row that was in "Entry" and then closed the application. I then deleted the app entirely off my phone and then did a rebuild and it worked fine. Not sure which step corrected the problem (the deletion of the row or the app), but hopefully if you're looking for a solution this will help. :)
Edit: Oh and if you worried about deleting your new Entity (in my case "Person") to build the app again remember you can get it back afterwards by using CMD+Z!
I had this problem - I first reset my simulator and then clean the project and rebuild. And then it works.
When you change core data, ( adding a field to table , removing field etc ), the sqlite file in applications document folder needs to be in sync with your schema.
This file is not overwritten by default, this file needs to be regenerated.
Follow these steps:
Go to the folder pointed by NSURL. (This path can be found in exception message generated by application before crashing.)
example : /Users//Library/Application Support/iPhone Simulator//Applications//Documents
remove or rename the sqlite file
Clean and Rerun the application
Rerunning application would generate a new sqlite file.
This will make sure that the schema and Xcode are in sync.
This may help some people but may not answer the question. In my case, the problem was solved because I forgot to add the model to the correct configuration. See the screenshot attached. All the models are added to the default configuration, but my application uses the private configuration. Drag and drop your model from the default configuration to the correct configuration.
iOS Simulator -> Reset Contents and Settings...
Worked for me
iOS Simulator -> Reset Contents and Settings... -> Reset
Works on iOS9 (xcode 7.1) as well

MagicalRecord: multiple databases

I have an app that uses MagicalRecord, and I'm pre-populating the database with a large amount of data that is used for reference. Within that same data model, I have user-definable information pertaining to things the user may do in the app.
The app was rejected because the pre-populated data is supposed to be marked as "do not backup". So, I'd like to have that data in a separate datastore so that I can keep the user data as backupable.
Is there a way to have two separate datastores using MagicalRecord?
I think it's possible, but not too easy though.
As you know, to work with more than one database, you should bring some changes to your PersistentStoreCoordinator, so it will have two PersistentStores. After this, you Core Data stack would look like this:
The other way is two make two separate PersistentStoreCoordinators, each carrying one store.
In Magical Record, there are several class methods for adding stores in
NSPersistentStoreCoordinator+MagicalRecord.h.
(NSPersistentStore *) MR_addInMemoryStore;
(NSPersistentStore *) MR_addAutoMigratingSqliteStoreNamed:(NSString *) storeFileName;
(NSPersistentStore *) MR_addSqliteStoreNamed:(id)storeFileName withOptions:(__autoreleasing NSDictionary *)options;
I think, that this is the place where you could do the thing you want.
Also i should mention, that the whole proccess of setting up the stack goes in MagicalRecord+Setup.h
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName
So you can add your Stores and Coordinators there.
I've never managed it by myself, that was just a brief investigation of a possible solution.
I was able to solve this issue using configurations. Since Magical Record always sends null for the configuration parameter, I broke apart setupCoreDataStackWithAutoMigratingSqliteStoreNamed and replaced it with a method that supports multiple configurations.
Because Magical Record does a good job of handling auto migrations, I first call setupCoreDataStackWithAutoMigratingSqliteStoreNamed, followed by cleanup, and then I supply my replacement code.
I have one object model with my seed data objects assigned the the "Seed" configuration and user objects assigned to the "User" configuration. Magical Record has already been initialized so it could auto migrate if necessary.
+(void) RB_setupMultipleStores:(NSString *) seedStoreName userStore:(NSString *) userStoreName
/* change persistent store to one with multiple configurations. Assumes Magical Record is initialized. */
{
NSError * error= nil;
[MagicalRecord cleanUp];
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 = [NSPersistentStore MR_urlForStoreName:[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];
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}
Also, MR 3.0 has concurrent stacks which may solve the problem once it is done.
Keeping data for different Core Data entities in different store files is well supported and fairly straightforward. However, MagicalRecrd doesn't provide any convenience methods for setting up your Core Data stack in this way. You simply have to allocate your stack manually, and tell MagicalRecord to use the NSPersistentStoreCoordinator you create. Here's how I did it in swift:
import Foundation
import CoreData
import MagicalRecord
class CoreDataSetup {
static func setupAutoMigratingStack(withContentConfigurationName contentConfigurationName: String, userConfirgurationNameName: String) {
MagicalRecord.cleanUp()
let managedObjectModel = NSManagedObjectModel.MR_defaultManagedObjectModel()
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!)
let contentURL = NSPersistentStore.MR_urlForStoreName(contentConfigurationName + ".sqlite")
let userURL = NSPersistentStore.MR_urlForStoreName(userConfirgurationNameName + ".sqlite")
let options = [
NSMigratePersistentStoresAutomaticallyOption : true,
NSInferMappingModelAutomaticallyOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"]
]
do {
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: contentConfigurationName, URL: contentURL, options: options)
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: userConfirgurationNameName, URL: userURL, options: options)
NSPersistentStoreCoordinator.MR_setDefaultStoreCoordinator(persistentStoreCoordinator)
NSManagedObjectContext.MR_initializeDefaultContextWithCoordinator(persistentStoreCoordinator)
} catch {
print("Error adding persistent store to coordinator: \(error) ")
}
}
}
Note that in my code I'm referring to your concept of the "seed" store as "content" and the user-definable store as "user".
To accomplish the second aspect of your question, configuring the content store to not be backed up, you simply have to play around with the URLs where you store each store, placing the content store in a non-backed up temporary directory, and copying it to that location up launch from your app bundle if it doesn't exist.

iOS Data Storage Guidelines Rejection

My app was rejected cause it must follow the iOS Data Storage Guidelines. I have already read some answer here on stackoverflow, and i have already read some blogs... I know my problem, at first application launch i copy 1 sqlite db and unzip some images in documents folder. The problem it's icloud that automaticcaly backup any files in documents directory. I don't need to use icloud in my app but my files must remain in document folder because they are the base data of my application and must be persit (cache folder or temp folder aren't correct solutions). So i've read about a flag that i can set file by file to forbid the backup :
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
I have also read that this method works only in ios higher than 5.0.1, in the other it will be simply ignored (i have setted my ios deployment target to 4.3)... If i use this method my app will be rejected again because the older ios devices backup aren't managed? If yes there is a cross-iosversion method to set the NSURLIsExcludedFromBackupKey?
EDIT
I'm sorry icloud isn't present in ios earlier 5.0 so i think that the problem regards only differences beetween version 5.0 and 5.0.1, I'm wrong?
I have passed all files that will stored in documents folder through this method.
Try this
-(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
if (&NSURLIsExcludedFromBackupKey == nil) {
// iOS 5.0.1 and lower
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
else {
// First try and remove the extended attribute if it is present
int result = getxattr(filePath, attrName, NULL, sizeof(u_int8_t), 0, 0);
if (result != -1) {
// The attribute exists, we need to remove it
int removeResult = removexattr(filePath, attrName, 0);
if (removeResult == 0) {
NSLog(#"Removed extended attribute on file %#", URL);
}
}
// Set the new key
NSError *error = nil;
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
return error == nil;
}
}
I've found this method here in Stack Overflow:
Use NSURLIsExcludedFromBackupKey without crashing on iOS 5.0
NOTE:
do not use
NSString *mediaDir = [NSString stringWithFormat:#"%#/Library/Caches/%#", NSHomeDirectory(), MEDIA_DIRECTORY];
since you are making assumptions about the name.
rather, use:
NSArray* lCachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString* lCacheDirectory = [lCachePaths objectAtIndex:0];
In my app I solved it by adding the files (images in my case) to the bundle,
and check in the method where I load the images if they exist in the document folder where the user generated data / images will be placed.
If it isn't there, I know it's one of my prepopulated images and I load it from the bundle.
For the sqlite file:
Not a really satisfying solution, but I create the data in code now, instead of a prepopulated sqlite file.
For media files, we store them in the Caches/ directory. During application startup, we check if they are there. Otherwise the images are either loaded from the bundle (in your case unzipped) or in our case re-downloaded from the Server.
The exact location is
NSString *mediaDir = [NSString stringWithFormat:#"%#/Library/Caches/%#", NSHomeDirectory(), MEDIA_DIRECTORY];
which is the place where you SHOULD store your temporary files. The files in the cache are generally persistent, much more so than in Temp, but can be deleted at some point.

SQLite + SQLCipher + FMDatabase on iOS

I'm currently using FMDatabase in my iOS app and I'm very happy with it.
I plan to encrypt the sqlite database with SQLCipher.
Here are my questions:
1) Are FMDatabase and SQLCipher compatibles ? I think I just need to add a new method in FMDatabase called openEncrypted...and do the job for SQLCipher. I hope all FMDatabase methods will work.
2) Actually, I have 2 databases in my app. Then I do an ATTACH DATABASE in my app to join them. I would like to encrypt just one of the two. Will it work or I need to encrypt the 2 databases ? (One is critical, the other is not)
3) I don't really understand what I will have to provide to Apple (documents) if I encrypt these files.
Thanks you !
For those looking for a simple tutorial on how to accomplish this, I was able to create one: http://www.guilmo.com/fmdb-with-sqlcipher-tutorial/
But the most important parts are, Opening your existing DB and attaching a new encrypted one. Then setting the key in your FMDB connections.
SQLCipher - Encrypting the database
// Import sqlite3.h in your AppDelegate
#import <sqlite3.h>
// Set the new encrypted database path to be in the Documents Folder
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
NSString *ecDB = [documentDir stringByAppendingPathComponent:#"encrypted.sqlite"];
// SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME
const char* sqlQ = [[NSString stringWithFormat:#"ATTACH DATABASE '%#' AS encrypted KEY 'secretKey';",ecDB] UTF8String];
sqlite3 *unencrypted_DB;
if (sqlite3_open([self.databasePath UTF8String], &unencrypted_DB) == SQLITE_OK) {
// Attach empty encrypted database to unencrypted database
sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);
// export database
sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, NULL);
// Detach encrypted database
sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, NULL);
sqlite3_close(unencrypted_DB);
}
else {
sqlite3_close(unencrypted_DB);
NSAssert1(NO, #"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}
self.databasePath = [documentDir stringByAppendingPathComponent:#"encrypted.sqlite"];
Note that we set 2 parameters in SQL Query, the DATABASE and the KEY. The DATABASE should be the full path to the encrypted database you want to create, in this case, string ecDB, and the KEY parameter is the key that’s going to be use to ENCRYPT your database, so choose a strong one
Now on your FMDB functions, call [db setKey:#"strongKey"] after every time you open the db.
// FMDatabase Example
FMDatabase *db = [FMDatabase databaseWithPath:[self getDatabasePath]];
[db open];
[db setKey:#"secretKey"];
// FMDatabaseQueue Exmple
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[self getDatabasePath]];
[queue inDatabase:^(FMDatabase *db) {
[db setKey:#"secretKey"];
...
}];
Let me know if you have any questions!
Yes, You still need to compile in SQLCipher, but you can use FMDB. FMDB also provides a function to set the encryption key for the database
You can attach a non-encrypted database to an encrypted database (see http://sqlcipher.net/sqlcipher-api/#attach)
You will generally need to do an encryption registration with the DOC and then self-classify as mass market http://www.bis.doc.gov/encryption/question4.htm
Hi I am using swift and following is the code I follow. But I have one problem the encrypted.sqlite file I can open with SQLiteBrowser what I was wrong here.
var db: COpaquePointer = nil;
let databasePath = FileUtils.getPath("data.db")
var ecDB = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent("encrypted.sqlite")
let result = String.fromCString("ATTACH DATABASE \(ecDB) AS encrypted KEY TaP")
if (sqlite3_open(databasePath, &db) == SQLITE_OK) {
sqlite3_exec(db, result!, nil, nil, nil);
sqlite3_exec(db, "SELECT sqlcipher_export('encrypted');", nil, nil, nil);
sqlite3_exec(db, "DETACH DATABASE encrypted;", nil, nil, nil);
sqlite3_close(db);
}
else {
sqlite3_close(db);
sqlite3_errmsg(db);
}

Resources