replaceItemAtURL:withItemAtURL:backupItemName:opti

2019-05-22 22:41发布

问题:

I cannot get the NSFileManager method replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error: to work in iOS 6. Apps that call this method and worked fine on iOS 5 have major issues on iOS 6. The problem does not occur on devices running versions of iOS below 6.0. The problem does not occur if the app is launched in the iOS Simulator by Xcode. Otherwise the problem seems to be universal.

Here is the test code I am trying to execute:

NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *destinationPath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];
NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"test.txt"];

// Create initial file in documents directory
if (![fileManager fileExistsAtPath:destinationPath])
{
    BOOL fileCopied = [fileManager copyItemAtPath:sourcePath
                                           toPath:destinationPath
                                            error:&error];
    if (!fileCopied)
        [[self statusLabel] setText:[NSString stringWithFormat:@"Creation Error:\n\n%@",
                                     [error localizedDescription]]];
}

// Replace file in documents directory with copy of file from app bundle
if ([fileManager fileExistsAtPath:destinationPath])
{
    NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath];
    BOOL fileReplaced = [fileManager replaceItemAtURL:destinationURL
                                        withItemAtURL:[NSURL fileURLWithPath:sourcePath]
                                       backupItemName:nil
                                              options:0
                                     resultingItemURL:&destinationURL
                                                error:&error];
    if (!fileReplaced)
        [[self statusLabel] setText:[NSString stringWithFormat:@"Replacement Error:\n\n%@",
                                     [error localizedDescription]]];
    else
        [[self statusLabel] setText:@"Successfully replaced file."];
}

It creates the file in the documents directory, if it doesn’t already exist. It then attempts to replace the file in the documents directory with a copy of a file from the app bundle. It then reports the status of the file creation/replacement. As I said before, it replaces fine if it’s being run on iOS 5 or lower or if it’s being run in the iOS Simulator with Xcode attached to the process. However, if it’s run on an iOS 6 device or the iOS Simulator without Xcode the replacement fails and an error is returned. The localized error description is The operation couldn’t be completed. (Cocoa error 512.).

The user info dictionary for the error is:

{
NSFileNewItemLocationKey = "file://localhost/var/mobile/Applications/487FBB9E-A2BD-4CF2-BB38-F36764623C2F/Test.app/test.txt";
NSFileOriginalItemLocationKey = "file://localhost/var/mobile/Applications/487FBB9E-A2BD-4CF2-BB38-F36764623C2F/Documents/test.txt";
NSURL = "file://localhost/var/mobile/Applications/487FBB9E-A2BD-4CF2-BB38-F36764623C2F/Documents/test.txt";
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=513 \"The operation couldn\U2019t be completed. (Cocoa error 513.)\" UserInfo=0x1d58d350 {NSFilePath=/var/mobile/Applications/487FBB9E-A2BD-4CF2-BB38-F36764623C2F/Test.app/test.txt, NSURLUnsetValueKeysKey=<CFArray 0x1d58d180 [0x39b9d100]>{type = immutable, count = 2, values = (\n\t0 : <CFString 0x39b945b4 [0x39b9d100]>{contents = \"NSURLFileSecurityKey\"}\n\t1 : <CFString 0x39b943d4 [0x39b9d100]>{contents = \"NSURLCreationDateKey\"}\n)}, NSUnderlyingError=0x1d58d010 \"The operation couldn\U2019t be completed. Operation not permitted\", NSURL=file://localhost/var/mobile/Applications/487FBB9E-A2BD-4CF2-BB38-F36764623C2F/Test.app/test.txt}";
}

I have an app on the App Store which depends on this method. The live app continues to work without flaw on iOS 5, but on iOS 6 it is has huge problems due to the method failure. Does anyone know why this method is failing?

回答1:

The NSFileManager method replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error: is not a copy method; it is a move method. I.e., the file isn’t replaced with a copy of the replacement file, but with the replacement file itself. Since an app is not supposed to be able to modify its own bundle, the above code should never have worked in any version of iOS.

To retain atomicity, the solution is to first save a copy of the replacement file to the temporary directory, then replace the file with the copy in the temporary directory.

Here is the fixed test code:

NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"txt"];
NSString *destinationPath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];

// Create initial file in documents directory
if (![fileManager fileExistsAtPath:destinationPath])
{
    BOOL fileCopied = [fileManager copyItemAtPath:sourcePath
                                           toPath:destinationPath
                                            error:&error];
    if (!fileCopied)
        [[self statusLabel] setText:[NSString stringWithFormat:@"Creation Error:\n\n%@", [error localizedDescription]]];
}

// Replace file in documents directory with copy of file from app bundle
if ([fileManager fileExistsAtPath:destinationPath])
{
    // Create temporary file
    NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.txt"];

    BOOL tempCopied = [fileManager copyItemAtPath:sourcePath
                                           toPath:tempPath
                                            error:&error];
    if (!tempCopied)
        [[self statusLabel] setText:[NSString stringWithFormat:@"Temp Creation Error:\n\n%@", [error localizedDescription]]];

    // Replace file with temporary file
    NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath];

    BOOL fileReplaced = [fileManager replaceItemAtURL:destinationURL
                                        withItemAtURL:[NSURL fileURLWithPath:tempPath]
                                       backupItemName:nil
                                              options:0
                                     resultingItemURL:&destinationURL
                                                error:&error];
    if (!fileReplaced)
        [[self statusLabel] setText:[NSString stringWithFormat:@"Replacement Error:\n\n%@", [error localizedDescription]]];
    else
        [[self statusLabel] setText:@"Successfully replaced file."];
}