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

如何将包含URL.BookmarkCreationOptions的结构体序列化为Data

解决安全作用域书签URL的编码存储与恢复问题

核心问题在于URL.BookmarkCreationOptions本身不可Codable,但我们不需要直接存储这个枚举——只需要保存生成的书签数据(Data类型,天然支持Codable),以及解析时需要的关键选项标识即可。下面是完整的实现步骤:

1. 定义可编码的书签结构体

创建一个包含书签数据和必要选项标识的Codable结构体,用来封装需要存储的信息:

struct SecureBookmark: Codable {
    let bookmarkData: Data
    let usesSecurityScope: Bool
    // 若有其他需要保留的选项(如allowsRelativePath),可添加对应Bool属性
}

2. 生成书签并存储到UserDefaults/@AppStorage

先通过目标URL生成安全作用域书签,再将结构体编码为Data后存储:

// 示例:假设已通过NSOpenPanel获取到需要保存的URL
let targetURL: URL = ...

do {
    // 生成安全作用域书签数据
    let bookmarkData = try targetURL.bookmarkData(
        options: .withSecurityScope,
        includingResourceValuesForKeys: nil,
        relativeTo: nil
    )
    let secureBookmark = SecureBookmark(bookmarkData: bookmarkData, usesSecurityScope: true)
    
    // 编码为Data
    let encoder = PropertyListEncoder()
    let encodedData = try encoder.encode(secureBookmark)
    
    // 存储到UserDefaults
    UserDefaults.standard.set(encodedData, forKey: "SavedSecureBookmark")
    
    // 或使用@AppStorage(需后续扩展RawRepresentable)
    // @AppStorage("SavedSecureBookmark") var savedBookmarkData: Data? = encodedData
} catch {
    print("生成/编码书签失败:\(error.localizedDescription)")
}

3. 从存储中恢复并使用URL

取出存储的Data,解码为结构体后恢复URL,同时处理安全作用域和书签过期问题:

// 从UserDefaults取出数据
guard let encodedData = UserDefaults.standard.data(forKey: "SavedSecureBookmark") else {
    print("未找到保存的书签数据")
    return
}

do {
    let decoder = PropertyListDecoder()
    let secureBookmark = try decoder.decode(SecureBookmark.self, from: encodedData)
    
    var isStale = false
    // 恢复URL,传入对应的解析选项
    let restoredURL = try URL(resolvingBookmarkData: secureBookmark.bookmarkData,
                              options: secureBookmark.usesSecurityScope ? .withSecurityScope : [],
                              relativeTo: nil,
                              bookmarkDataIsStale: &isStale)
    
    // 处理书签过期:重新生成并保存新书签
    if isStale {
        let newBookmarkData = try restoredURL.bookmarkData(
            options: .withSecurityScope,
            includingResourceValuesForKeys: nil,
            relativeTo: nil
        )
        let updatedBookmark = SecureBookmark(bookmarkData: newBookmarkData, usesSecurityScope: true)
        let updatedEncodedData = try PropertyListEncoder().encode(updatedBookmark)
        UserDefaults.standard.set(updatedEncodedData, forKey: "SavedSecureBookmark")
    }
    
    // 启用安全作用域并使用URL
    if secureBookmark.usesSecurityScope {
        guard restoredURL.startAccessingSecurityScopedResource() else {
            print("无法启用安全作用域访问")
            return
        }
        defer {
            restoredURL.stopAccessingSecurityScopedResource()
        }
        // 在这里执行对restoredURL的操作,比如读取文件内容
        print("恢复的安全作用域URL:\(restoredURL)")
    }
} catch {
    print("解码/恢复URL失败:\(error.localizedDescription)")
}

4. 直接使用@AppStorage的优化

如果想让SecureBookmark直接支持@AppStorage,可扩展其RawRepresentable协议:

extension SecureBookmark: RawRepresentable {
    init?(rawValue: Data) {
        do {
            self = try PropertyListDecoder().decode(SecureBookmark.self, from: rawValue)
        } catch {
            return nil
        }
    }
    
    var rawValue: Data {
        do {
            return try PropertyListEncoder().encode(self)
        } catch {
            return Data()
        }
    }
}

// 之后即可直接声明使用
@AppStorage("SavedSecureBookmark") var savedSecureBookmark: SecureBookmark?

关键注意事项

  • 安全作用域URL使用完毕后必须调用stopAccessingSecurityScopedResource(),用defer语句确保执行
  • 书签可能因文件移动、权限变更等变为过期状态(isStale为true),需重新生成并保存
  • 使用PropertyListEncoder/Decoder比JSON更适合处理二进制书签数据,效率更高

内容的提问来源于stack exchange,提问作者MatFetsch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:13:15