I cache the images to the document directory of my app using the following code.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *saveDirectory = [paths objectAtIndex:0];
But even after I close the application, its still there. How can I clear it when I lose my app. I am doing all this in my simulator.
I would implement applicationWillTerminate: in my application delegate and remove the cache files there. Or better yet, as suggested by Vladimir, save them in a temporary directory and let the OS clean them up when needed.
- (void)applicationWillTerminate:(UIApplication *)app
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:saveDirectory error:error];
for (NSString *file in cacheFiles) {
error = nil;
[fileManager removeItemAtPath:[saveDirectory stringByAppendingPathComponent:file] error:error];
/* handle error */
}
}
If you do not want your cached images to be preserved after application is closed better save them to temporary directory - they will be removed automatically.
If you want to manually remove the files you must store the paths for them and use the following NSFileManager function:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
Edit: sorry, I appeared to be wrong here about automatic deleting. Here's a quote from Developing Guide:
Use this directory to write temporary files that you do not need to persist between launches of your application. Your application should remove files from this directory when it determines they are no longer needed. (The system may also purge lingering files from this directory when your application is not running.)
NSString *file;
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:saveDirectory];
NSError* err;
while (file = [dirEnum nextObject]) {
err = nil;
[[NSFileManager defaultManager] removeItemAtPath:[saveDirectory stringByAppendingPathComponent:file] error:&err]];
if(err){
//print some errror message
}
}
Use the temporary directory path as specified in this question:
How can I get a writable path on the iPhone?
Related
I have file - of arbitrary type including Core Data repositories - that I need to keep around and not have iOS delete them. Usually, I do not want iCloud to back these files up. Where should I save them?
Saving files locally changed from iOS5.0 and earlier, 5.0.1, and 5.1 and newer primarily to the address iCloud backup issues. There are two Apple source documents (the File System Programming Guide, and QA1719) that together provide the information supporting the following:
iOS 5.0
Files should be saved in the "Caches" directory, as there is no way to prevent backups if they are stored in the Documents folder. Note that the system may remove these files (see QA1719), so you would need the ability to recreate each as needed. To find the caches directory, use this:
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]
iOS 5.0.1
Files should be saved in '/Library/Application Support' (FSP, page 15), which can be better accessed via:
[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject]
My experience is that this directory doesn't always exist, and thus you may need to create it:
- (NSString *)applicationAppSupportDirectory
{
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
}
NSFileManager *manager = [NSFileManager defaultManager];
NSString *appSupportDir = [self applicationAppSupportDirectory];
if(![manager fileExistsAtPath:appSupportDir]) {
__autoreleasing NSError *error;
BOOL ret = [manager createDirectoryAtPath:appSupportDir withIntermediateDirectories:NO attributes:nil error:&error];
if(!ret) {
NSLog(#"ERROR app support: %#", error);
exit(0);
}
}
Files saved to this directory (or subdirectories) need an extended attribute to tell iCloud not to back them up (see QA1719).
PS: I have not found a way to set the Deployment target to this release, if there is a way please leave a comment.
iOS 5.1
Files (or folders of files) should be located in the 'Application Support' folder as described above. To prevent iCloud from backing up use:
[URL setResourceValue: [NSNumber numberWithBool: YES] forKey:NSURLIsExcludedFromBackupKey error:&error]
as described in QA1719. Note that you can apply this key to a directory to prevent its contents from being backed up. The complete method from QA1719:
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
As part of my app start-up i copy bundle files to my documents directory.
This works fine for three out of four of my files but the fourth one create a Zero KB file.
running on iOS 5.0 sim. I have cleaned the build several times and checked the file name capitalization vis correct.
the file appears in the directory but is zero kb and should be 24K
any help appreciated.
-(BOOL) CheckDBs: (NSString *)dbname
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:dbname];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath: dbPath];
NSLog(#"AppDelegate CheckDatabase: %# = %i", dbPath, success);
if (success) {
//NSLog(#"return YES");
return YES;
}
else {
return NO;
}
} // Complete - checks if files exist in the User Documents directory
-(void) copyDBs: (NSString *) dbname
{
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:dbname];
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbname];
BOOL success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (success) {
// Version 4.0 code
//NSDictionary *attribs = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
//success = [fileManager setAttributes:attribs ofItemAtPath:dbPath error:&error];
NSLog(#"AppDelegate copyDatase: %# = %d", dbPath, success);
}
//NSLog(#"AppDelegate copyDatase: %# = %d", dbPath, success);
if (!success) {
NSLog(#"Failed to copy database: '%#'", [error localizedDescription]);
// NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
Have you also checked the original file size?
Try resetting your simulator. From the NSFileManager documentation:
If a file with the same name already exists at dstPath, this method
aborts the copy attempt and returns an appropriate error.
Make sure the destination is empty and try again. Also, check the error object.
If all that checks out there has got to be an error in spelling the file name. Check if the exact file exists in bundle, NSLog wherever you use a file name or path, etc. You should find the error. Also check the appropriate folder in the Finder.
Instead of using
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbname]
try
[[NSBundle mainBundle] pathForResource:shortName ofType:#"db"]
Ok I figured out what is causing the problem.
as i run the app the appdidfinishlaunching method is not complete before one of the view controllers is loading. That view controller attempts to access one of the files being copied over from the bundle.
I'm guessing that sqlite creates the file when you attempt to access the database, it creates it with with a zero bytes length.
So when my appdidfinish launching method checks for the existance of the file it exists due to the sql call.
This is usually only going to be a problem prior to the first run of the app as after that the database will exist.
problem now is how do i get the appdidfinish launching to complete prior to the rest being allow to start as the view controller in question is part of the mainwindow.xib
I have an app leveraging Core Data SQLITE3 that works perfectly in the simulator. However i do not understand how to update the DB on the device, which i guess is the same as in app-store.
I update the DB from .txt files in the app and create the DB, this function is there only for creating the DB and will be removed in the final version. My idea is to create the DB in the simulator, lock the update part of the code and then distribute the package with an updated database.
However, when i rebuild my app on the device it still have the old data in the DB.
I have been looking around but i am afraid i do not fully understand how to solve this. I did find this thread: Can't refresh iphone sqlite3 database
I would very much appreciate if some nice person could share some light on this and help me to solve this.
Cheers
Have you copied the db file from the bundle directory (which is read only) to a writable one? (like the documents directory of each application?).
When trying to save in the device did you get a sqlite error like this?
SQLITE_READONLY 8 /* Attempt to write a readonly database */
EDIT:
All the files in the main bundle are read only, so if you need to modify one/some of them, you need to copy the files in a location that is writable. Assuming you have called the db mydb.sqlite here is some code that copies the db (only if it does not exists) to the documents directory.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSFileManager *fm = [NSFileManager defaultManager];
NSString *docPath = [docDirectory stringByAppendingPathComponent:#"mydb.sqlite"];
if (![fm fileExistsAtPath:docPath]) { // file does not exists, copy it
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"mydb" ofType:#"sqlite"];
NSError *error = nil;
BOOL res = [fm copyItemAtPath:bundlePath toPath:docPath error:&error];
if (!res) {
// do something with error
}
}
Actually to use .db file inside the Bundle - it's a very bad idea.
Every thime, when I am using .db file, i am checking, if it allready exists inside my Application document directory, and then I will rewrite it.
#define DB_SHOULD_BE_REWRITTEN YES //You should update database and change allready existing db file to file from bundle
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"db.sqlite"];
BOOL success = [fileManager fileExistsAtPath:writableDBPath];
if (!success || DB_SHOULD_BE_REWRITTEN)
{
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"db.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
dbPath = writableDBPath;
So I can access a read-only SQLite database inside an iPhone / iPod app (Objective-C), but I'm writing a new app that will have a writable database. Obviously, the r/w file has to be in the user-writable directory. My question is, should I ship an empty database with the application and copy it over to the r/w location, or create the r/w database on the fly the first time the app launches?
You can do either, but it will be much easier (and less error prone) for you to create an empty database, put it in your bundle and then do this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];
My problem appears in the device but not in the simulator.
nDBres=sqlite3_prepare_v2(databaseConn, sqlStatement, -1, &compiledStatement,NULL)
It's an insert query that I'm trying to run. In the simulator it returns 0, whereas in the device it returns 8. After this whenever I try to run any other write operation, the app crashes.
I'm going nuts over this.
You can consult the list of sqlite3 error codes as part of the sqlite3 API documentation (http://www.sqlite.org/c3ref/c_abort.html. Error code 8 is SQLITE_READONLY ("Attempt to write a readonly database").
As you probably know, iOS sandboxes applications running on the device, so you must make sure to create your database in one of the areas that the OS exposes for creating application writable files.
There's a decent tutorial on how to set up a sqlite3 project on iOS http://icodeblog.com/2008/08/19/iphone-programming-tutorial-creating-a-todo-list-using-sqlite-part-1/.
From that tutorial, the most important part for your issue is probably the createEditableCopyOfDatabaseIfNeeded method in the app delegate. This illustrates how you can ensure that you create an editable database file when your app launches for the first time:
(Note, this isn't my code... I'm reproducing it from the tutorial on icodeblog.com, where they explain it in detail)
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[self createEditableCopyOfDatabaseIfNeeded];
[self initializeDatabase];
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"todo.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"todo.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}