macOS下如何保存安全作用域URL?Finder扩展书签未持久化问题
问题分析与修复方案
你的Finder扩展每次都请求权限,核心原因是书签数据没有被正确保存或读取,代码里存在几个关键问题:
关键问题点
- 字典未初始化:首次运行时,
NSKeyedUnarchiver.unarchiveObject返回nil,bookmarks为可选空字典,此时bookmarks?[url] = data不会执行,无法添加新的书签数据。 - 变量作用域错误:获取书签数据的
do块内定义了局部变量data,但外部赋值给bookmarks的是之前的空data,导致保存的书签数据为空。 - 错误处理缺失:获取和解析书签时的错误被忽略,且未处理书签过期(
isStale为true)的情况,旧书签失效后无法重新保存。 - 路径有效性:需确保
bookmarksPath指向扩展有权限读写的目录(如Application Support),否则书签文件写入会失败。
修复后的代码
func accessFile(url: URL, userID: String, completion: @escaping ([String:Any]?, Error?) -> Void){ // 初始化书签字典,避免nil问题 var bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] ?? [:] print("Testing if we have access to file") let directoryURL = url.deletingLastPathComponent() if let data = bookmarks[directoryURL] { // 已有访问权限,解析书签 print("we have access already") var isStale = false do { let newURL = try URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) // 处理书签过期,重新保存 if isStale { let updatedData = try newURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) bookmarks[directoryURL] = updatedData NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath) } // 访问文件 guard newURL.startAccessingSecurityScopedResource() else { completion(nil, NSError(domain: "AccessError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to access resource"])) return } // < do whatever to file > newURL.stopAccessingSecurityScopedResource() completion(nil, nil) } catch { print("Failed to resolve bookmark: \(error)") completion(nil, error) } } else { // 无权限,请求用户授权 print("have not asked for access yet or directory is not saved") 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 selectedDirURL = openPanel.url else { completion(nil, NSError(domain: "UserCanceled", code: -2, userInfo: [NSLocalizedDescriptionKey: "User canceled access request"])) return } do { // 正确获取书签数据,避免作用域问题 let bookmarkData = try selectedDirURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) // 保存书签到字典并归档 bookmarks[directoryURL] = bookmarkData guard NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath) else { throw NSError(domain: "SaveError", code: -3, userInfo: [NSLocalizedDescriptionKey: "Failed to save bookmarks"]) } // 访问文件 guard selectedDirURL.startAccessingSecurityScopedResource() else { throw NSError(domain: "AccessError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to access resource"]) } // < do whatever to file > selectedDirURL.stopAccessingSecurityScopedResource() completion(nil, nil) } catch { print("Error during access or save: \(error)") completion(nil, error) } } } }
额外注意事项
- 书签路径设置:确保
bookmarksPath是扩展可读写的路径,比如:let bookmarksPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("MyExtensionBookmarks.plist") .path - 沙箱权限:Finder扩展的沙箱需要开启
User Selected File权限(在Xcode的Signing & Capabilities中添加)。
内容的提问来源于stack exchange,提问作者Nat
相关产品推荐
相关产品推荐

