[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Working with Objects


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1 Selectors

A principal OO addition to Objective-C is the syntax to send messages to objects:

 
[myArray removeObjectIdenticalTo: anObject];

The example sends the message named removeObjectIdenticalTo: to myArray with the argument anObject.

Instead of using this syntax to send a message, the selector of a message may be used. The selector is a way of referring to an abstract message, it is a representation of the message name without the argument or the receiver.

Objective-C introduces a new data type: SEL for variables that hold selectors. A SEL variable is implemented as a pointer to a structure containing the name (and often incidental information about the return type and argument types) of the selector.

You can obtain a selector in a variety of ways -

Sending messages to objects, and invoking methods that are named by the message selector can be achieved using performSelector: and related methods:

 
[receiver performSelector: mySelector];
[receiver performSelector: mySelector withObject: arg1];
[receiver performSelector: mySelector withObject: arg1 
  withObject: arg2];

The GNU Objective-C runtime library provides a function sel_eq() which may be used to test for selector equality. This is necessary because, while the compiler tries to ensure that compile-time generated references to selectors for a particular message point to the same structure, selectors produced at runtime, or in different compilation units, will be different and a simple pointer equality test will not do.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.1 Target-Action concept

The Target-Action paradigm is used a great deal in the GNUstep GUI, but infrequently in other code, it is most applicable when building a network of related objects that can interact relatively simply.

The idea is that an object may have a target ... an instance variable of type id which refers to a target object, and an action ... an instance variable of type SEL.

When the object is told to perform its action, it asks the target to perform the action, passing itsself as the argument.

Neither object needs to know anything about the other object at compile time, so such objects can be connected together in a running program to build new behaviors into applications.

The code to implement this paradigm is simple -

 
- (id) performAction
{
  if (action == 0)
    {
      return nil;	// No action set ... do nothing
    }
  if (target == nil)
    {
      return nil;	// No target set ... do nothing
    }
  if ([target respondsToSelector: action] == NO)
    {
      return nil;	// Target cannot deal with action ... do nothing
    }
  return [target performSelector: action withObject: self];
}

As an example, consider a character attempting to make use of an object they have found while they are standing in front of a door -

 
  [obj setAction: @selector(openWith:)];
  [obj setTarget: theDoor];
  if ([obj performAction] == nil)
    {
      // Door didn't open.
    }
  else
   {
    // Door opened.
   }

The door object will be sent the openWith: message with the new object as its argument. If obj happens to be a key, the door may decide to open. It may also open in response to an object that happens to be a battering ram. The same fragment of code would handle both cases.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1.2 Avoiding Messaging Errors with respondsToSelector:

Using typed objects as shown below, the compiler would forewarn you if the anObject was unable to respond to the alert: message, as it knows what type of object anObject is:

 
AnObject	*anObject;		// an instance of AnObject class

anObject = [[AnObject alloc] init];	// build and initialize the object
[anObject alert: additionalObject];	// send it a message.

However at times the compiler will not forewarn you that a message will attempt to invoke a method that is not in the receiver's repertoire. For instance, consider the code below where anObject is not known to implement the alert: message:

 
  id		anObject;		// arbitrary object;

  anObject = [[AnObject alloc] init];	// build and initialize object
  [anObject alert: additionalObject];	// send it a message.

In this case, the compiler will not issue a warning, because it only knows that anObject is of type id ... so it doesn't know what methods the object implements.

At runtime, the Objective-C runtime library will fail to find a method implementation for the alert: message in the AnObject class, so it will send a forwardInvocation: message to anObject instead. The default implementation of the forwardInvocation: in the NSObject class will then raise a runtime exception which, if uncaught, will cause the program to crash.

In order to avoid this sort of problem, your code can use the respondsToSelector: method to see if an object can handle a particular message -

 
  id		anObject;

  anObject = [[AnObject alloc] init];
  if ([anObject respondsToSelector: @selector(alert:)] == YES)
    {
      [anObject alert: additionalObject];	// send it a message.
    }
  else
    {
      // Do something else if the object can't be alerted
    }

Note. Protocols may be used to handle unrecogized message checking at compile time.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2 Initializing and Allocating Objects

The NSObject class defines two allocation methods namely alloc and allocWithZone:.

 
+ (id) alloc;
+ (id) allocWithZone: (NSZone*)zone;

Both methods will allocate memory to hold an object, and initialise the objects' isa pointer, which as previously discussed defines the class to which the object belongs. The same initialization procedure sets all remaining instance variables to 0.

In practice further initialization procedures are implemented by instance methods beginning with the familiar init... syntax, and an object is not considered initialised until one of these methods has been executed (the exception being in the case of copied objects).

Note. Classes must provide init methods to initialize their declared instance variables.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2.1 init and Returned Objects

After initializing the instance variables of the receiver, an init... method returns a usable object, which might not be the receiver, however.

Here is an scenario where the returned object is not the receiver: the class NSConnection only permits one connection to exist between any two ports, so if you call initWithReceivePort:sendPort: when a connection for the ports exists, the method will deallocate the newly allocated instance, and return the current conflicting object, rather than the receiver.

The init... method may also return nil at times when it is passed an argument that does not compute; for example the argument to the NSString method initWithContentsOfFile: may be an erroneous file name.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3 Protocols

We use tweo types of protocol in Objective-C ... informal protocols, where we document methods to which objects will respond, and specify how they should behave, and formal protocols, where we provide a list of methods that an object will support in a format where the compiler can check things, and the runtime can also check that an object conforms to the protocol. Informal protocols are merely convention, but are useful where we want to say that some system will work as long as it (or its delegate) implements some subset of a group of methods. Formal protocols are more use when we want the compiler or runtime to check that an object implements all of a group of methods itsself.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4 Memory Management

There are three forms of memory management available in Objective-C

The recommended approach is to use some standard macros defined in NSObject.h which encapsulate the retain/release/autorelease mechanism, but which permit efficient use of the garbage collection system if you build your software with that.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.5 How Messaging Works

Objective-C messaging requires three types of information:

When you send a message like obj = [array objectAtIndex: i];, the compiler actually does several things for you -

When a method is executed, it therefore has the message arguments available to it on the stack, but also has two additional values - the receiver and the selector. These additional hidden arguments are referred to in the source code by the names self and _cmd.

The process of looking up the method implementation in the receiver at runtime is known as dynamic binding. This is part of what makes the language powerful and flexible, but it is inevitably (despite clever caching strategies used in the runtime library) a little slower than a simple function call in C.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.6 Hacking for Efficiency


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7 NSString

The NSString class defines objects holding raw Unicode character streams or strings. Unicode is a 16bit worldwide standard used to define character sets for all spoken languages. In GNUstep parlance the Unicode character is of type unichar.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7.1 Creating NSString Static Instances

A static instance is allocated at compile time. The creation of a static instance of NSString is achieved using the @"..." construct and a pointer:

 
NSString *wasp = @"Brainstorm";
wasp is a variable that refers to an NSString object representing the ASCII string "Brainstorm".


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7.2 NSString - +stringWithFormat:

The class method stringWithFormat: may also be used to create instances of NSString, and broadly echoes the printf function in the C programming language. stringWithFormat: accepts a list of arguments whose processed result is placed in an NSString that becomes a return value as illustrated below:

 
int		qos = 5;
NSString	*gprschannel;

gprschannel = [NSString stringWithFormat: @"The GPRS channel is %d", 
                qos];

The example will produce an NSString called gprschannel holding the string "The gprschannel is 5".

stringWithFormat: recognises the %@ conversion specification that is used to specify an additional NSString:

 
NSString *one;
NSString *two;

one = @"Brainstorm";
two = [NSString stringWithFormat: @"Our trading name is %@", one];

The example assigns the variable two the string "Our trading name is Brainstorm." The %@ specification can be used to output an object's description - as returned by the NSObject's -description), which is useful when debugging, as in:

 
NSObject *obj = [anObject aMethod];

