Correct way to remove a UIManagedDocument - ios5

I have a feature in my app whereby the User can reset everything on the app by click of a button. At this point, instead of trying to delete all the Core Data relations (Cascade Delete) and other complications, I decided to actually remove the entire UIManagedDocument using this piece of code
-(void)cleanUpDocument
{
[[NSFileManager defaultManager] removeItemAtPath:[self.document.fileURL path] error:nil];
}
This should remove the Document I assume? But it sometimes throws an error. And the weird part is that, when I try to re-create the Document the next time, I get an error saying "Can't create File, File already Exists". The code that i use to create the Document is this :-
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:nil]
}
My question is this :- what is the best/correct way to remove/delete an entire UIManagedDocument and start fresh on next successful login?
Thanks in advance.

I just had the same issue and tried exactly your approach at first, only to be greeted by similar errors. From what I gather, it's not the best (or at least not necessary) to delete the entire UIManagedDocument, but rather only the underlying persistent store (while keeping this managedObjectContext in sync, of course).
This answer worked for me: https://stackoverflow.com/a/8467628/671915

The problem is that you're removing the file while some objects still hold a reference to it and are keeping it open.
The correct solution is to do this:
[document closeWithCompletionHandler:^(BOOL success){
if([[NSFileManager defaultManager] fileExistsAtPath:[document.fileURL path]]){
[[NSFileManager defaultManager] removeItemAtURL:document.fileURL error:nil];
}

Related

coredata not working on xcode 4.3?

just wondering if someone else came across this.
I got this piece of code that used to work brilliant in previous xcode versions.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"mydb.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:#"mydb" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
[self addSkipBackupAttributeToItemAtURL:storeUrl];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
/*
Replace this implementation 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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
I would expect the following from this:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified diretory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
however this works only with already created db's previously.
If for example i try to put a random db named "mydb.sqlite" to my bundle and delete the original one then,
the app doesnt crash!!! a blank db is created and the new db is ignored.
This is completely wrong as it goes against my code.
In addition if I add back the original db nothing happens and the app just creates a blank db.
(and yes I do clean my project, delete the sim app, and even delete the build folder before any change occurs!)
any ideas??
There is a difference between your code and your expectation as you've stated it.
Here is your expectation:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified directory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
Here is what your code does:
Look for mydb.sqlite in the Documents directory
If mydb.sqlite does not exist, copy it from the main bundle (if it exists in the main bundle)
Create a new persistent store at ~/Documents/mydb.sqlite
The main problem I think you're experiencing is because of Step 3. addPersistentStoreWithType:... creates a new store at the URL provided. You instead need to requery the existence of the file (to check that there is now a file existing which may have been copied from the MainBundle) and if it exists, then use [persistentStoreCoordinator persistentStoreForURL:storeURL] instead of creating a new persistent store.
Once you have fixed this, the other problems should be more easily traceable. I suggest adding many more NSLog's in to your code, so you can see whether the code is following the execution path you expect. E.g. log each new object you create so you can easily see if any of them are nil. When you copy your bundle db, add an NSError pointer rather than NULL, and then if the error is non-nil, log it.
the answer to the question/ observation is simple.
downgrade to 4.2.1
I have restored my xcode to 4.2.1 (thank God for Time Machine) and now ALL is as expected.
I will file a bug report to Apple later on today.
Check that your default db is actually being included in the bundle - i.e. it is checked as included in the project, and is in the copy files build phase. I rearranged a project and an SQLite file, whilst bring copied into the project structure, was not included in my project - this would cause the behaviour you are seeing.

why does creating an nsarray of file names crash my app when document directory is empty?

ok i've been going round in circles for 1.5 hours, now i need some help. below is the code i'm using to create a list of files in my Documents Directory, all works fine unless the directory is empty.
fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error];
what can i do directly before this to check if the directory has contents?
ive tried
if ([[[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error] objectAtIndex:0]){
fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error];
}
but that also crashes the app
/////////////////////NOOB MISTAKE ALERT////////////////////
ok my problem was my next line of code
NSLog(#"fileList: %#",[fileList objectAtIndex:fileList.count-1]);
that's what was crashing my app, my own fault i admit lol. [fileList objectAtIndex:fileList.count-1] is out of bounds, i'm confused as to why the debugger didn't tell me that much.
Thanks for your help guys
The method should not crash even if there is no files. According to the doc,
... Returns an empty array if the directory exists but has no contents.
I would check
If the documentsDirectory variable is properly set as a NSString instance
If the documentsDirectory variable is not deallocated already
NSLogging documentsDirectory just before the call would be the right first step.
The method you are using returns an empty array if the directory has no content.
Before attempting to access anything from the array, check its count to make sure that something is present:
if ([fileList count] == 0)
// You have an empty directory

replaceItemAtURL fails without error on iOS but works fine on OSX

I'm implementing a manually-triggered migration process for a CoreData-based app, and after the migration completes successfully, I'm trying to move the migrated DB back over the top of the original one using replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:.
The problem is that on iOS, nothing I do will make this method return YES, however it also never puts anything into the error pointer to allow you to see what's going wrong.
I'd read things elsewhere (e.g. http://www.cocoabuilder.com/archive/cocoa/287790-nsdoc-magic-file-watcher-ruins-core-data-migration.html) indicating that not shutting down all the CoreData objects (e.g. NSMigrationManager, NSManagedObjectModel etc) before attempting the replace might be the cause, but that wasn't it. I even implemented a little two file create-and-swap thing that didn't involve CoreData DBs at all to verify that the CoreData stuff didn't have anything to do with it.
I then noticed in the official documentation that the newitemURL is supposed to be in a directory deemed appropriate for temporary files. I assumed that that meant a directory returned by URLForDirectory:inDomain:appropriateForURL:create:error: using NSItemReplacementDirectory as the search path.
That didn't work either! I ended up falling back to implementing the replacement logic using separate operations, but this is non-atomic and unsafe and all that bad stuff.
Does anyone have a working snippet of code that runs on iOS that either return YES from a call to replaceItemAtURL or actually puts error information into the error pointer?
Any help much appreciated.
EDIT - Test code included below. This runs in application:didFinishLaunchingWithOptions: on the main thread.
NSFileManager *fm = [[NSFileManager alloc] init];
NSError *err = nil;
NSURL *docDir = [NSURL fileURLWithPath:[self applicationDocumentsDirectory]];
NSURL *tmpDir = [fm URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:docDir
create:NO
error:&err];
NSURL *u1 = [docDir URLByAppendingPathComponent:#"f1"];
NSURL *u2 = [tmpDir URLByAppendingPathComponent:#"f2"];
NSURL *repl = nil;
[fm createFileAtPath:[u1 path]
contents:[[NSString stringWithString:#"Hello"]
dataUsingEncoding:NSUTF8StringEncoding]
attributes:nil];
[fm createFileAtPath:[u2 path]
contents:[[NSString stringWithString:#"World"]
dataUsingEncoding:NSUTF8StringEncoding]
attributes:nil];
BOOL test = [fm replaceItemAtURL:u1 withItemAtURL:u2 backupItemName:#"f1backup"
options:0 resultingItemURL:&repl error:&err];
// At this point GDB shows test to be NO but error is still nil
I have experienced issues with all the NSFileManager methods using an URL on iOS. However, all the methods using Path work. So I think you should use removeItemAtPath:error:and copyItemAtPath:toURL:error: for that purpose.
Hope it helps
In mac file system is not case sensitive, but in IOS it. Even though you cant have two files with same name but with different case at one location, the path is case sensitive. So if file is has .JPEG and in your code you are passing link with .jpeg it will fail.
It may not be the case with you but just what to share
Although strangely it should give you error.

NSFileManager - Copying Files at Startup

I need to copy a few sample files from my app's resource folder and place them in my app's document folder. I came up with the attached code, it compiles fine but it doesn't work. All the directories I refer to do exist. I'm not quite sure what I am doing wrong, could someone point me in the right direction please?
NSFileManager*manager = [NSFileManager defaultManager];
NSString*dirToCopyTo = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString*path = [[NSBundle mainBundle] resourcePath];
NSString*dirToCopyFrom = [path stringByAppendingPathComponent:#"Samples"];
NSError*error;
NSArray*files = [manager contentsOfDirectoryAtPath:dirToCopyFrom error:nil];
for (NSString *file in files)
{
[manager copyItemAtPath:[dirToCopyFrom stringByAppendingPathComponent:file] toPath:dirToCopyTo error:&error];
if (error)
{
NSLog(#"%#",[error localizedDescription]);
}
}
EDIT: I just edited the code the way it should be. Now however there's another problem:
2010-05-15 13:31:31.787 WriteIt
Mobile[4587:207] DAMutableDictionary.h
2010-05-15 13:31:31.795 WriteIt
Mobile[4587:207] FileManager
Error:Operation could not be
completed. File exists
EDIT : I have fixed the issue by telling NSFileManager the names of the copied files's destinations.
[manager copyItemAtPath:[dirToCopyFrom stringByAppendingPathComponent:file] toPath:[dirToCopyTo stringByAppendingPathComponent:file] error:&error];
I think the problem is in this line:
NSArray*files = [manager contentsOfDirectoryAtPath:dirToCopyTo error:nil];
You are listing files in a destination directory instead of the source. Change it to something like:
NSArray*files = [manager contentsOfDirectoryAtPath:dirToCopyFrom error:nil];
And you should be fine.
I think the problem is that yo are reading the files to copy from dirToCopyTo and I think you meant dirToCopyFrom
Also to get the documents directory you should be using NSDocumentDirectory with - (NSArray *)URLsForDirectory:(NSSearchPathDirectory)directory inDomains:(NSSearchPathDomainMask)domainMask
Please note that lengthy operation on startup must be avoided:
Not a good User Experience (delay and croppy behavior)
Watchdog in iOS can kill your app as if it were stuck.
So perform copy in a secondary thread (or operation... or whatever uses a different execution path).
Another problem will arise if You need data to populate your UI: in that case:
Disable UI elements
Start an async / threaded operation
In the completion call back of copying (via a notification, a protocol.. or other means)
notify to the UI interface it can start fetching data.
For example we copy a ZIP file and decompress it, but it takes some time so we had to put it in a timer procedure that will trigger UI when done.
If You need an example, ket me know.
PS:
Copying using ZIP file is MORE efficient as:
Only call to file system
Far less bytes to copy
The bad news: you must use a routine do decompress zip file, but you can find them on the web.
Decompressing Zip files should be more efficient as these calls are written in straight C, and not in Cocoa with all the overhead.
[manager copyItemAtPath:[dirToCopyFrom stringByAppendingPathComponent:file] toPath:dirToCopyTo error:&error];
The destination path is the path you want the copy to have, including its filename. You cannot pass the path to a directory expecting NSFileManager to fill in the name of the source file; it will not do this.
The documentation says that the destination path must not describe anything that exists:
… dstPath must not exist prior to the operation.
In your case, it's the path to the destination directory, so it does exist, which is why the copy fails.
You need to make it a path to the destination file by appending the desired filename to it. Then it will not exist (if not previously copied), so the copy will succeed.

Why can't I write files to my app's Document directory?

I have found several snippets of code describing how to write data to a user's application Documents folder. However, when I try this out in the iPhone simulator, no files get created. I called
[NSFileManager isWritbleAtPath:<my document folder>]
and it returned 0 (false). Do I need to make this folder explicitly writable, and if so, how do I do it?
The iPhone simulator should be able to write to the entire disk. My app routinely dumps test files to the root level of my boot volume (using [NSData's writeToPath:#"/test.jpg" atomically:NO]).
Are you sure that you've correctly determined the path to the documents folder? You need to expand the tilde in the path. Here's the code my app uses to put things in the documents folder. I don't think there's any more setup involved!
brushesDir = [[#"~/Documents/BrushPacks/" stringByExpandingTildeInPath] retain];
// create brush packs folder if it does not exist
if (![[NSFileManager defaultManager] fileExistsAtPath: brushesDir])
[[NSFileManager defaultManager] createDirectoryAtPath:brushesDir withIntermediateDirectories:YES attributes:nil error:nil];
NSLog(#"writable: %d", [[NSFileManager defaultManager] isWritableFileAtPath:NSHomeDirectory()]);
This prints 1 on the console.
Did you mean to call the method isWritableAtPath or isWritableFileAtPath ? And did you mean to call it on the class itself, or on a (default) instance of it?
Thanks for the pointers. So after a toiling through a few documents, I found the thing I was doing wrong: trying to save an NSArray that wasn't composed of basic datatypes such as NSDictionary, NSArray, or NSString. I was trying to save an array of MPMediaItems (from the MediaKit Framework in SDK 3.0+).
I had a trivial issue with the file writing to NSBundle. I had a requirement where a text file needs to be updated with the server as soon as app launches and it worked well with the simulator but not with the device. I later found out that we don't have write permission with NSBundle. Copying the file into Documents directory from NSBundle and using for my purpose solved my problem. I use :
[myPlistData writeToFile:fileName atomically:NO];