SwiftUI中在FileDocument结构体内部使用Class是否存在问题?
我开发了一个基于文档的macOS SwiftUI应用,使用FileDocument(而非ReferenceFileDocument)作为文档类型。我看到的所有教程包括苹果官方内容,都用struct来定义FileDocument内部的数据结构。现在我在FileDocument的struct内部使用了class类型的属性,目前Xcode没有给出警告,但我想在深入开发前确认这么做是否会给应用带来隐患。
示例代码如下:
public class TestProjectData: Codable{ var anotherString: String init(){ anotherString = "Hello world!" } } struct DocumentDataAsClassInsteadOfStructDocument: FileDocument, Codable { var project: TestProjectData init() { project = TestProjectData() } static var readableContentTypes: [UTType] { [.exampleText] } init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents, let _ = String(data: data, encoding: .utf8) else { throw CocoaError(.fileReadCorruptFile) } let fileContents = try JSONDecoder().decode(Self.self, from: data) self = fileContents } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { let data = try JSONEncoder().encode(self) return .init(regularFileWithContents: data) } }
在FileDocument的struct中嵌套class并非完全不可行,但会带来几个需要注意的隐患,主要和值语义破坏、状态管理相关:
值语义预期冲突:
FileDocument本身是值类型(struct),设计初衷是依赖值语义管理文档状态——比如修改文档时生成新实例,系统据此追踪变化、触发保存或撤销。但内部的class是引用类型,复制文档实例时,两个实例的project属性会指向同一个class对象,修改其中一个的anotherString会直接影响另一个实例的状态,这和值类型"复制即独立"的预期不符,容易引发难以排查的状态bug。Codable潜在问题:虽然当前
TestProjectData实现Codable后编码解码可正常工作,但如果后续给class添加循环引用属性(比如weak var parent: TestProjectData?),默认的Codable实现会因循环引用导致编码失败,需要手动实现encode(to:)和init(from:)处理这类场景,增加维护成本。文档状态同步问题:SwiftUI的视图更新、文档自动保存/版本控制功能,都依赖值类型变更触发。如果修改的是class内部属性,文档struct本身未发生变化,系统可能无法感知文档已被修改,导致视图不更新、文档未自动保存等问题。你需要额外手动触发状态刷新,比如给文档struct添加标记属性,修改class时更新该标记,才能让系统识别到变化。
内存泄漏风险:如果class实例被其他对象强引用(比如某个视图或全局对象),即使文档实例被销毁,class对象也可能无法被释放,引发内存泄漏。虽然这是class本身的特性,但在文档场景下更容易被忽略,因为文档生命周期和用户操作紧密相关。
如果你的场景必须使用class(比如需要共享数据、或者有复杂引用关系),更推荐使用ReferenceFileDocument——它本身就是为引用类型设计的,能更好适配class特性,避免上述大部分问题。如果坚持用FileDocument,则需要严格控制class的使用范围,避免循环引用,并且手动处理状态变更的通知逻辑。
内容的提问来源于stack exchange,提问作者user1542125

