繁体   English   中英

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

[英]How to save security scoped URL for later use macOS

我制作了一个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()
    
        }
}

目前它总是请求许可,所以书签没有被保存

我不是 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 now。

还要记住stopAccessingSecurityScopedResource()必须在线程上调用,所以如果您不确定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是否维护安全访问计数以确保安全,或者只是一个标志,在这种情况下它不会。

让我们通过删除书签和NSOPenPanel代码使一些问题可见:

var bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data]
// bookmarks is an optional and can be nil (when the file doesn't exist)

let data = bookmarks?[directoryURL]
if data == nil {
    // NSOpenPanel
    
    do {
        let data = try openPanelUrl.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
        // this data is a local data, the other data didn't change
    } catch {
        print(error)
    }
    
    // bookmarks and data are still nil
    bookmarks?[openPanelUrl] = data
    NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
    
    // use url
} else {

    // get the bookmark data again
    guard let data = bookmarks?[directoryURL]! else { return }
    
    // get url from data and use it
}

我会做类似的事情:

var bookmarks: [URL: Data]
if let savedBookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] {
    bookmarks = savedBookmarks
}
else {
    bookmarks = [:]
}
// bookmarks is a dictionary and can be saved

if let data = bookmarks[directoryURL] {
    // get url from data and use it
}
else {
    // NSOpenPanel
    
    do {
        if let newData = try openPanelUrl.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) {
            // bookmarks and newData are not nil
            bookmarks[openPanelUrl] = newData
            NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
            
            // use url
        }
    } catch {
        print(error)
    }
}

问题未解决?试试以下方法:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2023 STACKOOM.COM