如何将包含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
相关产品推荐
相关产品推荐

