Objective-C

An introduction to object oriented programming concepts is also available.

Interface

In Objective-C, we describe the interface to a class using the @interface declaration:

@interface Stack : Object

followed by the variables used to implement the object (these are called the class's instance variables, and each instance of this class has its own copy of these variables):

{
  StackLink *top;
  unsigned int size;
}

The we list the methods that this class implements (i.e. the messages that objects of this class understand):

- free;

- push: (int) anInt;

- (int) pop;

- (unsigned int) size;

and finish up with the @end declaration:

@end

This would normally be stored in a header file called Stack.h. The definitions for the superclass of the class you are subclassing are imported at the beginning of the file. An import uses the #import directive which is similar to the #include directive, but ensures that the file is only included by your file once. With all of this in place the file would look like this:

Stack.h

#import < objc/Object.h>


@interface Stack : Object
{
  StackLink *top;
  unsigned int size;
}

- free;
- push: (int) anInt;
- (int) pop;
- (unsigned int) size;

@end

Note that in the @interface declaration, we specify the class that this class inherits from (its superclass). The superclass of the Stack class is the Object class (all classes are at least a subclass of the Object class).

Method names always contain a colon (":") before any of the arguments that are passed along in the message (e.g. push).

Any number of parameters may be sent in a message, each separated by a keyword ending in a colon. For example, if we wanted to be able to push two integers onto the stack with one message, we could define the method:

- push: (int) first and: (int) second;

The name of this method is "push:and:".

Parameters passed in the message are declared using the C "typecast" notation. Any type that is valid as a C function parameter is valid as a method parameter. The return type of a method is also specified using the typecast notation. If no return type is specified, then the method is assumed to return a pointer to an object.

Implementation

We define the implementation of a class (in a separate file from the classes, create instances of objects, and send messages to objects. Two new fundamental types are added to the language:

id
pointer to an object
SEL
a message (we sometimes call messages selectors, thus the abbreviation).

Both variables of type id and type SEL are valid parameters that can be send in messages or passed to a C function.

Messages are sent using a Smalltalk-like syntax:

id s;
int i;

s = [Stack new];  // Send the message "new" to the factory object "Stack"
[s push:34];      // Send the message "push" with the argument 34 to s.
i = [s pop];      // Send the message "pop" to the object s.

In the implementation of a class, methods can manipulate the class' instance variables (e.g. the linked list pointed to by top in the Stack class), can generally do anything normally allowed in C, and can send messages to objects.

What objects can an object send messages to?

How does an object send a message to itself? Here is an example:

[self push:34.0];

self is a special variable which is a pointer to the object which received the message which invoked the currently executing method(!). In other words, it is the receiver of the message.

Example: Remember push:and:? Here is probably how that method would be implemented:

- push: (int) first and: (int) second
{
  [self push: first];
  [self push: second];
  return self;
}

Notice that the push:and: method returns self. Remember that if a method does not specify the type of its return value, the default is to return a pointer to an object (i.e. something of type id). This means that, barring any other sensible thing to return, methods should always return self!

How can an object send a message to itself but use its superclass' implementation? And why would someone ever want to do that?

Why? Because in the implementation of a method which is overridden (i.e. the superclass implements it, and the subclass implements it differently, a class might want (and often does want)to perform its superclass' implementation as part of its own implementation.

How? Any message an object sends to the pseudo-variable super will cause its superclass' implementation of the method to be performed.

An object can send a message to super in any method. super implicitly means " self, but use superclass' implementation."

There is one and only one factory object per class. There is a global id variable which points to it, and that variable's name is the same as the class' name. You send messages to it to create new instances of that class or to query class-specific (as opposed to instance-specific) information.

Example:

id s;

s = [Stack new];

In this case, we are sending the message new to the factory object Stack. This factory object will return an object of type Stack.

The file which contains the implementation of a class ends with the @end directive (just like the interface file does).

Stack.m

@implementation Stack

- free
{
  StackLink *next;

  while (top != (StackLink *) 0)
    {
      next = top->next;
      free ((char *) top);
      top = next;
    }
  return [super free];
}

/* other methods */

@end

Factory Objects

Objective-C automatically creates a factory object for each class used in an application.

The primary purpose of a factory object is to provide a mechanism to create instances of the class:

id myStack

myStack = [Stack new];

Factory objects respond to factory methods.

Factory methods are indicated by a "+" preceding the name when declared and defined.

+ new
{
  self = [super new];
  top = (StackLink *) 0;
  return self;
}

Factory objects are not instances of the class and therefore do not have access to the instance variables associated with an instance of the class, so factory methods typically redefine self before accessing instances variables.

Here are the interface and implementation files for the Stack class in their entirety:

Stack.h

#import < objc/Object.h>


@interface Stack : Object
{
  StackLink *top;
  unsigned int size;
}

- free;
- push: (int) anInt;
- (int) pop;
- (unsigned int) size;

@end

Stack.m

#import "Stack.h"

@implementation Stack

#define NULL_LINK (StackLink *) 0

+ new
{
  self = [super new];
  top = (StackLink *) 0;
  return self;
}

- free
{
  StackLink *next;

  while (top != NULL_LINK)
    {
      next = top->next;
      free ((char *) top);
      top = next;
    }
  return [super free];
}

- push: (int) value
{
  StackLink *newLink;

  newLink = (StackLink *) malloc (sizeof (StackLink));
  if (newLink == 0)
    {
      fprintf(stderr, "Out of memory\n");
      return nil;
    }
  newLink->data = value;
  newLink->next = top;
  top = newLink;
  size++;

  return self;
}

- (int) pop
{
  int value;
  StackLink *topLink;

  if (0 != size)
    {
      topLink = top;
      top = top->next;
      value = topLink->data;
      free (topLink);
      size--;
    }
  else
    {
      value = 0;
    }
  return value;
}

- (unsigned int) size
{
  return size;
}

@end