Cocoa for Scientists (Part IX): MVC is not the ‘Motor Vehicle Commission’ | MacResearch

Cocoa for Scientists (Part IX): MVC is not the ‘Motor Vehicle Commission’

Author: Drew McCormack
Website: http://www.macanics.net

Last time we got our first glimpse of Interface Builder, and built a working Cocoa application. It was about as useful as a Dashboard widget, but at least it was a start. In this tutorial, and the next, I want to extend the conversion application, Unitary, so that it can handle multiple types of conversions. To do this, we will need to write a few classes in Xcode, add a popup button in Interface Builder, and make the acquaintance of a fundamental Cocoa design pattern: Model-View-Controller (MVC).

MVC is Addictive

Model-View-Controller permeates the whole of Cocoa, and most other modern development frameworks (eg Ruby on Rails). It is a design pattern that promotes loose coupling, which is highly desirable in Object-Oriented Programming (OOP). ‘Loose coupling’ means that the components that make up your program are as independent as possible from one another, free to be varied with minimal impact on one another — no side-effects. If you are mathematically inclined, you can think of loose coupling as being similar to finding the eigenstates of a set of equations, the set of functions that decouple the degrees of freedom so that they can vary independently. The more tightly coupled the elements of a program are, the more difficult it is to understand the program and make changes to it.

Not surprisingly, MVC has three basic parts: the model, the view, and the controller. The model is made up of the classes that store data, and include information about what that data means, and how it is interrelated. To use a term from Computer Science, the model is said to describe the ‘business logic’ of the application.

A graphical application is not much good if you can’t view and manipulate the data it contains, and that’s the role of the view layer. A view is a representation of the data from the model layer. It need not be graphical at all; printing data to a console could also be considered a view. An application can have multiple views of the same data; for example, you could have an application that has a spreadsheet for displaying data sets, and a chart window where that same model data can be plotted. The spreadsheet and charts are both in the view layer.

In a well designed MVC app, the views should not directly access the model objects, but should retrieve data via an intermediate, the Controller. The controller is like a traffic cop: it takes any events received from the views, such as buttons being pushed, or selections changing, and decides what to do with that information. Usually, it will update the model data, or retrieve some data from the model layer and update the views with it. Similarly, if you make a change to the data in the model layer, the Controller is responsible for ensuring that all views are kept in synch with the change.

In the good old days, before Mac OS X 10.3 was released, developers had to write quite a bit of code for each of these layers in order to develop a Cocoa application. Beginning in Panther, Apple started to introduce frameworks to help out with a lot of the more repetitive parts of MVC. First came Cocoa Bindings, which includes controller classes. You still need to write some of the controller logic, but much of it can now be handled automatically for you, which makes keeping views in step with the model considerably simpler. In Mac OS X 10.4, Apple introduced the Core Data framework, which is used to create data models. This makes storing data, and supporting behavior like undo/redo, much simpler than it was before.

We will meet these technologies in due course, but will start with the old-fashioned way of doing things. Note that this is more than an academic exercise, because as nice as Bindings and Core Data are, they can’t handle all scenarios. The cost to the developer of having these cool new technologies is a steeper learning curve, because now you have to know how to use both approaches.

The Conversion Abstract Class

We are going to begin by creating some model classes to represent different types of conversions. Next time, we will add some view objects to control the active conversion.

All conversion classes will descend from one abstract Conversion class, and we will write that first. Locate the finished project from last time, or download it again, and then follow these steps:

  1. Open Unitary Xcode project by double clicking it.
  2. Open Unitary group in the Groups & Files pane on the left of the project window.
  3. Open the Classes group, and then select the Classes group in preparation for adding a file to it.
  4. Choose the menu item File > New File…
  5. In the New File panel, locate the Cocoa group, and select ‘Objective-C class’. Click Next.
  6. Enter ‘Conversion.m’ as the file name, and make sure ‘Also create “Conversion.h”’ is checked. Click Finish.

Now we will write the interface for the Conversion class.

Click Conversion.h in Groups & Files list view. Make sure the editor is visible on right; you may need to pull up the split view from the bottom of the window. Enter the following code.



#import <Cocoa/Cocoa.h>

@interface Conversion : NSObject {
}
@end

@interface Conversion (Abstract)

-(NSString *)firstUnitName;
-(NSString *)secondUnitName;

-(double)secondValueForFirstValue:(double)firstValue;
-(double)firstValueForSecondValue:(double)secondValue;

@end

Editing the <code>Conversion.h</code> file

As stated above, Conversion is an abstract class. It is used to define the interface of a conversion model class, but is never directly instantiated itself. We will not provide any implementation for the methods declared in the interface, because there is no reasonable implementation for a general conversion. Instead, we leave this task to the Conversion subclasses.

You may be wondering why I have used a category to declare the methods, rather than simply putting them in the main interface block. If you do put the method declarations in the main interface block, the compiler will issue warnings that the implementations of the methods were not found. A category can be used to avoid these warnings without providing an implementation.

The TemperatureConversion Class

With the abstract Conversion class in place, we need one or more concrete classes to implement real conversions. We will add several of these in future, but we will begin with just a simple TemperatureConversion class, which performs the Celsius–Fahrenheit conversion.

