You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 11:35:20