[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 -
@selector()
directive -
SEL mySelector = @selector(removeObjectIdenticalTo:); |
NSSelectorFromString()
function -
SEL mySelector = NSSelectorFromString(&ruml;emoveObjectIdenticalTo:"); |
_cmd
which contains
the selector for the current method -
- (void) removeObjectIdenticalTo: (id)anObject { SEL mySelector = _cmd; // Method implementation here ... } |
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]; |
receiver
- the object receiving the message.
mySelector
- the selector of the method to be performed.
arg1
arg2
- arguments sent with message.
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
init
and Returned Objects 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] | [ ? ] |
Typically memory is allocated in zones, and most memory is
allocated from the default area
(which is returned by the NSDefaultMallocZone()
) function.
When it is necessary to group objects in the same area of memory -
perhaps for performance reasons, you may create a zone from where you
would allocate those objects. This will minimise the paging required by
your application when accessing those objects frequently.
Low level memory allocation is performed by:
NSAllocateObject()
NSDeallocateObject()
These are rarely used but are available when you require more
advanced control or performance. These functions are called by
[NSObject +allocWithZone:]
and [NSObject -dealloc]
.
If you call NSAllocateObject()
directly to create an instance
of a class, you may break some functionality of that class,
such as caching of frequently used objects.
Objects are destroyed using dealloc
, and are created using:
+alloc
-copy
-mutableCopy
The allocation methods are covers for the following more versatile methods:
+allocWithZone:
-copyWithZone:
-mutableCopyWithZone:
These methods may specify zones from which the memory is allocated,
rather than use the default zone. NSObject also provides +new
,
which is simply a cover for the combination of +alloc
and -init
.
The -dealloc
method returns the memory occupied by the object
to the zone from which it was originally allocated; it can use the -zone
method to determine which zone this is.
Explicit memory allocation and deallocation is efficient - but when you pass objects around inside a program (and especially from/to libraries etc.,) it quickly becomes difficult and/or inefficient to keep track of who owns an object, and who should be responsible for calling its deallocation method.
The OpenStep specification remedies this problem by providing a reference counting mechanism along with a set of conventions that make memory management easy.
Additionally, the GNU Objective-C compiler and the GNUstep system provide a memory sweeping garbage collection mechanism using the Boehm conservative garbage collection library.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The dealloc
method is defined in the NSObject class and
releases memory allocated to the receiver.
The NSObject
implementation of the method deallocates
only instance variables. Additional allocated, unshared memory
used by the object is deallocated separately.
Other entities that depend solely on the deallocated receiver,
including complete objects, must also be deallocated separately.
In this instance subclasses of NSObject
override
the dealloc
method. Every class that has its objects allocate
additional memory must have its own dealloc
method.
Each version of dealloc ends with a message to super to
perform an inherited version of the method, as illustrated in
the following example:
- (void) dealloc { RELEASE(anObject); NSZoneFree(myZone, myMemory); [super dealloc]; } |
(See Section Memory Deallocation.)
+alloc
method is not guaranteed to return an object
created using NSAllocateObject()
. It may actually return
any object, though convention dictates that the return value is
an object with its isa
instance variable set to point to
a class which is a subclass of that to which the alloc
message was sent, and which can respond to the initialisation
methods of that class.
@implementation Crown - (id) initWithLevel: (CharacterLevel)level { if (level > wizardLevel && [crownOfTheEmperor isOwned] == NO) { RELEASE(self); [crownOfTheEmperor setOwned: YES]; self = RETAIN(crownOfTheEmperor); } else { self = [super init]; if (self != nil) { // Perform standard initialisation here. } } return self; } @end |
In the above example, the initWithLevel:
method is used to
create a crown discovered in a random treasure store. If certain
conditions are met, the system returns a special object ... only
one of which may exist and be owned by a character.
In this case, the initialiser for a crown expects a character level as an argument, and the old crown object is released and replaced by a special one.
self
before initialising instance variables and returning
self
.
This is necessary because, unlike in C++ or Java, the Objective-C language does not enforce initialisation ... so it's up to you as the programmer to ensure that superclasses are permitted to perform their object initialisation code before the subclass performs its initialisation (actually, this gives you the flexibility to change how things are initialised ... but you should obey the conventions unless you are sure you know what you are doing).
An initialiser of any class intended for re-use/subclassing should
therefore not replace self
with an object of a class that
is not a subclass of the original (though it is legal to return nil,
and you should therefore check for that in your code).
This rule is essential because, if the initialiser of the original subclass receives another object from the superclass initialiser, the new object needs to have enough memory allocated, and the same initial instance variable layout, as the original object, or assigning values into it may overwrite memory that's not part of the initialised object ... causing a crash or other runtime problems.
So, to correct our example, we should only return the special object if it is a subclass of the original object ...
if (level > wizardLevel && [crownOfTheEmperor isOwned] == NO && [crownOfTheEmperor isKindOfClass: [self class]] == YES) { RELEASE(self); [crownOfTheEmperor setOwned: YES]; self = RETAIN(crownOfTheEmperor); } |
The isKindOfClass:
method is used to test that the object we
are intending to return is legitimate ... a subclass of whatever
class the original item (self
) allocated was.
Since the init
method is defined in the root class
(NSObject
), it's quite likely that any class could be initialised
by calling this method. If init
is not the designated initialiser
for your class, you should therefore write an implementation of
init
which does call the designated initialiser ...
- (id) init { self = [self initWithLevel: noviceLevel]; return self; } |
+new
method provided by the
NSObject
class. This is simply an alloc
followed
by an init
.
More sophisticated methods combine allocation, initialisation, and autoreleasing of objects. These methods return objects which have been added to the current autorelease pool (see later in this chapter for details). By convention, these methods have names beginning with something like the name of the class ...
+ (Crown*) crown { return AUTORELEASE([[self alloc] init]); } + (Crown*) crownWithLevel: (CharacterLevel)level { return AUTORELEASE([[self alloc] initWithLevel: level]); } |
dealloc
method. The last thing that this method needs to do is call the
dealloc
method of the superclass ... since it is normally
the deallocation method of the root class that actually frees the
memory used by the object.
The dealloc
method is analogous to the destructor in a C++
class but, as with initialisation, the Objective-C language does
not call this method for you, and you must explicitly call it
(or refrain from calling it perhaps if you are caching the object
for latert re-use). Often, if you have not allocated memory or
olther objects as instance variables in your object, you don't
have to write a dealloc
method, as the method in the superclass
will do.
@implementation Crown - (void) dealloc { if (self == crownOfTheEmperor) { /* * The crown of the emperor can not be destroyed ... but it * can be made available for someone else to find. */ [crownOfTheEmperor setOwned: NO]; } else { // Release instance variables here. [super dealloc]; } } @end |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
A particularly important use of protocols is in defining the methods that an object in a remote process can respond to ... by setting the protocol used by a local proxy object, you can avoid having to send messages to the remote process to check what methods are available - you can simply check the local protocol object.
A protocol is declared as a series of method declarations, just like
a class interface. The difference is that a protocol declaration
begins with @protocol
rather than @class
, and has
an optional super protocol specified in angle brackets.
Another difference is that within a protocol declaration, certain additional type qualifiers used for Distributed Objects are valid -
@protocol GameServer - (oneway void) handleClient: (id)client event: (in bycopy NSData*) event; - (BOOL) registerClient: (id)client byName: (in bycopy NSString*) name; @end @protocol MasterServer <GameServer> - (oneway void) systemShutdownBy: (id)client; @end |
An object is said to conform to a protocol if it implements all
the methods listed in that protocol. The NSObject
class provides
a method for runtime testing to see if an object conforms to a protocol -
if ([anObject conformsToProtocol: aProtocol] == YES) { // We can go ahead and use the object. } else { NSLog(@"Object of class %@ ignored ... does not conform to protocol", NSStringFromClass([anObject class])); } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There are three forms of memory management available in Objective-C
alloc
, copy
etc, and deallocate
them when you have finished with them (using dealloc
).
This gives you complete control over memory management, and is highly
efficient, but error prone.
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.
Coin *c = [[Coin alloc] initWithValue: 10]; // Put coin in pouch, RETAIN(c); // Calls 'retain' method (retain count now 2) // Remove coin from pouch RELEASE(c); // Calls 'release' method (retain count now 1) // Drop in bottomless well RELEASE(c); // Calls 'release' ... (retain count 0) then 'dealloc' |
AUTORELEASE
macro to call the autorelease
method, which adds an object to
the current autorelease pool. An autorelease pool simply maintains
a reference to each object added to it, and for each addition, the
autorelease pool will call the release
method of the object
when the pool is released. So doing an AUTORELEASE
is just
the same as doing a RELEASE
, but deferred until the current
autorelease pool is deallocated.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
isa
, an instance
variable of type Class
, which points to a structure containing
links into the class hierarchy, information specifying instance variable
layout for the object, and a dispatch table specifying what code should
be executed for each supported message.
When you send a message like obj = [array objectAtIndex: i];
, the
compiler actually does several things for you -
array
) on the stack.
objectAtIndex:
and generates code to put it on the stack.
i
) on the stack.
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] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
NSString
from a standard ASCII C string (not fixed at
compile time):
char *function (void); |
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] | [ ? ] |
NSString
s 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 NSMutableString
use 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] | [ ? ] |
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] | [ ? ] |