I need my application work as follows:
1- User download and startup the application.
2- The application will automatically fill the (based core data) with specific records.
3- The application will works ok.
4- When the user close the application and restart it.
5_ The application will not automatically fill the core data with the specific records because it already there.
6- The user will not be able to add/remove/update the data.
Is there a good technique to do that using only core data and accepted by Apple.
Populate the data in the simulator
Make a copy of the SQLITE database from the simulator folder
Add the database as a resource to your project.
Do something like this before initializing your Core Data stack:
code:
// Put down default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"MyDataFile" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
Not shown, but critical: all the files you prepopulate data for must be marked as "do not backup", or you will be rejected, per the App Store guidelines. See this SO question for some details on how to do this: Why was my application still rejected after excluding files from iCloud backup using this code?
I can tell you one very easy way to do it. In the app delegate, create a variable in NSUserDefaults and change its value when the application is loaded for the first time. Then depending upon the value of NSUserDefaults, fill the data you want in the Core Data store. And this will happen only once because the value of NSUserDefaults variable is not going to change again.
Related
I have an app that downloads a whole bunch of data from over 100+ APIs upon successful login. I successfully download the data, and then use iExplorer to extract the data container folders (Documents, Library and Tmp) from the fully loaded application.
I would like to take a blank version of the original app, in .ipa format, and insert those data container folders into that fully loaded .ipa file. Then I will be able to take this new fully loaded .ipa, and use a deployment software to deploy it to a bunch of local user's devices. So everyone will have this fully loaded app.
Please, has anyone done this? Please provide some feedback, and don't argue with my methodology, because this has be done this way due to requirements. Maybe there is a step I'm missing? I'm not sure.
With the source code in hand, you can run the app in the simulator (no need for iExplorer), wait for it to download all the files and browse to the folder on your computer where the app was installed.
From there you can put aside any files you want along with their respective folders. If you're using Coredata there should be a SQLITE database file there somewhere (typically in your Application Support folder) and this might be all you need but it is hard to tell without looking at your implementation details.
Once you have the files you need set aside, add them to the app bundle via Xcode and create code to check whether files already exist (in which case you don't want to replace them), and if not copy all files needed from the bundle into their respective folders.
Here's some semi pseudo-code for you:
NSDictionary *userPrefs = [[NSUserDefaults standardUserDefaults] objectForKey:self.email];
if (![userPrefs[kInitialSetupCompleted] boolValue])
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationFilePath = ...
NSURL *seedFilePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:kCoreDataStoreName ofType:nil]];
NSError* err = nil;
if (![fileManager copyItemAtURL:seedPath toURL:destinationFilePath error:&err]) {
NSLog(#"Could not copy seed data. error: %#", err);
// Deal with error
} else {
// Set user defaults kInitialSetupCompleted to YES
}
}
I'm storing some files in the Library directory in an iOS app, using the following methods to construct it. In the end, I can call [MyClass dataDirectory] to do my file handling and all is well. I've recently discovered, however, that some files seem to be mysteriously disappearing out of this directory. According to the documentation, this should not be the case. Is this a safe place to store persistent files?
The console output of this directory is: ~/var/mobile/Containers/Data/Application/{id}/Library/Data
+ (NSString*)libraryDirectory
{
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString*)dataDirectory
{
NSString* dir = [[self libraryDirectory] stringByAppendingPathComponent:#"Data"];
BOOL isDir=NO;
NSError * error = nil;
NSFileManager *fileManager = [NSFileManager new];
if (![fileManager fileExistsAtPath:dir isDirectory:&isDir] && isDir)
{
[[NSFileManager defaultManager] createDirectoryAtPath:dir
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:dir isDirectory:YES]];
if (error != nil) {
DDLogError(#"Fatal error creating ~/Library/Data directory: %#", error);
}
return dir;
}
And the skip method:
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[URL path]])
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
DDLogError(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
return YES;
}
In the code you posted, the first problem is here:
if (![fileManager fileExistsAtPath:dir isDirectory:&isDir] && isDir)
At the point where this is evaluated, isDir will default to NO, and will be set to NO if the file does not exist or is not a directory. This will prevent the directory from being created. Remove && isDir or change to || !isDir to get the logic you want.
Now on to your original question:
Is this (a subdirectory of NSLibraryDirectory) a safe place to store persistent files?
Yes. NSLibraryDirectory is backed up by default. To comply with the iOS Data Storage Guidelines an application should not store user-created data in that location, but it is a safe place to store application data. NSApplicationSupportDirectory is a directory that is generally within the NSLibraryDirectory, and is the preferred place to store this kind of data. Data within that location will be backed up, and will be migrated during application and OS updates.
The iOS Data Storage Guidelines, File System Programming Guide, and App Programming Guide for iOS all provide guidance on where to put files, and how they will be backed up from standard file system locations.
Unless those files have had their NSURLIsExcludedFromBackupKey/kCFURLIsExcludedFromBackupKey resource metadata value altered. Then it gets much more complicated.
Files 'Excluded From Backup'
Generally, if a file outside of a Documents directory can be backed up, the system assumes it can also purge it under low space or other conditions. This is why setting NSURLIsExcludedFromBackupKey to YES on a file allows the file to persist even in low storage conditions. If your application sets NSURLIsExcludedFromBackupKey to YES for a file, your application assumes responsibility for the life of that file.
The catch here is that the backup process and the purge process do not follow the same logic. Apple's documentation indicates that for the purposes of controlling the backup behavior, it is possible to set NSURLIsExcludedFromBackupKey on a directory. The children of that directory will effectively inherit that resource value (in practice, this may not be accurate). The purge process, however, does not seem to have the same behavior. It may not check the backup exclusions of the parent directories and apply it to children, and as a result if a file does not have NSURLIsExcludedFromBackupKey explictly set it may be purged.
This gets even more complicated. If you were to read the documentation for the constant NSURLIsExcludedFromBackupKey you would see:
Some operations commonly made to user documents cause this property to be reset to false; consequently, do not use this property on user documents.
This actually applies to much more than user documents. For example, if you were to perform an atomic write on a file such as:
[thing writeToURL:URL atomically:YES encoding:NSUTF8StringEncoding error:&error]
If the file at URL had NSURLIsExcludedFromBackupKey set to YES before the write, it would now appear to be set to NO. An atomic write like this will first create a temporary file, write to that, and replace the original with the new file. In doing so, file and URL resource flags are not preserved. The original file had the NSURLIsExcludedFromBackupKey resource value set, the newly created file at the same location now does not. This is just one example; many Foundation APIs perform atomic writes like this implictly.
There are scenarios where this gets even more complex. When an application is updated it is installed into a new location with a new application container path. Data inside the old application container is migrated. There are few guarantees regarding what may or may not be migrated as part of the update process. It may be everything, it may be only some things. In particular there are is no guidance concerning how files or directories marked with the NSURLIsExcludedFromBackupKey resource attribute will be treated. In practice it seems that these are often the least likely files to be migrated, and when they are migrated the NSURLIsExcludedFromBackupKey attribute is rarely preserved.
OS updates are also an issue. Historically Over-The-Air updates have been problematic and have caused the NSURLIsExcludedFromBackupKey resource attribute to be effectively cleared or ignored. A "major" OS update will clear the device and restore from a backup - which is equivalent to migrating to new hardware. Files marked with the NSURLIsExcludedFromBackupKey resource attribute will not be migrated, and the application will have to re-create them.
Update scenarios are described in TechNote 2285: Testing iOS App Updates
Because of this, when using NSURLIsExcludedFromBackupKey it is generally best to set the value on every access, and as always should be done through the File Coordination APIs (unless you are writing to a shared group container, which is an entirely different set of issues). If the NSURLIsExcludedFromBackupKey resource attribute value is lost files can be purged at any time. Ideally an application should not depend on the NSURLIsExcludedFromBackupKey or how the OS may (or may not!) handle it, but instead be designed such that the data could be recreated on demand. That may not always be possible.
It's clear from your question and the code that you posted that you are somewhat dependant on NSURLIsExcludedFromBackupKey ensuring that your file(s) have an application-controlled lifetime. As you can see from the above, that may not always be the case: there are many, many common scenarios where that resource attribute value can disappear, and with it your files.
It is also worth noting that NSFileProtection attributes work the same way, and can disappear in the same scenarios (and a few more).
TL;DR; What should I do?
Based on your question, code, and the description of the behavior you are seeing:
Setting the NSURLIsExcludedFromBackupKey value on the directory containing the file(s) you are interested in preserving may not be enough to prevent them from being purged. It would be wise to set NSURLIsExcludedFromBackupKey on every access to the actual files, rather than just a parent directory. Also attempt to ensure this resource value is set after any write to the file, especially through a high level API that may be doing atomic writes, etc.
All NSFileManager and file reading/writing operations should use file coordination. Even in an application that is single threaded there will be other processes interacting with "your" files. Processes like the daemons that run backups or purge files during low space conditions. Between your -fileExistsAtPath: and the -setResourceValue:forKey:error: another process could alter, delete, or move your file and its attributes. -setResourceValue:forKey:error: will actually return YES and no error in many cases where it did nothing, like the file not existing.
Files and directories marked with NSURLIsExcludedFromBackupKey are the responsibility of the application to manage. The application should still purge those files or their contents at some appropriate time, or set limits on their growth. If you look at the per-application disk usage information on a device, you can probably guess the names of some applications that do not do this correctly.
Test update scenarios as described in TechNote 2285: Testing iOS App Updates. often. Ideally the iOS Simulator would have a "Simulate Low Disk Space" capability similar to simulating memory warnings, but at this time it does not.
If at all possible, alter application logic to recreate these files if they go missing.
In the documentation you linked it is stated that
Critical data should be stored in the /Documents directory. Critical data is any data that cannot be recreated by your app, such as user documents and other user-generated content.
It is also mentioned that
Cached data should be stored in the /Library/Caches directory. Examples of files you should put in the Caches directory include (but are not limited to) database cache files and downloadable content, such as that used by magazine, newspaper, and map apps. Your app should be able to gracefully handle situations where cached data is deleted by the system to free up disk space.
The directory you are using is not explicitly mentioned for storing user data, it is used by the system and is not save for your data. It's guarantied to be untouched by an update of your app, but that's it
To find the documents folder you could do something like
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolderPath = [paths firstObject];
I have an app (say version 1.0) which is using Core data and now i have updated the app using sqlite and for the new update i am not using core data at all (we will call it version 1.1), however i have kept the name of my database and all the column names of the table similar to the previous core data app.
My query is that what major or minor effects will it have if the user updates to the new version (1.1) which is using sqlite. Will it have some kind of dataloss or any crashes if yes then please guide me out by providing any links or suggestions
To avoid dataloss i am planning to transfer data in chunk from core data to sqlite app as read in this post, so am i doing this right do let me know if i have missed any valuable steps.
Migrating user data stored in sqlite to core data in app upgrade
Thank you
If your actions will correct and grammar, users willn't lose any data.
How I understand, in your next version of app you will managing data by any sqlite framework, then you just need .sqlite data file. To get it .sqlite file path you can:
NSString *dataFilePath = [[[self applicationDocumentsDirectory] path]
stringByAppendingPathComponent: #"YourAppName.sqlite"];
NSURL *dataFileUrl = [NSURL fileURLWithPath:storePath];
When applicationDocumentsDirectory method implement like this:
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
If I understand something incorrect, write it in comments.
I use coreData in my iOS App. It's possible, that the user Add, Delete Data into the Database.
I have to deliver default data ( some different data-sets ).
At the moment, I'm creating the database by first Application launch. I read data from a csv file an create the database with it.
The csv is in the Application sandbox; the coreData (managedDocument) is in ApplicationDocument (creation on runtime...).
It works perfect for me - but I ask me, will Apple allow that, if I push the App to the AppStore?
There is nothing wrong with this approach and it can't be a reason for rejection. There is also another way to do it. You can create the database the way you do it now, copy the .sqlite file and provide it as your default database. Then copy it on app first run. The following code will do it:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent: #"YourDBName.sqlite"];
if (![fileManager fileExistsAtPath:storeURL.path]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storeURL.path error:NULL];
}
}
With this approach you will not need to include your csv file in your bundle.
Yes, apple does allow shipping a database populated by default.
the standard way to do it is to ship a default database in your bundle, then at launch time check if there is a database in your application documents directory, and if it does not exist, then copy the database from your bundle to the documents directory.
I am building a project where I need to prepopulate a coredata database with existing data.
I built a parser to create the sqlite file in the iOS Simulator, everything works fine.
I am using a single entity, and one of the attribute is indexed.
Performance after parsing my data file into core data is great, everything is good.
Now I am using the generated sqlite file (~200Mb) in project with same data model, same index, etc... and on first startup I copy over the db file to prepopulate the data
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myproject" ofType:#"sqlite"];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: #"myproject.sqlite"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath])
{
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
The copying works fine, and the data can be accessed normally.
However the performance is terrible, and the index is clearly not being used.
A look at the size of the sqlite file after the copy operation, it went from 200Mb to 120Mb.
Everything looks alright in the model, what needs to be indexed is checked as indexed.
1) Is there a way for the index data not to be removed when copying the sqlite over?
2) Is it possible to programmatically rebuild the index?
3) Any other thoughts?
Check at the Apple documentation about this issue:
Although Core Data supports SQLite as one of its persistent store types, the database format is private. You cannot create a SQLite database using native SQLite API and use it directly with Core Data (nor should you manipulate an existing Core Data SQLite store using native SQLite API). If you have an existing SQLite database, you need to import it into a Core Data store (see “Efficiently Importing Data”).
To sum up, don't do it. The database schema is private and may change.
I use CSV files to pre-load all my initial data to CoreData on background when the application starts for the first time. Beware of multithreading CoreData access, by the way.
Hope it helps.
Looks like the problem was that the project didn't get cleaned well in between tests, it could have been a bug in Xcode 4.3 I was using at the time.
The same methodology is working fine now.