#import "ClassA.h"
#import "EDOClientService.h"
- (void)invokeRMI {
ClassA *A = [EDOClientService rootObjectWithPort:HOST_PORT];
[A method];
}
The following graph shows what happens when Client is invoking a method
on an
instance A
of class ClassA
.
The instance A
, of type ClassA
as recognized by the compiler, is shadowed by
an instance of EDOObject
, who points to the actual instance in Host
.
The value types parameters or returns are coded and passed by copy, while the other types are wrapped by another distant object and passed by ref. The errors or exceptions will be populated from the remote process to the local process.
INET socket is used but UNIX domain socket can also be used while two apps have the entitlements with the same group name to ensure the security. The data being transferred can also be ciphered.
EDOHostService is used to serve as an instance, local to the process address as a
remote object that others can use. Once a EDOHostService is created, it will listen
on the given port number (if zero, it will pick any available port number from
the system) and execute the invocation in the given dispatch_queue
. The
dispatch_queue
will then hold a strong reference to the service. From this
point on, the EDOHostService is bound to that queue.
NOTE: There can only be one service that is associated with a dispatch_queue
.
The newer one will override the older one.1
When Client is making a remote invocation on an EDOObject and it includes passing a parameter:
- If it's value type2, it will be serialized and deserialized. Pass-by-value.
- If it is not a value type, it will be boxed into a EDOObject and sent over the wire. Pass-by-ref.
In case of #2, after boxing any variable residing in Client into a remote object, Host can then call on the remote object back to Client. During this process, a few things are happening.
In order to box the parameter, Client needs a service to manage the remote invocations where Host can callback Client on the boxed parameter; the way EDO is doing it now is to retrieve the bound service from the current running queue, thus if there is no service is associated with the current running queue, it will panic. It is a good idea to create a service for the current queue before making any remote invocations1.
After unboxing the parameter, Host will try to resolve to the actual instance at the local address if they come from the same process. If the object is a remote object and can't be resolved, it will then try to find if there is already another existing EDOObject that is pointing to the same underlying object and return the same EDOObject if there is one. (uniqueness)
It is a common pattern to pass a reference to NSError *
and in case of errors,
the reference is filled by an instance of NSError
. This type of parameter is
defined as an out parameter. Only a reference to NSObject *
is supported and
only seen as an out parameter. For example, this type can also be seen as a
pointer to a c-array of NSObject *
and if one is used to iterate over it, it
will crash.
Blocks are supported by eDO and executed in the same process that they originated from.
Pointers are not supported as there is no way to calculate the actual frame or buffer size.
When the arguments are being passed via a remote invocation:
- If it is a value type, the argument will be serialized and deserialized, the new object will be re-created remotely;
- If it is a reference type, the argument will be wrapped as a remote object, and any invocation will be forwarded back to the original object.
All the Objective-C objects are by default the reference type, and in order to
let EDO know it is a value type and passed by value, override
-(BOOL)edo_isEDOValueType
to returns YES
.
The list of default value types enabled by EDO:
- NSData
- NSDate
- NSIndexSet
- NSIndexPath
- NSString
- NSValue, including all subclasses, e.g. NSNumber
For optimization and broader application, we also provide APIs for users to
specify the calling and returning convention for remote invocation. The
supported APIs are passByValue
(for parameters) and returnByValue
(for
return values).
[[foo returnByValue] bar];
foo
is a remote object. By default the return value should be a remote object,
but with returnByValue
it will return a local object with the same value.
returnByValue
should be used on the remote invocation target.
[foo bar:[param passByValue]];
foo
is a remote object and param
is a local object. By default, it will wrap
param
to an EDOObject
and then send to remote process, but with
passByValue
, the remote process will receive a local object with the same
value as param
.
Actually, passByValue
and returnByValue
can also be used on return values
and parameters respectively. For example:
- (void)remoteCall {
return [myArray passByValue];
}
And then [remoteFoo remoteCall]
will give you the local array object. This
could be useful sometimes but in general we suggest users to follow the previous
usecase to avoid confusion and mistake.
In a lot of cases, using passByValue
and returnByValue
can help users gain
performance (e.g. remote NSArray/NSDictionary iteration). The only requirement
of using this feature is that the returned/passed types need to conform to
NSCoding
.
Footnotes
-
One goal is to have the ability to not always associate a service with a dispatch_queue. The long term goal is also to create a short lived service to serve a most recent invocation so the user doesn't need to. ↩ ↩2
-
The value type is the type that will be fully serialized, i.e. NSString, NSData; note NSArray, NSSet are not value types and will be boxed into a remote object. If you would like your object to be passed by value, override
-(BOOL)edo_isEDOValueType
to returnsYES
. Examples ↩