首页 > 解决方案 > MacOS 沙盒应用程序:无需 NSOpenPanel 即可访问文件

问题描述

在沙盒化的基于 NSDocument 的应用程序中,可以使用 NSOpenPanel 访问任何兼容的文档,无论文档保存在何处。如果没有 NSOpenPanel,应用程序只能访问沙盒容器中的文件。

由于我的应用程序管理两种类型的子类 NSdocument(文本作为读取器/写入器和图像仅作为读取器),我尝试为图像实现一个单独的“打开最近”菜单。当用户打开它们时,我禁用了它们的普通行为,覆盖了noteNewRecentDocumentURL: (NSURL *)urlNSDocumentController 的方法以返回图像 url 的 NO。这样只有文本文档出现在普通的文件 -> 打开最近的菜单中(当用户选择它们时正常打开)。图像列在自定义菜单中。

这些图像 url 出现问题,因为应用程序是沙盒化的:应用程序无法直接打开专用菜单中列出的任何图像文件(任何读取操作都会返回 -54 错误。可以使用以下方法检查此行为:

[[NSFileManager defaultManager] isReadableFileAtPath:[fileURL path]]

FALSE在这种情况下总是返回。只有一个例外:当我从专用的“打开最近”菜单重新打开一个文件时,该文件之前已在同一应用程序会话中使用 NSOpenPanel 打开,然后关闭:在这种情况下isReadableFileAtPath:返回TRUE并且可以访问该文件。但是当应用程序退出并重新启动时,不能以这种方式访问​​最近的图像文件。

我确定了解决此问题的三个解决方案:

  1. 一旦用户“合法”地通过 NSOpenPanel 访问图像文件,就将其移动到沙盒容器中。当然,它可以工作,但会阻止用户自行决定文件的位置!同样,在沙箱中复制文件也不是解决办法。

  2. 在沙箱中为这些文件创建别名。由于我找不到这样做的方法,我无法测试这是否是一个解决方案。

  3. 禁用应用程序沙盒。但这是更糟糕的解决方案,因为使用沙盒的原因有很多!

是否有第四个解决方案,它可以授权对任何图像文件的只读访问,无论它位于何处,而不禁用沙箱?

标签: macosnsurlfile-accessappstore-sandboxsecurity-scoped-bookmarks

解决方案


嗯,伊万的建议非常好。经过几次阅读(不到一个小时),我可以实现那些安全范围的书签。对于感兴趣的人,这里是主要发现。

  1. 将该功能添加到沙盒应用程序的权利文件中,将 com.apple.security.files.bookmarks.document-scope(或 com.apple.security.files.bookmarks.app-scope,或两者)键设置为 TRUE。

  2. 像这样修改您的文档打开方法(调用 NSOpenPanel):

-(void) openMyDocument:(id)sender{

      // ... do your stuff

    [self.panel beginWithCompletionHandler:^(NSInteger result) {
        if (result == NSModalResponseOK) {
            NSURL* selectedURL = [[self.panel URLs] objectAtIndex:0];            
            NSData *bookmark = nil;
            NSError *error = nil;
            bookmark = [selectedURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                     includingResourceValuesForKeys:nil
                                      relativeToURL:nil // Make it app-scoped
                                              error:&error];
            if (error) {
                NSLog(@"Error while creating bookmark for URL (%@): %@", selectedURL, error);
            }
            NSString *access = [NSString stringWithFormat:@"%@%@", @"Access:", [selectedURL path]];
            [[NSUserDefaults standardUserDefaults] setObject:bookmark forKey:access];
            [[NSUserDefaults standardUserDefaults] synchronize];

            // ... then open the document your way
        }
    }
}
  1. 修改您创建的方法以在不使用 NSOpenPanel 的情况下读取文件
- (void) openDocumentForScopedURL: (NSURL *) fileURL

        NSString *accessKey = [NSString stringWithFormat:@"%@%@", @"Access:", [fileURL path]];
        NSData *bookmarkData = [[NSUserDefaults standardUserDefaults] objectForKey:accessKey];
        NSURL *bookmarkFileURL = nil;
        if (bookmarkData == nil){
            // no secured-scoped bookmark found, alert the user
            return;
        } else {
            NSError *error = nil;
            BOOL bookmarkDataIsStale;

            bookmarkFileURL = [NSURL
                               URLByResolvingBookmarkData:bookmarkData
                               options:NSURLBookmarkResolutionWithSecurityScope
                               relativeToURL:nil
                               bookmarkDataIsStale:&bookmarkDataIsStale
                               error:&error];
            [bookmarkFileURL startAccessingSecurityScopedResource];
        }


        // ... Then open your file, using bookmarkFileURL
        // ... and do your stuff

        // IMPORTANT. You must notify that stopped to access

        [bookmarkFileURL stopAccessingSecurityScopedResource];            
}

推荐阅读