NSLog (@"The method returned: %@", obj);


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7.3 C String Conversion

When a program needs to call a C library function it is useful to create an NSString from a standard ASCII C string (not fixed at compile time):
 
char *function (void);
To create an NSString using the contents of the returned C string (from the above example), use the NSString class method stringWithCString::

 
char *result;
NSString *string;

result = function ();
string = [NSString stringWithCString: result];

To convert an NSString to a standard C ASCII string, use the cString method of the NSString class:

 
char		*result;
NSString	*string;

string = @"Hi!";
result = [string cString];


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7.4 NSMutableString

NSStrings are immutable objects; meaning that once they are created, they cannot be modified. This results in optimised NSString code. To modify a string, use the subclass of NSString, called NSMutableString. Use a NSMutableString wherever a NSString could be used.

An NSMutableString responds to methods that modify the string directly - which is not possible with a generic NSString. To create a NSMutableStringuse stringWithFormat::

 
NSString *name = @"Brainstorm";
NSMutableString *str;
str = [NSMutableString stringWithFormat: @"Hi!, %@", name];

While NSString's implementation of stringWithFormat: returns a NSString, NSMutableString's implementation returns an NSMutableString.

Note. Static strings created with the @"..." construct are always immutable.

NSMutableStrings are rarely used because to modify a string, you normally create a new string derived from an existing one.

A useful method of the NSMutableString class is appendString:, which takes an NSString argument, and appends it to the receiver:

 
NSString	*name = @"Brainstorm";
NSString	*greeting = @"Hello";
NSMutableString	*s;

s = AUTORELEASE ([NSMutableString new]);
[s appendString: greeting];
[s appendString: @", "];
[s appendString: name];

This code produces the same result as:

 
NSString *name = @"Brainstorm";
NSString *greeting = @"Hello";
NSMutableString *s;

s = [NSMutableString stringWithFormat: @"%@, %@", greeting, name];


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.7.5 Reading and Saving Strings

The the GNUstep base library has numerous string manipulation features, and among their most notable are those relating to writing/reading strings to/from files. To write the contents of a string to a file, use the writeToFile:atomically: method:

 
#include <Foundation/Foundation.h>

int
main (void)
{
  CREATE_AUTORELEASE_POOL(pool);
  NSString *name = @"This string was created by GNUstep";

  if ([name writeToFile: @"/home/nico/testing" atomically: YES])
    {
      NSLog (@"Success");
    }
  else 
    {
      NSLog (@"Failure");
    }
  RELEASE(pool);
  return 0;
}

writeToFile:atomically: returns YES for success, and NO for failure. If the atomically flag is YES, then the library first writes the string into a file with a temporary name, and, when the writing has been successfully done, renames the file to the specified filename. This prevents erasing the previous version of filename unless writing has been successful. This is a useful feature, which should be enabled.

To read the contents of a file into a string, use stringWithContentsOfFile:, as shown in the following example that reads @"/home/Brainstorm/test":

 
#include <Foundation/Foundation.h>

int
main (void)
{
  CREATE_AUTORELEASE_POOL(pool);
  NSString *string;
  NSString *filename = @"/home/nico/test";

  string = [NSString stringWithContentsOfFile: filename];
  if (string == nil)
    {
      NSLog (@"Problem reading file %@", filename);
      /*
       * <missing code: do something to manage the error...>
       * <exit perhaps ?>
       */
    }

  /*
   * <missing code: do something with string...>
   */

  RELEASE(pool);
  return 0;
}


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by root on July, 28 2004 using texi2html