Block Specification Sheet


Portable Object Compiler (c) 97,98 by Stes & Lerman. All Rights Reserved.

Block

Inherits from:Object

Maturity Index:Experimental

Class Description

Objective-C Blocks are similar to block expressions in Smalltalk. Blocks are a way to express deferred computations; computations to be written at one place but invoked from another.

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,

Examples

Classical examples of what we can do, or plan to do, with Blocks are a computation that a collection is to perform on each of its members.

[ aCollection do : {id x | [x print];} ];

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.

Blocks instance methods can be used as control flow statements:

[ [x isOrange ] ifTrue: {[ citrus add:x ];} ];

Or they can be used to specified what should be done in some special case:

[ aCollection find:myObject ifAbsent: {
    [aCollection add:myObject];
}];

Blocks are generally useful, as examples for defining menus, handling exceptions or defining threads (lightweight tasks) show.


[ aMenu str:"Open" action: {id x | [x doOpenFile]; } ];

[ { [x foo]; } ifException: {id e | [e raise]; } ];

[ { execl("/bin/sh","ls",0); } fork ];

Scope of Variables

Objective-C blocks can reference global data, instance variables from an enclosing method implementation, or local (stack) variables.

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.

- aMethod
{
    id aLocal = [Object new];

    [ {
        [aLocal doIt];
	aLocal = nil;
        [self doIt];
        [anInstanceVariable doIt];
        [aGlobal doIt];
    } value]; 

    return aLocal;
}

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.

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.

Syntactical Limitations

To avoid confusion with C language elements, such as braced groups used as initializers, or with GNU C braced groups inside expressions, Blocks are allowed only as arguments or as receiver of Objective-C messages. This restriction allows us to use a very convenient and natural syntax for Blocks without arguments.

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).

Return Value of a Block

A Block, when evaluated with value or value:, returns normally the Block object itself. However, any Object can serve as return value.

Particulary useful are Blocks that consists of just a single expression. Those Blocks have an implicit return value that is equal to the expression :

[ aCollection detect: { id e | [e idEqual:anObject] } ];

Note that there's no semi-colon following the idEqual: message expression.

Exception Handling

The Block class has two supporting methods for exception handling : raise and ifException:. An exception is raised by sending a message raise to a default handler.

- bar:anObject 
{

   if (!anObject) {
      [{printf("'anObject' must-be non-nil\n");} raise];
   }

}

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.

[{

	/* code that may raise an exception */
	
	[self bar:anObject];

} ifException:{

	/* code to be executed before longjump to this place */
	
	printf("caught exception\n");

}];

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.

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.

Building Blocks By Hand

In some cases, it might not be possible to use the special syntax for Blocks, as supported by the Portable Object Compiler. It is still possible to use the Block class, using a different compiler, by manually creating a block. The code for the block needs to be placed in a function :

static id foo(id anObject) { return anObject; }
The block itself is then created by using the newBlock function :

id aBlock = newBlock(1,foo,NULL);
The compiler has an option -noBlocks that can be used to turn off the special syntax for Blocks.

Method Types

Tags

Exception Handling

Evaluating Blocks

Control Flow

Methods



initialize

+ initialize

Allocates 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.



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.

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.



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.



untilFalse:

- untilFalse : aBlock

Method to repeatedly evaluate aBlock, until the receiver Block evaluates to something that isFalse. The argument Block aBlock is evaluated at least once.



untilTrue:

- untilTrue : aBlock

Method to repeatedly evaluate aBlock, until the receiver Block evaluates to something that isTrue. The argument Block aBlock is evaluated at least once.