[英]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)
}
}
问题未解决?试试以下方法:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.