|
|
|
|
|
|
|
| PATH |

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 run-time, 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 put up an attention panel 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.
Similarly, several separate processes could cooperate on the editing of a single document. There could be a different editing tool for each type of data in the document. One task might be in charge of presenting a unified user interface on-screen and of sorting out which user instructions were the responsibility of which editing tool. Each cooperating task could be written in Objective-C, with Objective-C messages being the vehicle of communication between the user interface and the tools and between one tool and another.
Remote messaging in Objective-C requires a run-time 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 run-time 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 run-time 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 diagrammed in Figure 4-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 4-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 will be 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 conformsTo: 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.
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:
onewayinoutinoutbycopybyref
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.
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 4-2 Round-Trip Message
Most remote messages will be, 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 will. 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.
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 run-time system for remote messaging must make some adjustments behind the scenes.
If the argument is used to pass information by reference, the run-time 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 one case, information is passed on the first leg of the round trip. In the other 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 run-time system for remote messaging, Objective-C provides type modifiers that can clarify the programmer's intention:
in indicates that information is being
passed in a message:- setTune:(in struct tune *)aSong;
out indicates that an argument
is being used to return information by reference:
- getTune:(out struct tune *)theSong;
inout, indicates that
an argument is used both to provide information and to get information
back:- adjustTune:(inout struct tune *)aSong;
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 run time, not by the compiler.
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 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 will need 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 into or out of a
method should all be passed 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.

© 2001 Apple Computer, Inc.
|
|
|
Contact Us | Privacy Notice Copyright © 2000 Apple Computer, Inc. All rights reserved. 1-800-MY-APPLE |