13

The check for availability seems to be working fine but I can't seem to set the NSURLIsExcludedFromBackupKey key without getting this crash on launch:

dyld: Symbol not found: _NSURLIsExcludedFromBackupKey Referenced from: /Users/sam/Library/Application Support/iPhone Simulator/5.0/Applications/B0872A19-3230-481C-B5CE-D4BDE264FBDF/Transit.app/Transit Expected in: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/Frameworks/Foundation.framework/Foundation in /Users/sam/Library/Application Support/iPhone Simulator/5.0/Applications/B0872A19-3230-481C-B5CE-D4BDE264FBDF/Transit.app/Transit

Here's my method:

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL {    
    if (&NSURLIsExcludedFromBackupKey == nil)
        return NO;

    NSError *error;
    [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
    return (error != nil);
}

Crash goes away if I comment out this line:

[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];

Do I have to weak-link Foundation?

EDIT: not sure if it makes a difference, but this method is put inside an NSFileManager category.

samvermette
  • 40,269
  • 27
  • 112
  • 144

6 Answers6

19

Here's code for iOS <= 5.0.1 and >= 5.1 and includes the migration technique that @Cocoanetics mentioned.

- (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
        return [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}
tim
  • 1,682
  • 10
  • 18
  • 1
    Also this won't work for files that are present when a user upgrades from 5.0.1 to 5.1. Then these will be having the file attribute, but not the key. You'd have to check for this case and migrate the files. The safer approach would be to set both and use the actual NSString. – Cocoanetics Mar 08 '12 at 16:04
  • 2
    I forked the gist to add migration by detecting and removing the attribute: https://gist.github.com/2002382 – kevboh Mar 08 '12 at 18:05
  • 3
    You shouldn't use the presence of error to detect trouble; use the BOOL that setResourceValue:forKey:error returns. The Cocoa convention is that the error might have something in it (which you should ignore) on success. – Jesse Rusak Mar 22 '12 at 19:30
  • @tim, I just suggested an edit that you can approve. Pretty much it handles the migration technique that Cocoanetics pointed out and it includes Jesse Rusak's fix for the NSError issue. I also am the one who committed ShareKit 2.0 to include the backup attribute code. Thanks to everyone's suggestions, I will update the code there in a future pull request. – SimplyKiwi Jun 03 '12 at 01:54
  • 1
    Do not forget to #import . – Roman B. Oct 18 '12 at 20:28
  • Great. Thanks. So does this mean I still can save data into Documents folder if disable backup of my data? – Bagusflyer Apr 02 '13 at 04:24
  • This [link](http://developer.apple.com/library/ios/#qa/qa1719/_index.html) from iOS Developer Library might be of help. – christosc Aug 08 '13 at 07:38
5

This seems to be a bug with the iPhone 5.0 Simulator. I tried running the code on a 5.0 device and no crash. Reported this bug as rdar://11017158.

EDIT: this has been fixed in Xcode 4.5 DP2 (not sure if it is in 4.4).

samvermette
  • 40,269
  • 27
  • 112
  • 144
  • Nice catch. I would #define out the code in the simulator so you can still test on it in 5.0. – kevboh Mar 09 '12 at 12:41
  • 3
    I am afraid that the problem accure not only on simulator. I have the same crash when building Release build. Actually everything works fine on simulator. I think the problem is with const evaluation on application launch, because application crashed even before main method call. – lukewar May 17 '12 at 11:42
  • 4
    I can confirm that it will actually crash on a 5.0.1 device when using a release build. – Olivier Jul 11 '12 at 07:05
4

Add this line to force the symbol to be a weak import:

extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
2

Depending on what you're doing, this quick fix may work for you. It did for me.

Add the CoreFoundation framework to your project and mark it as OPTIONAL (not required).

Matjan
  • 3,591
  • 1
  • 33
  • 31
1

The problem is that this key is only present on 5.1 and above. For 5.0.1 you need to set the extended file attribute instead. The only backwards-compatible method would be to find out the NSString value of this key and set this instead below 5.1.

Cocoanetics
  • 8,171
  • 2
  • 30
  • 57
  • I'm not even looking for a backwards-compatible solution. I just want to set the attribute on iOS 5.1 and don't bother doing anything on 5.0 (that's what the check does). But even though that last line is never called, the app crashes with the "Symbol not found" error. – samvermette Mar 08 '12 at 16:03
  • Yes, but the problem is `NSURLIsExcludedFromBackupKey` is NOT weak-linked! I think if we find out the NSString value of NSURLIsExcludedFromBackupKey, it would work. But how to find it ?? – Martin Jul 25 '12 at 09:50
  • 1
    Oh, so dumb i an... `NSLog(@"%@", NSURLIsExcludedFromBackupKey);` to find its value. So it gives `@"NSURLIsExcludedFromBackupKey"`, and by replacing `NSURLIsExcludedFromBackupKey` by `@"NSURLIsExcludedFromBackupKey"` it works ! – Martin Jul 25 '12 at 10:08
  • Thanks to Cocoanetics, i posted a solution to the problem here : http://stackoverflow.com/a/11647534/127493 It works for me, please keep me informed if it works for you. – Martin Jul 25 '12 at 10:26
0

I had this same thing after updating ShareKit and redoing a project to target iOS 5.1 I would either get an error at compilation or at linking related to NSURLIsExcludedFromBackupKey. The ShareKit people seem to recommend that you can solve the problem by making sure your project links with the CoreFoundation framework and set it to "Optional" rather than "Required". However this didn't work for me.

Eventually i got around it by using the preprocessor:

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    #ifndef NSURLIsExcludedFromBackupKey                   
    // iOS <= 5.0.1.
    const char* filePath = [[URL path] fileSystemRepresentation];
    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;    
    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
    #else                                                                       
    // iOS >= 5.1
    // 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);
        }
    }
    return [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
    #endif
}
Hal
  • 1,125
  • 1
  • 11
  • 11
  • 4
    NSURLIsExcludedFromBackupKey isn't a preprocessor macro, that #ifdef will always be false. – Daniel Jun 26 '12 at 04:18