swift 如何保存安全范围内的URL以供以后使用macOS

nvbavucw  于 2022-12-22  发布在  Swift
关注(0)|答案(1)|浏览(143)

我已经做了一个Finder extension来添加一个菜单到Finder的上下文菜单中的任何文件。我想访问这个文件时,用户选择这个自定义菜单,显然这个文件,他们选择可以在任何地方的文件系统和允许的沙箱区域之外。

func accessFile(url: URL, userID: String, completion: @escaping ([String:Any]?, Error?) -> Void){
    var bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data]

    print("Testing if we have access to file")
    // 1. Test if I have access to a file
    let directoryURL = url.deletingLastPathComponent()
    let data = bookmarks?[directoryURL]
    if data == nil{
        print("have not asked for access yet or directory is not saved")
        // 2. If I do not, open a open dialog, and get permission
        let openPanel = NSOpenPanel()
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = true
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = false
        openPanel.prompt = "Grant Access"
        openPanel.directoryURL = directoryURL

        openPanel.begin { result in
            guard result == .OK, let url = openPanel.url else {return}
        
        
            // 3. obtain bookmark data of folder URL and store it to keyed archive
            do{
                let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
            }catch{
                print(error)
            }
            bookmarks?[url] = data
            NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
        
            // 4. start using the fileURL via:
            url.startAccessingSecurityScopedResource()
            // < do whatever to file >
            url.stopAccessingSecurityScopedResource()
        }
    }else{
        // We have accessed this directory before, get data from bookmarks
        print("we have access already")
        let directoryURL = url.deletingLastPathComponent()
        guard let data = bookmarks?[directoryURL]! else { return }
        var isStale = false
        let newURL = try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
    
        // 3. Now again I start using file URL and upload:
        newURL?.startAccessingSecurityScopedResource()
        // < do whatever to file >
        newURL?.stopAccessingSecurityScopedResource()
    
        }
}

当前它总是请求权限,因此书签不会被保存

3yhwsihp

3yhwsihp1#

我不能100%确定这是否是问题的根源,但是我不知道你在哪里使用了isStale值。如果它从URL(resolvingBookmarkData:...)返回true,你必须重新创建/重新保存书签。所以在你的else块中,你需要如下代码:

var isStale = false
let newURL = try? URL(
    resolvingBookmarkData: data, 
    options: .withSecurityScope, 
    relativeTo: nil, 
    bookmarkDataIsStale: &isStale
)

if let url = newURL, isStale 
{
    do
    {
        data = try url.bookmarkData(
            options: .withSecurityScope, 
            includingResourceValuesForKeys: nil, 
            relativeTo: nil
        )
    }
    catch { fatalError("Remaking bookmark failed") }

    // Resave the bookmark
    bookmarks?[url] = data
    NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
}

newURL?.startAccessingSecurityScopedResource()
// < do whatever to file >
newURL?.stopAccessingSecurityScopedResource()

当然,data现在需要是var而不是let
还要记住stopAccessingSecurityScopedResource()必须在 main 线程上调用,所以如果您不确定accessFile是否在主线程上调用,您可能需要显式地这样做:

DispatchQueue.main.async {
    newURL?.stopAccessingSecurityScopedResource()
}

你在两个地方都想这么做。
我喜欢在URL上写一个扩展,使它更好一点:

extension URL
{
    func withSecurityScopedAccess<R>(code: (URL) throws -> R) rethrows -> R
    {
        self.startAccessingSecurityScopedResource()
        defer {
            DispatchQueue.main.async {
                self.stopAccessingSecurityScopedResource()
            }
        }
        return try code(self)
    }
}

所以我可以写:

url.withSecurityScopedAccess { url in
    // Do whatever with url
}

不管你是否使用扩展,在DispatchQueue.main上显式调用stopAccessingSecurityScopedResource()并不意味着直到下一次主运行循环迭代之前访问不会停止,这通常不是问题,但是如果你在一次运行循环迭代中多次启动和停止对同一个URL的访问,它可能不起作用。因为它将多次调用startAccessingSecurityScopedResource(),而其间没有stopAccessingSecurityScopedResource(),并且在下一次迭代中,当执行排队的任务时,它将多次调用stopAccessingSecurityScopedResource()。我不知道URL是否维护了允许其安全的安全访问计数,或者只是一面旗帜,在这种情况下,它就不是了

相关问题