< Previous PageNext Page > Hide TOC

Remote Messaging

Like most other programming languages, Objective-C was initially designed for programs that are executed as a single process in a single address space.

Nevertheless, the object-oriented model, where communication takes place between relatively self-contained units through messages that are resolved at runtime, would seem well suited for interprocess communication as well. It’s not hard to imagine Objective-C messages between objects that reside in different address spaces (that is, in different tasks) or in different threads of execution of the same task.

For example, in a typical server-client interaction, the client task might send its requests to a designated object in the server, and the server might target specific client objects for the notifications and other information it sends. Or imagine an interactive application that needs to do a good deal of computation to carry out a user command. It could simply display a dialog telling the user to wait while it was busy, or it could isolate the processing work in a subordinate task, leaving the main part of the application free to accept user input. Objects in the two tasks would communicate through Objective-C messages.

In this section:

Distributed Objects
Language Support


Distributed Objects

Remote messaging in Objective-C requires a runtime system that can establish connections between objects in different address spaces, recognize when a message is intended for an object in a remote address space, and transfer data from one address space to another. It must also mediate between the separate schedules of the two tasks; it has to hold messages until their remote receivers are free to respond to them.

Cocoa includes a distributed objects architecture that is essentially this kind of extension to the runtime system. Using distributed objects, you can send Objective-C messages to objects in other tasks or have messages executed in other threads of the same task. (When remote messages are sent between two threads of the same task, the threads are treated exactly like threads in different tasks.) Note that Cocoa’s distributed objects system is built on top of the runtime system; it doesn’t alter the fundamental behavior of your Cocoa objects.

To send a remote message, an application must first establish a connection with the remote receiver. Establishing the connection gives the application a proxy for the remote object in its own address space. It then communicates with the remote object through the proxy. The proxy assumes the identity of the remote object; it has no identity of its own. The application is able to regard the proxy as if it were the remote object; for most purposes, it is the remote object.

Remote messaging is illustrated in Figure 12-1, where object A communicates with object B through a proxy, and messages for B wait in a queue until B is ready to respond to them:


Figure 12-1  Remote Messages


The sender and receiver are in different tasks and are scheduled independently of each other. So there’s no guarantee that the receiver is free to accept a message when the sender is ready to send it. Therefore, arriving messages are placed in a queue and retrieved at the convenience of the receiving application.

A proxy doesn’t act on behalf of the remote object or need access to its class. It isn’t a copy of the object, but a lightweight substitute for it. In a sense, it’s transparent; it simply passes the messages it receives on to the remote receiver and manages the interprocess communication. Its main function is to provide a local address for an object that wouldn’t otherwise have one. A proxy isn’t fully transparent, however. For instance, a proxy doesn’t allow you to directly set and get an object’s instance variables.

A remote receiver is typically anonymous. Its class is hidden inside the remote application. The sending application doesn’t need to know how that application is designed or what classes it uses. It doesn’t need to use the same classes itself. All it needs to know is what messages the remote object responds to.

Because of this, an object that’s designated to receive remote messages advertises its interface in a formal protocol. Both the sending and the receiving application declare the protocol—they both import the same protocol declaration. The receiving application declares it because the remote object must conform to the protocol. The sending application declares it to inform the compiler about the messages it sends and because it may use the conformsToProtocol: method and the @protocol() directive to test the remote receiver. The sending application doesn’t have to implement any of the methods in the protocol; it declares the protocol only because it initiates messages to the remote receiver.

The distributed objects architecture, including the NSProxy and NSConnection classes, is documented in the Foundation framework reference and Distributed Objects Programming Topics.

Language Support

Remote messaging raises not only a number of intriguing possibilities for program design, it also raises some interesting issues for the Objective-C language. Most of the issues are related to the efficiency of remote messaging and the degree of separation that the two tasks should maintain while they’re communicating with each other.

So that programmers can give explicit instructions about the intent of a remote message, Objective-C defines six type qualifiers that can be used when declaring methods inside a formal protocol:

These modifiers are restricted to formal protocols; they can’t be used inside class and category declarations. However, if a class or category adopts a protocol, its implementation of the protocol methods can use the same modifiers that are used to declare the methods.

The following sections explain how these modifiers are used.

Synchronous and Asynchronous Messages

Consider first a method with just a simple return value:

- (BOOL)canDance;

When a canDance message is sent to a receiver in the same application, the method is invoked and the return value provided directly to the sender. But when the receiver is in a remote application, two underlying messages are required—one message to get the remote object to invoke the method, and the other message to send back the result of the remote calculation. This is illustrated in the figure below:


Figure 12-2  Round-Trip Message


Most remote messages are, at bottom, two-way (or “round trip”) remote procedure calls (RPCs) like this one. The sending application waits for the receiving application to invoke the method, complete its processing, and send back an indication that it has finished, along with any return information requested. Waiting for the receiver to finish, even if no information is returned, has the advantage of coordinating the two communicating applications, of keeping them both “in sync.” For this reason, round-trip messages are often called synchronous. Synchronous messages are the default.

