mjl5007
mjl5007

Reputation: 149

Cocoa MVC: interaction between "model controller" and "view controller"

Just started learning Objective-C and Cocoa with the help of BNR's Cocoa Programming for Mac OS X (4th Ed.), and I'm working on a document-based application. I've read through Apple's developer documentation on the document architecture, and have chosen to subclass NSWindowController and override makeWindowControllers on my NSDocument subclass. I have a few reasons for doing this:

So, my NSDocument subclass is a model controller, and my NSWindowController subclass is a view controller. Further, I understand that most of an application's "work" is done in the controller objects, as the views and models should be as application-agnostic and reusable as possible. Now comes my question: how do these two types of controllers interact to actually do this "work"?

For example, imagine I'm writing a spreadsheet application, and I want to have a menu item (or toolbar button) that brings up a sheet for creating a chart or graph from some of my data. In that sheet, the user will enter various parameters and options for how to create the chart or graph, then click "OK" (or whatever the button is called).

Who should respond to the menu item's action, the document (model controller) or the window controller (view controller)? The task of actually loading and showing the sheet seems decidedly "view-related", so it should go in the window controller, correct? But the controller for the sheet needs a model to display to the user (a Chart object, or maybe ChartInputs); where does that model get created and given to the sheet controller? Should the document respond to the menu item by creating the ChartInputs model object, then pass that to the window controller, which creates the sheet controller, passing it the model object, and shows the sheet? Or should the window controller respond to the menu item, request a new model object (perhaps through some sort of factory provided via dependency injection into the constructor of the window controller), then proceed with creating the sheet controller, passing the model, and showing the sheet?

What about after the user fills out the sheet and clicks "OK"? Where should control be returned to process the user's choices and actually create the chart -- window controller, document, or both? What about logic to validate the user's inputs after they click "OK", but before the sheet is dismissed (in case something is invalid)?

Upvotes: 3

Views: 1722

Answers (1)

paulmelnikow
paulmelnikow

Reputation: 17208

To begin, consider windowless operation of your NSDocument. For example, you might create a utility application which shares your NSDocument class, opening documents for scripting, printing, or other manipulation, but without presenting your primary document window. Imagine your NSDocument class reused for that application – and put the logic you wouldn't want into your window controller. This way, the NSDocument subclass is primarily responsible for activities which affect the document's state.

These are the responsibilities of the model-controller (NSDocument subclass):

  • Serialization and deserialization
  • Loading and saving
  • Manipulating the document's state
  • Managing and dispatching print views
  • Monitoring the document for changes by others
  • Refreshing supporting data sources – sources which affect the document model – and applying changes to the model and document as needed
  • Managing an activity log related to the document
  • Owning the undo manager
  • Exposing the essential model objects to the view controller
  • Creating window controllers
  • Facilitating some editing behaviors, such as a change to one property triggering creation or removal of objects

If you're using Core Data, your managed object context, persistent store coordinator, and persistent store are part of the model-controller, not the model. Of course the managed objects themselves are part of the model.

This leaves these responsibilities to the model:

  • Helper methods for inserting, rearranging, and deleting model objects
  • Helper methods for accessing specific parts of the model
  • Data validation
  • Rendering the model to strings, in various formats
  • Serializing and deserializing oneself
  • Facilitating some editing behaviors, such as a change to one property triggering changes to other properties

On the other side, these are the responsibilities of the view-controller:

  • Manipulating the view to keep it in sync with the model
  • Manipulating the view to keep it in sync with the document state
  • Responding to localized actions, such as a button which adds or removes a model object
  • Presenting auxiliary views and responding to input in those views
  • Dispatching actions which are affected by selections in the UI, such as the selected rows in a table view
  • Managing auxiliary controllers used during editing, which are not related to the document itself (such as web-service data)
  • Serving as a data source for view objects

If you're using Cocoa Bindings, your bindings also part of the view controller.

This design produces a reasonable separation of responsibilities between the view-controller and model-controller. However, they both sit between the view and model. While this produces modularity it doesn't produce decoupling.

Though I did consider windowless operation, I largely arrived at this design pattern empirically – by placing similar code together and separating code that felt out-of-place. I'm curious if others post authoritative sources or references which agree or disagree about how to do this.


To take up your example, I'd suggest this design:

  1. EditorWindowController creates ChartParameters object and gives it a reference to the document's model:

    [[ChartParameters alloc] initWithWorkbook:self.document.workbook]
    
  2. EditorWindowController sets up the new-chart view, which probably has its own NewChartViewController. It passes the ChartParameters and document objects to the NewChartViewController and displays the window.

  3. The ChartParameters object is responsible for validating the user's choices. The NewChartViewController needs to manipulate the view to keep it in sync with the validation result. (Avoid letting the user make a mistake: don't wait until the end to validate the input.)

  4. When the view finishes, the NewChartViewController asks the model to create a new chart using the given parameters:

    [self.document.workbook addChartWithParameters:self.chartParameters]
    

If you want the not-yet-a-chart object to be part of your document, you can do is this way instead:

  1. EditorWindowController asks document model to create a new chart object:

    Chart *newChart = [self.document.workbook addChart]
    
  2. Thew new chart should have a flag set, indicating it's not ready for display.

  3. EditorWindowController sets up the NewChartViewController, passes it the chart, displays the window.

  4. Chart object validates the users's choices and the NewChartViewController keeps the view in sync.

  5. When finished, tell the chart it's ready for display. Or if the user cancels, remove it.

In either of these designs, NewChartViewController is a model-controller and view-controller in one, localized for its particular task.

Upvotes: 6

Related Questions