SwiftUI FileDocument using Class instead of Struct

381 Views Asked by At

I've started a document-base macOS app in SwiftUI and am using a FileDocument (not a Reference FileDocument) as the type of document. In every tutorial I've seen, even in Apple's own WWDC video discussing it (https://developer.apple.com/wwdc20/10039), a struct is always used to define the FileDocument.

My question is: is there an issue with using a class in the struct defining the document. Doing so doesn't result in any Xcode warnings but I wanted to be sure I'm not creating any issues for my app before going down this path.

Below is some example code for what I'm talking about: declaring TestProjectData as a class for use within the DocumentDataAsClassInsteadOfStructDocument - struct as a FileDocument?

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)
    }
}
2

There are 2 best solutions below

0
On

SwiftUI's architecture is all about using value types for speed and consistency. E.g. on a state change we create all the View structs and then SwiftUI diffs and uses the result to init/update/deinit UIView objects.

I believe the same thing happens with FileDocument. The struct is diffed on a change and the difference is used to init/update/deinit a UIDocument object.

If you init object vars inside these structs then basically it is a memory leak because a new object will be init every time the struct is created which is every time something changes. Also chances are you'll end up using the wrong instance of the object because there will be so many. You can see this type of problem surface when blocks are used inside body, the callback usually happens on an older version of the View struct, which isn't a problem when everything is value types but it is a big problem if referencing old objects.

Try to stick to value types in SwiftUI if you can, if you use objects you'll run into all kinds of headaches. And I don't think ReferenceFileDocument even works yet - I seem to remember it needs some kind of undo manager workaround.

0
On

It appears that yes, we need to use a struct for documents. See this post for a thorough example of the issues you can run into if you use a class instead of a struct.

SwiftUI View doesn't update