ActorKit: Objective-C Asynchronous Inter-thread Message Passing

03 Dec 2008, 00:13 PST

Introduction

In the process of writing Peeps, I decided to implement a library to simplify handling of background processing on the iPhone. The phone itself is a somewhat limited device, and I found that background processing was required to maintain UI interactivity while loading and rendering images for Peep's portrait and CoverFlow views.

Having spent considerable time working with actor-based concurrency systems, I decided to implement a ActorKit -- an Objective-C implementation of asynchronous inter-thread message passing. You can peruse the documentation and download the 1.0-beta1 release of ActorKit from the project page

ActorKit

ActorKit is intended to facilitate the implementation of concurrent software on both the desktop (Mac OS X) and embedded devices (iPhone OS). On the iPhone, thread-based concurrency is a critical tool in achieving high interface responsiveness while implementing long-running and potentially computationally expensive background processing. On Mac OS X, thread-based concurrency opens the door to leveraging the power of increasingly prevalent multi-core desktop computers.

To this end, ActorKit endeavours to provide easily understandable invariants for concurrent software:

As an actor may only synchronously receive messages, no additional concurrency primitives are required, such as mutexes or condition variables.

Building on this base concurrency model, ActorKit provides facilities for proxying Objective-C method invocations between threads, providing direct, transparent, synchronous and asynchronous execution of Objective-C methods on actor threads.

Actor Creation and Simple Message Passing

Messages

The Actor model of concurrency is fundamentally based on communication between isolated actors through asynchronous message passing. In ActorKit, any Objective-C object conforming to the NSObject protocol may be used as an inter-actor message, but message objects should be immutable to ensure thread safety. ActorKit, being written in Objective-C, can not enforce message immutablity or full isolation of Actor threads. It is entirely possible to pass mutable messages, or access mutable global variables. Like many other libraries implementing Actor message passing semantics, isolation is maintained purely through convention.

While ActorKit supports messaging with any Objective-C object, the PLActorMessage class provides generally useful facilities such as unique message transaction ids, automatically determining the message sender, and including additional message payloads.

Actor Proceses

In ActorKit, all threads are fully functioning actors -- including the "main" Cocoa thread. Each actor is represented by a PLActorProcess instance, which may be passed to any other running actors, and is used to send messages asynchronously to the given process.

ActorKit ensures that all message reception within a given actor occurs serially, and provides strict guarantees on message ordering -- messages M1 and M2 sent from actor A1 will be delivered to actor A2 in the same order. However, delivery of messages from actor A1 may be interspersed with delivery of messages sent by other actors:

In the future, ActorKit may be extended to leverage Apple's Grand Central to provide hybrid event/thread M:N scheduling of actor execution on available cores, an approach presented by Philipp Haller and Martin Odersky and implemented in Scala's Actor library (LAMP-REPORT-2007-001, EPFL, January 2007)

A Simple Echo Actor

- (void) echo {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    PLActorMessage  *message;
 
    // Loop forever, receiving messages
    while ((message = [PLActorKit  receive]) != nil) {
        // Echo the same message back to the sender.
        [[message sender] send: message];
 
        // Flush the autorelease pool through every loop iteration
        [pool release];
        pool = [[NSAutoreleasePool alloc] init];
    }
 
    [pool release];
}
 
- (void) run {
    // Spawn a new actor thread. This will return a process instance which may be used
    // to deliver messages the new actor.
    id proc = [PLActorKit  spawnWithTarget: self selector: @selector(echo:)];
 
    // Send a simple message to the actor.
    [proc send: [PLActorMessage  messageWithObject: @"Hello"]];
   
    // Wait for the echo
    PLActorMessage  *message = [PLActorKit  receive];
}

Sending Synchronous Messages with Actors

In an Actor system where all messages are sent asynchronously, synchronous messaging may be achieved with the following steps:

Message Sequence:

ActorKit provides facilities for handling this common usage scenario. Unique transaction ids may be generated via -[PLActorKit createTransactionId] and every PLActorMessage generates and uses a new transactionId.

The PLActorRPC class utilizes the PLActorMessage's transaction id to wait for a reply on your behalf.

Send a message, and wait for the reply:

id helloActor = [PLActorKit spawnWithTarget: self selector: @selector(helloActor:)];
PLActorMessage *message = [PLActorMessage messageWithObject: @"Hello"];
PLActorMessage *reply = [PLActorRPC sendRPC: message toProcess: helloActor];

Transparently Proxying Objective-C Messages with Actors

ActorKit provides two NSProxy subclasses which provide transparent proxying of Objective-C synchronous and asynchronous method invocations via actor messaging. PLActorRPCProxy spawns a new actor to execute Objective-C methods for a given object instance, while PLRunloopRPCProxy executes Objective-C methods on a provided NSRunLoop.

In combination, these classes allow for safely and transparenty executing methods on Objective-C instances from any thread:

NSString *actorString = [PLActorRPCProxy proxyWithTarget: @"Hello"];
NSString *runloopString = [PLRunloopRPCProxy proxyWithTarget: @"Hello" runLoop: [NSRunLoop mainRunLoop]];
 
// Executes synchronously, via a newly created actor thread.
[actorString description];
 
// Executes synchronously, on the main runloop.
[runloopString description];

By default, PLActorRPCProxy and PLRunloopRPCProxy will execute methods synchronously, waiting for completion prior to returning. In order to execute a method asynchronously -- allowing a long running method to execute without waiting for completion -- it is necessary to mark methods for asynchronous execution.

The Objective-C runtime provides a number of type qualifiers that were intended for use in implementing a Distributed Object system. Of particular note is the 'oneway' qualifier, which allows us to specify that a method should be invoked asynchronously.

When a method is declared with a return value of 'oneway void', the proxy classes will introspect this return value, and execute the method asynchronously, without waiting for a reply:

- (oneway void) asyncMethod {
    // Execute, asynchronously
}

- (NSString *) synchronousMethod {
    // Execute, synchronously
    return @"Hello";
}

A Simple Echo Actor with PLActorRPCProxy

The following actor returns a proxy from its init method, ensuring that all methods called on the object instance will occur via the actor thread.

// An actor that responds to Objective-C messages either synchronously or asynchronously.
@implementation EchoActor
 
- (id) init {
    if ((self = [super init]) == nil)
        return nil;
  
    // Launch our actor
    id proxy = [[PLActorRPCProxy  alloc] initWithTarget: self];
  
    // Release ourself, as the proxy has retained our object,
    // and return our proxy to the caller
    [self release];
    return proxy;
}
  
// Method is called asynchronously
- (oneway void) asynchronousEcho: (NSString *) text listener: (EchoListener *) echoListener {
    [echoListener receiveEcho: text];
}
  
// Method is called synchronously
- (NSString *) synchronousEcho: (NSString *) text {
    return text;
}
  
@end

Integration & Development Services

ActorKit is provided free of charge under the MIT license, and may be freely integrated with any application. We can provide assistance with integrating our code in your own iPhone or Mac application, as well as development of additional features under a license of your choosing. We're also available for standard iPhone and Mac OS X application development.

Contact me for more information, or visit the Plausible Labs website.

References

Actors that Unify Threads and Events, Philipp Haller and Martin Odersky, LAMP-REPORT-2007-001, EPFL, January 2007. Available from http://lamp.epfl.ch/~phaller/actors.html.