0

I am writing an OS X document-based application using cocoa/swift. So far I have a model, which is managed by the NSDocument subclass. Custom views are managed by custom view controllers, which update the views to keep them synchronised with the model.

The model->controller->view flow of information is strightforward. I have the view controllers observing the document and, when the document changes, the view controllers do their job with the views. The issue is that, in this process, the model's objects are obviously exposed to the view controllers, and therefore the view controllers could also modify the model if I wish (or if I make a mistake).

I'd like the document to be the only one who has "permission" to modify the model objects. The view controllers should have read-only access to them. Is there a way to do this in Swift?

Thanks in advance.

George
  • 1,235
  • 10
  • 23
  • Note: With structs there is no problem, since declaring them as `private(set)` within the document does the job. With classes, though `private(set)` only prevents changing the reference from outside the document, but not the content of the object. – George Jul 25 '15 at 06:47

1 Answers1

0

In Swift, the private variable modifier does not apply to classes which are defined in the same file, so if you put your Model class definition in the same file as your NSDocument subclass, then the NSDocument subclass can change the private Model variables as if they were public, but an NSViewController subclass defined in another file will not have access to the private Model variables.

Then you can make private variables partially private by writing:

private(set) var name: String

...which will allow the NSController subclass to read them, but not set them. Swift synthesizes setters and getters for all your variables (not just computed properties), and that tells Swift to make the setter private.

I tested private(set) with some observer code, and the above scenario will allow the NSDocument subclass to change the Model, but if the NSViewController subclass tries to change the Model, Xcode immediately flags the assignment with the error:

Cannot assign to the result of this expression

MyDocument.swift:

import Cocoa

class Employee: NSObject {
    private(set) var name: String

    init(name:  String) {
        self.name = name
        super.init()
    }
}


class MyDocument: NSDocument {

    dynamic var worker = Employee(name: "Joe")

    //...The rest of the NSDocument junk here
}

MyViewController.swift:

import Cocoa

class MyViewController: NSObject {


    var document: MyDocument
    var IdentifierForThisClass: Int = 0


    init(document: MyDocument) {

        self.document = document
        super.init()

        self.document.addObserver(self,
            forKeyPath: "worker",
            options: .Old | .New,
            context: &IdentifierForThisClass
        )
    }

    override func observeValueForKeyPath(
        keyPath: String,
        ofObject object: AnyObject,
        change: [NSObject : AnyObject],
        context: UnsafeMutablePointer<Void>) {

            println("Observer:")

            if context != &IdentifierForThisClass {
                println("This Observer message was meant for a parent class!")
                super.observeValueForKeyPath(keyPath,
                    ofObject: object,
                    change: change,
                    context: context
                )

                return
            }

            var newValue = change[NSKeyValueChangeNewKey] as! Employee

            println("\tThe worker has been changed to: \(newValue.name)")


    }

    func doStuff() {
        println("Inside doStuff():")
        println("\tThe worker's name is \(document.worker.name)")
        //document.worker.name = "Jenny"
    }

}

Some code to exercise the classes:

let myDoc = MyDocument()

let viewController = MyViewController(
    document: myDoc
)

myDoc.worker = Employee(name: "Jenny")
viewController.doStuff()

--output:--
Observer:
    The worker has been changed to: Jenny
Inside doStuff():
    The worker's name is Jenny

Then if I uncomment the line:

doStuff() {
    ...
    //document.worker.name = "Jenny"
    ...
}

Xcode immediately flags that as an error.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • That only works if you define your Model objects in the same file as NSDocument, I am afraid… – George Nov 16 '15 at 11:07