Add a new class by following the procedure that was used above for the Conversion class. In this case, name the class ‘TemperatureConversion’. Now enter the following in the TemperatureConversion.h file.


#import <Cocoa/Cocoa.h>
#import "Conversion.h"

@interface TemperatureConversion : Conversion {
}
@end

and add this to the TemperatureConversion.m file:

#import "TemperatureConversion.h"

@implementation TemperatureConversion

-(NSString *)firstUnitName {
    return @"Celsius";
}

-(NSString *)secondUnitName {
    return @"Fahrenheit";
}

-(double)secondValueForFirstValue:(double)celsius {
    return (1.8 * celsius + 32.0); 
}

-(double)firstValueForSecondValue:(double)fahrenheit {
    return ((fahrenheit - 32.0) / 1.8);
}

@end

Here we have filled in all the abstract methods declared in the super class. Note that you do not have to redeclare these methods in the header. The firstUnitName and secondUnitName methods return literal NSStrings; NSString is the Cocoa class used to store strings, and an NSString literal is created using the @ symbol with double quotes.

To find out more about NSString, which is a very common Cocoa class, go to the Xcode documentation. One way to do this is to use the Help > Documentation menu item; select Cocoa in the Search Groups list on the left; and enter ‘NSString’ in the search field at the top. But a much faster way is to hold down the option key, and double click on the text NSString in the source code. This is a short cut to the NSString docs. Another useful short cut is holding down the command key, and double clicking the class name. This takes you to the header file of the class. These short cuts are very useful, and well worth internalizing. (If you need help remembering stuff like this, check out my new app Mental Case. Sorry … I couldn’t resist the plug.)

The conversion routines in TemperatureConversion use the formulas that were previously located in the UnitaryController class. secondValueForFirstValue: takes a Celsius value, and converts to Fahrenheit, and firstValueForSecondValue: performs the inverse conversion.

Who’s in Control Here?

With a model class in place, we can now rewrite the UnitaryController controller class to make use of it. First, enter the following into UnitaryController.h:


#import <Cocoa/Cocoa.h>

@class TemperatureConversion;

@interface UnitaryController : NSObject {
    IBOutlet NSTextField    *celsiusTextField;
    IBOutlet NSTextField    *fahrenheitTextField;

    TemperatureConversion   *temperatureConversion;
}

-(IBAction)convert:(id)sender;

@end

The main change here is the inclusion of the new instance variable temperatureConversion. It has the class TemperatureConversion, but we haven’t imported that class into the header. Instead, I have used the @class keyword, which is used to declare that TemperatureConversion is a class, but nothing more than that. In the header, that is all that the compiler needs to know. Why not just import the header? It is worth avoiding unnecessary imports, because using them creates compiler dependencies. If we were to import TemperatureConversion.h in the UnitaryController.h header file, whenever TemperatureConversion.h changed, UnitaryController.m and any other files importing UnitaryController.h would need to be recompiled. You can avoid this cascading of compilations by using @class as much as possible in your headers.

Now modify the UnitaryController.m implementation file as follows:


#import "UnitaryController.h"
#import "TemperatureConversion.h"

@implementation UnitaryController

-(void)awakeFromNib {
    [fahrenheitTextField setFloatValue:32.0];
    [celsiusTextField setFloatValue:0.0];

    temperatureConversion = [[TemperatureConversion alloc] init];
}

-(void)dealloc {
    [temperatureConversion release];
    [super dealloc];
}

-(IBAction)convert:(id)sender {
    double celsiusTemperature = [celsiusTextField doubleValue];
    double fahrenheitTemperature = [fahrenheitTextField doubleValue];
    if ( sender == celsiusTextField ) {
        fahrenheitTemperature = 
            [temperatureConversion secondValueForFirstValue:celsiusTemperature];
        [fahrenheitTextField setDoubleValue:fahrenheitTemperature];
    }
    else if ( sender == fahrenheitTextField ) {
        float celsiusTemperature = 
            [temperatureConversion firstValueForSecondValue:fahrenheitTemperature];
        [celsiusTextField setDoubleValue:celsiusTemperature];
    }
}

@end

Here, the TemperatureConversion.h file has been imported, because the compiler needs to know what methods it declares. And because no other files import the UnitaryController.m file, there is no danger of cascading compiler dependencies in this case.

The implementation of UnitaryController is similar to last time, except that it now uses TemperatureConversion to apply the conversion formulas. The temperatureConversion instance is initialized in the awakeFromNib method, and kept around until the UnitaryController is deallocated and dealloc is called. The convert: action calls the secondValueForFirstValue: and firstValueForSecondValue: appropriately for the conversion being performed.

You should now be able to build and run the application. Click the Build and Go toolbar item. The application should behave exactly as it did in the last tutorial, because we didn’t change any functionality this time round, we just refactored the code.

View You Next Time

The completed project source for this tutorial can be downloaded here. Next time we will modify the view to allow the user to choose between multiple conversions. If you want to practice the model development from this tutorial, why not try to write your own Conversion subclass. Pick a unit conversion that you need daily, and take a shot at it, basing your solution on TemperatureConversion. That way, you will not only learn how to write models, but next time you will be able to include your conversion in the view.

An excellent post from Drew McCormack.

Advertisements
This entry was posted in General and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s