Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overwriting NSDocument's init(contentsOf:ofType:) in Swift

background:

Regarding to the document-based Cocoa application, Apple suggests to override NSDocument's init(contentsOf:ofType:) to customize things, therefore it seems as if it's a good place to override.

You can override this method to customize the reopening of autosaved documents.

cf. init(contentsOf:ofType:) - NSDocument | Apple Developer Documentation

However on Swift, it is actually impossible because super's init(contentsOf:ofType:) can't be invoked in init(contentsOf:ofType:) since this initializer is one of the convenience initializers.

convenience init(contentsOf url: URL, ofType typeName: String) throws {

    try super.init(contentsOf: url, ofType: typeName)  // <- this marked as error
}

what I want:

When I write document-based apps in Objective-C, I use this method, namely initWithContentsOfURL:ofType:error:, to prepare some file-related properties only for the document which opened with an existing file but not for new blank documents or resumed documents.

It cannot be simply substituted with NSDocument's read(from:ofType) because it will be invoked not only when document opens but also every time when the document is reloaded (e.g. Revert).

It is also not possible to do my stuff on normal init(), because fileURL property is not yet set at that moment.

init() {
    super.init()

    self.fileURL  // <- returns nil
}

I know a convenience initializer must invoke a designated initializer, but init(contentsOf:ofType:) is much complex and unclear to imitate super's behavior unlike init(type:). (How do I initialise a new NSDocument instance in Swift?)

Something like this? (but not sure)

 convenience init(contentsOf url: URL, ofType typeName: String) throws {
    self.init()

    self.fileURL = url
    self.fileType = typeName
    self.fileModificationDate = (try? FileManager.default.attributesOfItem(atPath: url.path))?[.modificationDate] as? Date
}

question:

So, my question is: Where is the suitable point to do stuff with fileURL only once when document is opened?

workaround 1:

For workaround, I currently override makeDocument(withContentsOf:ofType: of NSDocumentController subclass and create a document from original document's initializer in which self.init(contentsOf: url, ofType: typeName) is invoked.

workaround 2:

The first workaround I mentioned above was not safe. I found it's better to do additional initialization in a separated function.

override func makeDocument(withContentsOf url: URL, ofType typeName: String) throws -> NSDocument {

    let document = try super.makeDocument(withContentsOf: url, ofType: typeName)

    (document as? Document)?.additionalInit()  // call original func

    return document
}
like image 233
1024jp Avatar asked Nov 08 '22 10:11

1024jp


1 Answers

Definitly a shortcoming in current swift and as far as I can see discussed at Swift development. Yet, the override can be easily achieved. From Apple documentation it is clear that read(from:, typeName) is called and fileURL, fileType and fileModificationDate are set. That are a few simple calls like for example:

convenience init(contentsOf url: URL, ofType typeName: String) throws {
    self.init()
    try self.read(from:url, ofType:typeName)
    self.fileURL = url
    self.fileType = typeName
    self.fileModificationDate = Date()
    //methods and sets values for the fileURL, fileType, and fileModificationDate properties.
    self.undoManager?.disableUndoRegistration()
    self.initializeGroups(false)
    self.managedObjectContext?.processPendingChanges()
    self.undoManager?.enableUndoRegistration()
}

I am just starting an NSPersistentDocument subclass and for now this works well.

like image 101
Volker Avatar answered Nov 15 '22 04:11

Volker