The purpose of ActorKit is 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.
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.
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 [1] 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 [2].
- (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<PLActorProcess> 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];
}
Message Sequence:
ActorKit provides facilities for handling this common usage scenario. Unique transaction ids may be generated via the createTransactionId (PLActorKit) method, 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<PLActorProcess> helloActor = [PLActorKit spawnWithTarget: self selector: @selector(helloActor:)]; PLActorMessage *message = [PLActorMessage messageWithObject: @"Hello"]; PLActorMessage *reply = [PLActorRPC sendRPC: message toProcess: helloActor];
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";
}
// 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
Contact Plausible Labs for more information: http://www.plausiblelabs.com
[2] 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.
1.5.5