Maturity Index:Experimental
The Portable Object Compiler uses blocks for exception handling i.e., the processing of exceptional conditions that interrupt the normal flow of program execution, such as out of memory, division by zero etc.
Blocks are a non-standard part of the Objective-C language; this implementation, the first and most powerful as far as we know, is based on Brad Cox's TaskMaster paper where the same concept is discussed under the name of Action Expressions. The syntax is slightly different (we don't use a semi-colon to separate arguments, but use prototype-style comma separated, argument declaration). In addition, the semantics of this Block implementation is more powerful than (in fact, a generalization of) the one described in Taskmaster, and incorporates additional ideas from K.Lerman and B.Cox.
Blocks are,
The braced group in the message expression above, is a Block with one argument. Note that arguments are separated by a vertical bar from the Block statements, like in Smalltalk. The Collection do: method will evaluate the block for each element x in the Collection.[ aCollection do : {id x | [x print];} ];
Blocks instance methods can be used as control flow statements:
Or they can be used to specified what should be done in some special case:[ [x isOrange ] ifTrue: {[ citrus add:x ];} ];
Blocks are generally useful, as examples for defining menus, handling exceptions or defining threads (lightweight tasks) show.[ aCollection find:myObject ifAbsent: { [aCollection add:myObject]; }];
[ aMenu str:"Open" action: {id x | [x doOpenFile]; } ]; [ { [x foo]; } ifException: {id e | [e raise]; } ]; [ { execl("/bin/sh","ls",0); } fork ];
The compiler promotes stack variables to heap-allocated variables, when they are referenced from within a Block. Modifying these variables inside the block is allowed even when the function that creates the Blocks exits. Global variables, and instance variables, are also (trivially) shared between the Block and the function in which it is created.
In the above example, all statements in the Block are legal. Note that the Block can refer to aLocal, just as a compound statement inside a function or method implementation can. In addition, the Block is allowed to assign a value to the local, and this value is shared with the calling method.- aMethod { id aLocal = [Object new]; [ { [aLocal doIt]; aLocal = nil; [self doIt]; [anInstanceVariable doIt]; [aGlobal doIt]; } value]; return aLocal; }
When a Block is instantianted in a method, it can also reference instance variables. Indeed, in Objective-C, objects are always heap-allocated, and when the Block is instantiated, the self pointer is passed to the Block. The Block can then access instance variables via the self pointer. It doesn't matter if the method in which the Block was created, meanwhile exited or not : the self pointer points to an object in the heap.
It is still possible to assign the value of the Block to a variable, by sending it the self message (much like Class objects can only be assigned to a variable in a indirect way).
Particulary useful are Blocks that consists of just a single expression. Those Blocks have an implicit return value that is equal to the expression :
Note that there's no semi-colon following the idEqual: message expression.[ aCollection detect: { id e | [e idEqual:anObject] } ];
The exception can be caught by an enclosing ifException: method. The receiver of this method is a block that is to be evaluated (and which might raise an exception). The argument block is the exception handler : the handler is a block that is to be evaluated by the raise method before it performs a long-jump to the ifException: message expression to resume normal execution at that location.- bar:anObject { if (!anObject) { [{printf("'anObject' must-be non-nil\n");} raise]; } }
The result of these lines of code is, to send a bar: message (much as evaluating the receiver of ifException: with value would do) and, if an exception would occur inside bar:, the handler is executed (in this case, it prints a message). After evaluating the handler, the calling stack is unwound and the method ifException: returns : the raise method jumps to the enclosing ifException: method and program execution continues at that point.[{ /* code that may raise an exception */ [self bar:anObject]; } ifException:{ /* code to be executed before longjump to this place */ printf("caught exception\n"); }];
Because a handler is executed as a subroutine of the routine that performs the raise method, a debugger can be used to inspect where an exception was raised.
If no handler would have been specified, the default handler would have been executed, which in this case prints an error message and aborts the process.
Exceptions can be nested and handlers can reraise the exception (by sending again a raise message to the default handler), passing control to the next enclosing handler, until only the default handler remains.
Note: It's possible to associate a small integer, a tag, to a Block. This can be used to switch, in the handler, on the kind of exception that was raised. See the methods setTag: and tag for more information.
The block itself is then created by using the newBlock function :static id foo(id anObject) { return anObject; }
The compiler has an option -noBlocks that can be used to turn off the special syntax for Blocks.id aBlock = newBlock(1,foo,NULL);
+ initializeAllocates two instances of Object that can be referred to as idTrue and idFalse and that serve for such methods as ifTrue:, whileTrue: etc. as true and false booleans.
The resume message does NOT evaluate the default handler. It simply resumes execution at the location where the default handler would have been executed. This gives the handler that sends the resume message a chance to remove the cause of the exception. For instance, an out of memory handler, might free some memory and then resume.
tag
- (int) tag
Returns the tag of the Block. Can be used in an exception handler to switch on the kind of exception that was raised :
switch([aBlock tag]) {
case MY_EXCEPTION : return nil;
default : return [aBlock raise];
}
setTag:
- setTag :(int) aTag
Sets the tag of the Block to aTag and returns the Block itself. Can be used to tag a block that is used as default handler in exception handling.
[[myBlock setTag:MY_EXCEPTION] raise];
raise
- raise
Evaluates the receiving block (the default handler) and then aborts the process, unless the exception raised by this method is caught by an enclosing ifException: method. In the latter case, this method will evaluate the exception handler block of the ifException: message, passing the default handler as argument, and then jump towards the enclosing ifException: statement and resume program execution at that location.
ifException:
- ifException : aHandler
Evaluates the receiver of the message and returns its return value. If an exception is raised, evaluates aHandler and immediately returns nil.
resume
- resume
On some occasions, it might be interesting to resume program execution at the location where the program raised an exception. An exception handler might send a resume message to the default handler (argument of the exception handler) and thereby preventing the raise method to perform a longjump.
ifOutOfMemory:
- ifOutOfMemory : aBlock
Like ifException: but only catches out of memory exceptions.
ifClassNotFound:
- ifClassNotFound : aBlock
Like ifException: but only catches class not found exceptions.
value
- value
Evaluates the receiver of the message and returns its return value.
atExit
- atExit
Evaluates the receiver of the message when the process exits. See the ANSI C function atexit() for more details. There's a maximum of 32 exit Blocks that can be registered to be automatically called on exit. Blocks are evaluated in reverse order of their registration.
value:
- value : anObject
Evaluates, with anObject as argument, the receiver of the message and returns its return value.
value:with:
- value : firstObject with : secondObject
Evaluates the receiver of the message with two arguments and returns its return value.
idTrue
+ idTrue
Returns an Object that is guarantueed to be isTrue.
idFalse
+ idFalse
Returns an Object that is guarantueed to be isFalse.
repeatTimes:
- repeatTimes :(int) n
Method to evaluate the receiver Block n times. Similar to the Smalltalk method timesRepeat: but with argument and receiver interchanged. Returns self.
isFalse
- (BOOL) isFalse
isTrue
- (BOOL) isTrue
whileTrue:
- whileTrue : aBlock
Method to repeatedly evaluate aBlock, while the receiver Block evaluates to something that isTrue.
whileFalse:
- whileFalse : aBlock
Method to repeatedly evaluate aBlock, while the receiver Block evaluates to something that isFalse.