Cocoa 处理过时NSURL书签的正确方法是什么?

Cocoa 处理过时NSURL书签的正确方法是什么?,cocoa,nsurl,security-scoped-bookmarks,Cocoa,Nsurl,Security Scoped Bookmarks,从安全范围的书签解析NSURL时,如果用户重命名或移动了该文件或文件夹,则书签将过时。苹果公司的文件说,关于陈腐: isStale 返回时,如果是,则书签数据已过时。你的应用应该 使用返回的URL创建一个新书签,并使用它代替 现有书签的任何存储副本 不幸的是,这很少适用于我。它可能在5%的时间内工作。尝试使用返回的URL创建新书签会导致错误,代码为256,在控制台中查看会显示来自sandboxd的消息,其中说拒绝更新URL上的文件读取数据 注意如果重新生成书签确实有效,它似乎只在第一次重新生成书

从安全范围的书签解析NSURL时,如果用户重命名或移动了该文件或文件夹,则书签将过时。苹果公司的文件说,关于陈腐:

isStale

返回时,如果是,则书签数据已过时。你的应用应该 使用返回的URL创建一个新书签,并使用它代替 现有书签的任何存储副本

不幸的是,这很少适用于我。它可能在5%的时间内工作。尝试使用返回的URL创建新书签会导致错误,代码为256,在控制台中查看会显示来自sandboxd的消息,其中说拒绝更新URL上的文件读取数据

注意如果重新生成书签确实有效,它似乎只在第一次重新生成书签时有效。如果文件夹/文件再次被移动/重命名,它似乎永远不会起作用

我最初如何创建和存储书签

-(IBAction)bookmarkFolder:(id)sender {
  _openPanel = [NSOpenPanel openPanel];
  _openPanel.canChooseFiles = NO;
  _openPanel.canChooseDirectories = YES;
  _openPanel.canCreateDirectories = YES;
  [_openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
    if (_openPanel.URL != nil) {
      NSError *error;
      NSData *bookmark = [_openPanel.URL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                                  includingResourceValuesForKeys:nil
                                                   relativeToURL:nil
                                                           error:&error];
      if (error != nil) {
        NSLog(@"Error bookmarking selected URL: %@", error);
        return;
      }
      NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      [userDefaults setObject:bookmark forKey:@"bookmark"];
    }
  }];
}
-(void)resolveStoredBookmark {
  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  NSData *bookmark = [userDefaults objectForKey:@"bookmark"];
  if (bookmark == nil) {
    NSLog(@"No bookmark stored");
    return;
  }
  BOOL isStale;
  NSError *error;
  NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
                                         options:NSURLBookmarkResolutionWithSecurityScope
                                   relativeToURL:nil
                             bookmarkDataIsStale:&isStale
                                           error:&error];
  if (error != nil) {
    NSLog(@"Error resolving URL from bookmark: %@", error);
    return;
  } else if (isStale) {
    if ([url startAccessingSecurityScopedResource]) {
      NSLog(@"Attempting to renew bookmark for %@", url);
      // NOTE: This is the bit that fails, a 256 error is 
      //       returned due to a deny file-read-data from sandboxd
      bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
               includingResourceValuesForKeys:nil
                                relativeToURL:nil
                                        error:&error];
      [url stopAccessingSecurityScopedResource];
      if (error != nil) {
        NSLog(@"Failed to renew bookmark: %@", error);
        return;
      }
      [userDefaults setObject:bookmark forKey:@"bookmark"];
      NSLog(@"Bookmark renewed, yay.");
    } else {
      NSLog(@"Could not start using the bookmarked url");
    }
  } else {
    NSLog(@"Bookmarked url resolved successfully!");
    [url startAccessingSecurityScopedResource];
    NSArray *contents = [NSFileManager.new contentsOfDirectoryAtPath:url.path error:&error];
    [url stopAccessingSecurityScopedResource];
    if (error != nil) {
      NSLog(@"Error reading contents of bookmarked folder: %@", error);
      return;
    }
    NSLog(@"Contents of bookmarked folder: %@", contents);
  }
}
解析书签的代码

