Version 15 (modified by mikel, 6 years ago) (diff)


Writing the Lisp Code

In this section we'll write Lisp code that duplicates the features provided by the Objective C code in Apple's tutorial. In Apple's tutorial, the explanation of the Objective C code begins with the section  Bridging the Model and View: The Controller.

The Lisp code in this section of the HOWTO is considerably simpler than the corresponding Objective C code, in part because we can ignore the conventions that XCode uses for laying out source files. We can just write all our definitions into a single Lisp source file, and load that file into Clozure CL when we are ready to build the application. The Lisp code is also a little more compact than the corresponding Objective-C.

First Things First

Place the following line at the top of your Lisp source file:

(in-package "CCL")

OpenMCL's Objective-C bridge code is defined in the "CCL" package. Usually, when building an application, you'll create a package for that application and import the definitions you need to use. In order to keep the discussion short in this simple example, we just place all our definitions in the "CCL" package.

Defining the Converter Class

We begin by defining the Converter class. Recall from Apple's tutorial that this is the Model class that implements the conversion between dollars and other currencies. Here is the Lisp definition that implements the class you created in InterfaceBuilder:

(defclass converter (ns:ns-object)
  (:metaclass ns:+ns-object))

This is an ordinary CLOS class definition, with a couple of simple wrinkles. First, the superclass it inherits from is the NS-OBJECT class in the "NS" package. NS-OBJECT is an Objective-C class, the ancestor of all Objective-C objects. This CLOS definition creates a new Objective-C class named "Converter".

We tell OpenMCL how to build the right kind of class object by including the :METACLASS option in the definition:

(:metaclass ns:+ns-object)

The Objective-C bridge knows that when the metaclass is ns:+ns-object, it must lay out the class object in memory as an Objective C class, rather than a normal CLOS STANDARD-CLASS.

Next, we define the method "convertCurrency:atRate:":

(objc:defmethod (#/convertCurrency:atRate: :float) 
    ((self converter) (currency :float) (rate :float))
  (* currency rate))

This is the method that actually does the currency conversion. It's very simple--really, it just multiples currency times rate. Most of the text in the definition is Objective C bridge code that links the definition to the right class with the right argument and return types.

objc:defmethod is a version of DEFMETHOD that creates Objective C method definitions.

The syntax #/convertCurrency:atRate: uses the "#/" reader macro to read a symbol with case preserved, so that you can see in your code the same name that Objective-C uses for the method, without worrying about how the name might be converted between Lisp and Objective-C naming conventions.

The number of arguments to an Objective-C method is the number of colons in the name, plus one. Each colon indicates an argument, and there is always an extra "self" argument that refers to the object that receives the message. The "self" argument is implicit in Objective-C definitions, but it's just another argument in the Lisp code.

We indicate the return type and the types of arguments in the method definition by surrounding parameters and the method name with parentheses, and appending the type name. Thus, for example,

(#/convertCurrency:atRate: :float)

means that the return type of the method is :FLOAT, and

(self converter)

means that the type of the object that receives the convertCurrency:atRate: message is Converter.

You'll see these same conventions repeated in the next section.

Defining the ConverterController Class

The previous section defined the Model class, Converter. All we need now is a definition for the ConverterController class. Recall from your reading of Apple's Tutorial that the CurrencyConverter example uses the Model-View-Controller paradigm. You used InterfaceBuilder to construct the application's views. The Converter class provides the model that represents application data. Now we define the controller class, ConverterController, which connects the View and the Model.

Here's the Lisp definition of the ConverterController? class:

(defclass converter-controller (ns:ns-object)
  ((amount-field :foreign-type :id :accessor amount-field)
   (converter :foreign-type :id :accessor converter)
   (dollar-field :foreign-type :id :accessor dollar-field)
   (rate-field :foreign-type :id :accessor rate-field))
  (:metaclass ns:+ns-object))

Once again we use the Objective-C bridge to define an Objective-C class. This time, we provide several instance-variable definitions in the class. We also explicitly define the names of accessor functions for each of them. The :FOREIGN-TYPE initargs enable us to specify the types of the fields in the foreign (Objective-C) class.

Each field in the definition of the ConverterController class is an outlet that will be used to store a reference to one of the UI fields that you created in InterfaceBuilder. For example, amount-field will be connected to the "Amount" text field.

Why did we spell the name "amount-field" in Lisp code, and "amountField" when creating the outlet in InterfaceBuilder? The Objective-C bridge automatically converts Lisp-style field names (like "amount-field") to Objective C-style field names (like "amountField"), when handling class definitions.

The converter field at launch time contains a reference to the Converter object, whose class definition is in the previous section.

The final piece of the implementation is a definition of the "convert:" method. This is the method that is called when a user clicks the "Convert" button in the user interface.

(objc:defmethod #/convert: ((self converter-controller) sender)
  (let* ((conv (converter self))
         (dollar-field (dollar-field self))
         (rate-field (rate-field self))
         (amount-field (amount-field self))
         (dollars (#/floatValue dollar-field))
         (rate (#/floatValue rate-field))
         (amount (#/convertCurrency:atRate: conv dollars rate)))
    (#/setFloatValue: amount-field amount)
    (#/selectText: rate-field self)))

Start Previous