However, it’s not always necessary or a good idea to wait for a reply. Sometimes it’s sufficient simply to dispatch the remote message and return, allowing the receiver to get to the task when it can. In the meantime, the sender can go on to other things. Objective-C provides a return type modifier, oneway, to indicate that a method is used only for asynchronous messages:

- (oneway void)waltzAtWill;

Although oneway is a type qualifier (like const) and can be used in combination with a specific type name, such as oneway float or oneway id, the only such combination that makes any sense is oneway void. An asynchronous message can’t have a valid return value.

Pointer Arguments

Next, consider methods that take pointer arguments. A pointer can be used to pass information to the receiver by reference. When invoked, the method looks at what’s stored in the address it’s passed.

- setTune:(struct tune *)aSong
{
    tune = *aSong;
    ...
}

The same sort of argument can also be used to return information by reference. The method uses the pointer to find where it should place information requested in the message.

- getTune:(struct tune *)theSong
{
    ...
    *theSong = tune;
}

The way the pointer is used makes a difference in how the remote message is carried out. In neither case can the pointer simply be passed to the remote object unchanged; it points to a memory location in the sender’s address space and would not be meaningful in the address space of the remote receiver. The runtime system for remote messaging must make some adjustments behind the scenes.

If the argument is used to pass information by reference, the runtime system must dereference the pointer, ship the value it points to over to the remote application, store the value in an address local to that application, and pass that address to the remote receiver.

If, on the other hand, the pointer is used to return information by reference, the value it points to doesn’t have to be sent to the other application. Instead, a value from the other application must be sent back and written into the location indicated by the pointer.

In the first case, information is passed on the first leg of the round trip. In the second case, information is returned on the second leg of the round trip. Because these cases result in very different actions on the part of the runtime system for remote messaging, Objective-C provides type modifiers that can clarify the programmer’s intention:

The Cocoa distributed objects system takes inout to be the default modifier for all pointer arguments except those declared const, for which in is the default. inout is the safest assumption but also the most time-consuming since it requires passing information in both directions. The only modifier that makes sense for arguments passed by value (non-pointers) is in. While in can be used with any kind of argument, out and inout make sense only for pointers.

In C, pointers are sometimes used to represent composite values. For example, a string is represented as a character pointer (char *). Although in notation and implementation there’s a level of indirection here, in concept there’s not. Conceptually, a string is an entity in and of itself, not a pointer to something else.

In cases like this, the distributed objects system automatically dereferences the pointer and passes whatever it points to as if by value. Therefore, the out and inout modifiers make no sense with simple character pointers. It takes an additional level of indirection in a remote message to pass or return a string by reference:

- getTuneTitle:(out char **)theTitle;

The same is true of objects:

- adjustRectangle:(inout Rectangle **)theRect;

These conventions are enforced at runtime, not by the compiler.

Proxies and Copies

Finally, consider a method that takes an object as an argument:

- danceWith:(id)aPartner;

A danceWith: message passes an object id to the receiver. If the sender and the receiver are in the same application, they would both be able to refer to the same aPartner object.

This is true even if the receiver is in a remote application, except that the receiver needs to refer to the object through a proxy (since the object isn’t in its address space). The pointer that danceWith: delivers to a remote receiver is actually a pointer to the proxy. Messages sent to the proxy would be passed across the connection to the real object and any return information would be passed back to the remote application.

There are times when proxies may be unnecessarily inefficient, when it’s better to send a copy of the object to the remote process so that it can interact with it directly in its own address space. To give programmers a way to indicate that this is intended, Objective-C provides a bycopy type modifier:

- danceWith:(bycopy id)aClone;

bycopy can also be used for return values:

- (bycopy)dancer;

It can similarly be used with out to indicate that an object returned by reference should be copied rather than delivered in the form of a proxy:

- getDancer:(bycopy out id *)theDancer;

Note: When a copy of an object is passed to another application, it cannot be anonymous. The application that receives the object must have the class of the object loaded in its address space.

bycopy makes so much sense for certain classes—classes that are intended to contain a collection of other objects, for instance—that often these classes are written so that a copy is sent to a remote receiver, instead of the usual reference. You can override this behavior with byref, however, thereby specifying that objects passed to a method or objects returned from a method should be passed or returned by reference. Since passing by reference is the default behavior for the vast majority of Objective-C objects, you will rarely, if ever, make use of the byref keyword.

The only type that it makes sense for bycopy or byref to modify is an object, whether dynamically typed id or statically typed by a class name.

Although bycopy and byref can’t be used inside class and category declarations, they can be used within formal protocols. For instance, you could write a formal protocol foo as follows:

@Protocol foo
- (bycopy)array;
@end

A class or category can then adopt your protocol foo. This allows you to construct protocols so that they provide “hints” as to how objects should be passed and returned by the methods described by the protocol.



< Previous PageNext Page > Hide TOC


© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.