-(IBAction)bookmarkFolder:(id)sender {
  _openPanel = [NSOpenPanel openPanel];
  _openPanel.canChooseFiles = NO;
  _openPanel.canChooseDirectories = YES;
  _openPanel.canCreateDirectories = YES;
  [_openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
    if (_openPanel.URL != nil) {
      NSError *error;
      NSData *bookmark = [_openPanel.URL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                                  includingResourceValuesForKeys:nil
                                                   relativeToURL:nil
                                                           error:&error];
      if (error != nil) {
        NSLog(@"Error bookmarking selected URL: %@", error);
        return;
      }
      NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      [userDefaults setObject:bookmark forKey:@"bookmark"];
    }
  }];
}
-(void)resolveStoredBookmark {
  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  NSData *bookmark = [userDefaults objectForKey:@"bookmark"];
  if (bookmark == nil) {
    NSLog(@"No bookmark stored");
    return;
  }
  BOOL isStale;
  NSError *error;
  NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
                                         options:NSURLBookmarkResolutionWithSecurityScope
                                   relativeToURL:nil
                             bookmarkDataIsStale:&isStale
                                           error:&error];
  if (error != nil) {
    NSLog(@"Error resolving URL from bookmark: %@", error);
    return;
  } else if (isStale) {
    if ([url startAccessingSecurityScopedResource]) {
      NSLog(@"Attempting to renew bookmark for %@", url);
      // NOTE: This is the bit that fails, a 256 error is 
      //       returned due to a deny file-read-data from sandboxd
      bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
               includingResourceValuesForKeys:nil
                                relativeToURL:nil
                                        error:&error];
      [url stopAccessingSecurityScopedResource];
      if (error != nil) {
        NSLog(@"Failed to renew bookmark: %@", error);
        return;
      }
      [userDefaults setObject:bookmark forKey:@"bookmark"];
      NSLog(@"Bookmark renewed, yay.");
    } else {
      NSLog(@"Could not start using the bookmarked url");
    }
  } else {
    NSLog(@"Bookmarked url resolved successfully!");
    [url startAccessingSecurityScopedResource];
    NSArray *contents = [NSFileManager.new contentsOfDirectoryAtPath:url.path error:&error];
    [url stopAccessingSecurityScopedResource];
    if (error != nil) {
      NSLog(@"Error reading contents of bookmarked folder: %@", error);
      return;
    }
    NSLog(@"Contents of bookmarked folder: %@", contents);
  }
}
当书签过时时,得到的解析URL确实指向正确的位置,尽管[URL startAccessingSecurityScopedResource]返回YES,但我实际上无法访问该文件

也许我误解了有关陈旧书签的文档,但我希望我只是在做一些愚蠢的事情。每次重命名或移动带书签的文件/文件夹时弹出NSOpenPanel,这是我目前唯一的选择,看起来很可笑


我应该补充一点,我的授权文件中com.apple.security.files.bookmarks.app-scope、com.apple.security.files.user-selected.read-write和com.apple.security.app-sandbox都设置为true。

经过大量令人失望的测试,我得出以下结论。虽然合乎逻辑,但它们令人失望,因为用户的体验远不理想,开发人员也很痛苦,这取决于他们愿意帮助用户重新建立对书签资源的引用的程度

当我在下面说“续订”时,我的意思是“生成一个新书签,使用从过期书签解析的URL替换过期书签。”

  • 只要已添加书签的资源在您的应用程序已具有访问权限的目录中移动或重命名,则续订始终有效。因此,默认情况下,它总是在应用程序的容器文件夹中工作

  • 如果将书签资源移动到应用程序无权访问的文件夹中,则续订失败。e、 g.用户将文件夹从容器文件夹拖动到容器文件夹外的某个文件夹。您将能够解析URL,但无法访问或续订书签

  • 如果书签资源位于应用程序无权访问的文件夹中,则续订失败,然后重命名该文件夹。这意味着用户可以显式地授予您的应用程序对资源的访问权,然后通过重命名该资源而无意中撤销该访问权

  • 如果将资源移动到另一个卷,解析将失败。不确定这是一般书签的限制还是仅在沙盒应用程序中使用

  • 对于问题2和问题3,您作为开发人员处于良好的地位,因为书签URL的解析确实有效。你至少可以通过准确地告诉用户需要哪些资源来授予你的应用程序访问权限以及它们在哪里来引导用户。让他们选择一个包含(直接或间接)您需要续订书签的所有资源的文件夹,可以改善体验。这甚至可以是卷,如果他们愿意为您的应用程序提供如此多的访问权限,那么卷就完全解决了问题

    对于问题4,解决方案根本不起作用。用户将不得不在没有任何提示的情况下重新定位文件,因为您无法解析新位置。我在当前应用程序中所做的一件事减轻了这个问题的痛苦,那就是为我存储书签的任何资源添加一个扩展属性。这样做至少可以让用户选择一个文件夹来搜索以前关联的资源


    令人沮丧的限制,但书签仍然胜过存储静态路径。

    @user362515请确认,您是说,在10.11之前创建新书签时,旧书签解决了问题,但报告已过时且无法访问,您没有看到任何问题?具体来说,2号子弹不再是个问题了?如果是这样,那太好了!从OS X 10.11开始,从已解决的旧书签创建新的安全范围书签似乎没有问题。是的,只要在新书签上运行
    startAccessingSecurityScopedResource
    ,持续时间为
    bookmarkDataWithOptions
    。顺便说一句,重命名/移动文件的书签解析仅在同一卷上有效,这是有文档记录的。@user362515真棒,至少这是一些改进。我没有在文档中看到它说解析只能在原始卷上工作。我确实看到了它所说的对于无法装入的卷它将失败的地方。你有没有链接到你看到这个的确切位置?我实际上已经创建了一个单独的问题,与此相关