Git Product home page Git Product logo

edistantobject's Introduction

eDistantObject

Apache License Build Status

eDistantObject (easyDistantObject or eDO) provides users an easy way to make remote invocations between processes in Objective-C and Swift without explicitly constructing RPC structures.

Similar to NSDistantObject, eDistantObject takes advantage of Objective-C runtime features: it behaves as a proxy, or a puppet, in one process, and when it receives the message, it forwards the message via the communication layer (POSIX socket) to the object in a different process.

You can find the guideline to setup eDO and try it with your code in Setup Guide, and more about how eDO actually works in Details Doc.

How to use

Consider a typical use case, where a Client needs to communicate with a Host. The usage steps for eDO are broken into three main steps:

1. Host

On the host side, an EDOHostService must be created so that the eDistantObject is set up. Say this is done in a file Host.m. Adding the following code will setup a simple distant object.

The execution queue will hold a strong reference to the EDOHostService, so it needs to be retained to keep the service alive. To stop serving the root object, we can either call the invalidate API or release the queue, which implicitly invalidates the service hosting the root object.

FooClass is just a placeholder. Any class can be used in this way.

- (void)startUp {
  // Arbitrary port number for starting the service. Ensure that this doesn't
  // conflict with any existing ports being used.
  UInt16 portNumber = 12345;

  // The root object exposed is associated with a dispatch queue, any invocation made
  // on this object will have its invocations forwarded to the host. The invocations
  // will be dispatched to the associated queue.
  FooClass *rootObject = [[FooClass alloc] init];
  // If the execution queue is released, the EDOHostService running in this
  // queue will be released as well. Users can choose their own way to
  // retain the queue.
  self.executionQueue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_SERIAL);
  [EDOHostService serviceWithPort:portNumber
                       rootObject:rootObject
                            queue:self.executionQueue];
}

So that both the client and the host are aware of the methods available in the FooClass class, the header FooClass.h needs to be exposed in both the host as well as the client targets. However, any calls from the client side will be forwarded to the host, hence FooClass.m will only need to be compiled and linked with the Host process.

# FooClass.h

@interface FooClass
- (void)method1;
@end

# FooClass.m omitted [Present only in the Host Process, containing the implementation of method1]

In the client side, say a file Client.m makes a call to the distant object, FooClass. For this purpose, the client will have to fetch the root distant object using EDOClientService. Once this is set up, the distant object can be used as if it were a local object, with calls proxied to the host.

- (void)someMethod {
  // The object, fetched remotely from the host is seen by the client to be the same as a local
  // object.
  FooClass *rootObject = [EDOClientService rootObjectWithPort:portNumber];
  [rootObject method1];
}

For more information please look at Where to write code.

Swift Support

eDO can also work on Swift although it uses features of Objective-C as long as the object defined and used are marked to invoke in the Objective-C manner.

For Swift support you will need some extra setup. Please refer to the Swift Guide.

For Contributors

Please make sure you’ve followed the guidelines in CONTRIBUTING.md before making any contributions.

Setup eDistantObject project

  1. Clone eDistantObject repository from GitHub:
git clone https://github.com/google/eDistantObject.git
  1. After you have cloned the eDistantObject repository, install dependencies:
pod install
  1. Open eDistantObject.xcworkspace and ensure that all the targets build.
  2. You can now use eDistantObject.xcworkspace to make changes to the project.

edistantobject's People

Contributors

albertdai avatar albertwang0116 avatar bootstraponline avatar brettfazio avatar federicoasara avatar ji-yaqi avatar khandpur avatar mobile-devx-github-bot avatar rrallo avatar strangewiz avatar tirodkar avatar wuhao5 avatar ynzhang0509 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

edistantobject's Issues

Forward `isKindOfClass:` to distant object?

Is there a reason we are not forwarding isKindOfClass: to the distant object? I think that would make the most sense since users are more likely to want to check the class of the actual object than of the proxy. FWIW, NSDistantObject forwards.

Error while running the tests.

When I try to run the test after cloning, I get the error

Showing All Messages
Undefined symbol: OBJC_CLASS$_EDOTestValueType

Detail is slightly confusing

Hi,

Here is a few feedback on https://github.com/google/eDistantObject/blob/master/docs/detail.md
I'm trying to understand it, and thought maybe you'd want some feedback.
I must note I'm new to Objective-C/iOS development, trying to learn (which is why I read the doc)

In the image, what is "v@:"?

Why is the disjunction "value type/reference type" done first in the section "Boxing and Unboxing parameters" and then in "Value Type (pass by value) and Reference Type (pass by reference)". If there is a difference, I don't get it.

In "Boxing and Unboxing Parameters" you state it's a good idea to create a service so that the client can listen to request by the host. Would you mind adding a link to show how to do that? Because this seems absent from the examples of setup or readme

Only a reference to NSObject * is supported and only seen as an out parameter

I find this sentence quite confusing. Where it is supported? I assume you mean that some other things are forbidden. What is forbidden and where? I believe being explicit would help.

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.

What does "one" refere too? And what is "it" (well, I assume that "it" and "one" refers to the same thing)

and in order to let EDO know it is a value type and passed by value, override -(BOOL)edo_isEDOValueType to returns YES.

I was confused. Because the sentence begin with "all objects". I assume the end of this sentence refers to a single object. In this case, I guess that it would help to rewrite it as:

in order to let EDO knows that an object o must behave as value type and hence be passed by value, you should override its method -(BOOL)edo_isEDOValueType to return YES.

However, I'm far from sure this is what you meant, so any clarification would help, especially if actually I misunderstood

Face with a crash caused by [NSKeyedArchiver edo_archivedDataWithObject:]

[NSKeyedUnarchiver edo_unarchiveObjectWithData:]: unrecognized selector sent to class 0x7fff86d90730
 +[NSKeyedArchiver edo_archivedDataWithObject:]: unrecognized selector sent to class 0x7fff86d90758
 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSKeyedArchiver edo_archivedDataWithObject:]: unrecognized selector sent to class 0x7fff86d90758'

Hi, all I'm facing with a crash caused by [NSKeyedArchiver edo_archivedDataWithObject:] with static library in Xcode 12. And the console log shows above. How can I fix it? Can anybody help?

Blocks cannot be serialized properly to remote objects due to load order bugs

As discussed in #156, it seems that blocks cannot be properly serialized to remote objects, when attempting a remote method call in a +load method, due to load order woes.

The code linked in the other issue shows that __attribute__((constructor)) is used, which is ordered later in the start chaing, after +load methods.

Moving the code to +load is also not the best solution, as +load are also subject to call order issues, as I recently discovered. I've settled on nasty hacks in my case. But those hacks aren't suitable here, as the code is in a class category.

The result is that when a block is called from the remote process, it is not serialized correctly on the client side (because the above-linked code has not executed yet), and an exception is raised +[NSInvocation _invocationWithMethodSignature:frame:]: method signature argument cannot be nil in:

    frame #0: 0x00007fff503b5af0 libobjc.A.dylib`objc_exception_throw
    frame #1: 0x00007fff23b9bc83 CoreFoundation`+[NSInvocation _invocationWithMethodSignature:frame:] + 355
  * frame #2: 0x0000000103e6efb8 DetoxHelper`__38+[EDOInvocationRequest requestHandler]_block_invoke(.block_descriptor=0x0000600003a7cd20, originalRequest=0x0000600002174540, service=0x0000600001f78540) at EDOInvocationMessage.m:313:34
    frame #3: 0x0000000103e6bb1e DetoxHelper`-[EDOExecutor edo_handleMessage:](self=0x0000600003a7ce10, _cmd="edo_handleMessage:", message=0x0000600003a68060) at EDOExecutor.m:143:16
    frame #4: 0x0000000103e6ac2f DetoxHelper`-[EDOExecutor runWithBlock:](self=0x0000600003a7ce10, _cmd="runWithBlock:", executeBlock=0x0000000103e7c660) at EDOExecutor.m:88:5
    frame #5: 0x0000000103e7bca9 DetoxHelper`+[EDOClientService sendSynchronousRequest:onPort:withExecutor:](self=EDOClientService, _cmd="sendSynchronousRequest:onPort:withExecutor:", request=0x000060000216c400, port=0x0000600003478180, executor=0x0000600003a7ce10) at EDOClientService.m:273:9
    frame #6: 0x0000000103e66bfb DetoxHelper`-[EDOObject(self=0x0000600002178700, _cmd="edo_forwardInvocation:selector:returnByValue:", invocation=0x0000600002172d80, selector="waitForIdleWithCompletionHandler:", returnByValue=NO) edo_forwardInvocation:selector:returnByValue:] at EDOObject+Invocation.m:89:32
    frame #7: 0x0000000103e669f9 DetoxHelper`-[EDOObject(self=0x0000600002178700, _cmd="forwardInvocation:", invocation=0x0000600002172d80) forwardInvocation:] at EDOObject+Invocation.m:66:3
    frame #8: 0x00007fff23b9d566 CoreFoundation`___forwarding___ + 838
    frame #9: 0x00007fff23b9f6c8 CoreFoundation`__forwarding_prep_0___ + 120
    frame #10: 0x0000000103e65300 DetoxHelper`__23-[DetoxManager connect]_block_invoke(.block_descriptor=0x0000000103e9b4e0) at DetoxManager.m:58:3
    frame #11: 0x00007fff511fc7f9 libdispatch.dylib`_dispatch_client_callout + 8
    frame #12: 0x00007fff511fda25 libdispatch.dylib`_dispatch_once_callout + 20
    frame #13: 0x0000000103e65184 DetoxHelper`-[DetoxManager connect] [inlined] _dispatch_once(predicate=0x0000000103ea7830, block=0x0000000103e651b0) at once.h:84:3
    frame #14: 0x0000000103e65169 DetoxHelper`-[DetoxManager connect](self=0x000060000347c780, _cmd="connect") at DetoxManager.m:54
    frame #15: 0x0000000103e65036 DetoxHelper`+[DetoxManager load](self=DetoxManager, _cmd="load") at DetoxManager.m:36:3
    frame #16: 0x00007fff503bcdf0 libobjc.A.dylib`load_images + 1226
    frame #17: 0x0000000103dc2d79 dyld_sim`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
    frame #18: 0x0000000103dcf970 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 438
    frame #19: 0x0000000103dce786 dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #20: 0x0000000103dce826 dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #21: 0x0000000103dc3046 dyld_sim`dyld::initializeMainExecutable() + 129
    frame #22: 0x0000000103dc70fc dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3831
    frame #23: 0x0000000103dc21cd dyld_sim`start_sim + 122
    frame #24: 0x00000001094eb6b7 dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
    frame #25: 0x00000001094e9375 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 818
    frame #26: 0x00000001094e4227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #27: 0x00000001094e4025 dyld`_dyld_start + 37

A few unused-but-set-variable errors in EDO

diff --git a/Channel/Sources/EDOChannelUtil.m b/Channel/Sources/EDOChannelUtil.m
index 9894719..433a1c3 100644
--- a/Channel/Sources/EDOChannelUtil.m
+++ b/Channel/Sources/EDOChannelUtil.m
@@ -53,7 +53,7 @@ size_t EDOGetPayloadSizeFromFrameData(dispatch_data_t data) {

EDOSocketFrameHeader_t *frame = NULL;
dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&frame, NULL);

  • (void)contiguousData;
    if (!edo_isFrameHeaderValid(frame)) {
    return 0;
    }
    diff --git a/Service/Sources/EDOHostService.m b/Service/Sources/EDOHostService.m
    index 5f68abf..cc67f77 100644
    --- a/Service/Sources/EDOHostService.m
    +++ b/Service/Sources/EDOHostService.m
    @@ -336,6 +336,7 @@ static const char kEDOExecutingQueueKey = '\0';
  • (BOOL)removeObjectWithAddress:(EDOPointerType)remoteAddress {
    NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
    __block NSObject *object NS_VALID_UNTIL_END_OF_SCOPE;
  • (void)object;
    dispatch_sync(_localObjectsSyncQueue, ^{
    // Transfer the ownership of local object to the outer queue, where the object should be
    // released.

To silence:
edo/Service/Sources/EDOHostService.m:338:21: error: variable 'object' set but not used [-Werror,-Wunused-but-set-variable]
__block NSObject *object NS_VALID_UNTIL_END_OF_SCOPE;
^
and

edo/Channel/Sources/EDOChannelUtil.m:55:19: error: variable 'contiguousData' set but not used [-Werror,-Wunused-but-set-variable]
dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&frame, NULL);
^

Support asynchronous block callback arguments in methods

I am not sure how in scope this request is, but this is a very common use case, where a remote server is requested for information, which is not immediately available.

NSXPCConnection allows for asynchronous block callback arguments, which do not require an immediate result. (This isn't supported by NSConnection/NSDistantObject). Apple does this with NSInvocation, which is able to target blocks.

With eDistantObject, block arguments seem broken or unsupported in two ways. If a block argument is called synchronously in the remote method, there is a +[NSInvocation _invocationWithMethodSignature:frame:]: method signature argument cannot be nil crash in EDOInvocationMessage:

    frame #0: 0x00007fff503b5af0 libobjc.A.dylib`objc_exception_throw
    frame #1: 0x00007fff23b9bc83 CoreFoundation`+[NSInvocation _invocationWithMethodSignature:frame:] + 355
  * frame #2: 0x0000000103e6efb8 DetoxHelper`__38+[EDOInvocationRequest requestHandler]_block_invoke(.block_descriptor=0x0000600003a7cd20, originalRequest=0x0000600002174540, service=0x0000600001f78540) at EDOInvocationMessage.m:313:34
    frame #3: 0x0000000103e6bb1e DetoxHelper`-[EDOExecutor edo_handleMessage:](self=0x0000600003a7ce10, _cmd="edo_handleMessage:", message=0x0000600003a68060) at EDOExecutor.m:143:16
    frame #4: 0x0000000103e6ac2f DetoxHelper`-[EDOExecutor runWithBlock:](self=0x0000600003a7ce10, _cmd="runWithBlock:", executeBlock=0x0000000103e7c660) at EDOExecutor.m:88:5
    frame #5: 0x0000000103e7bca9 DetoxHelper`+[EDOClientService sendSynchronousRequest:onPort:withExecutor:](self=EDOClientService, _cmd="sendSynchronousRequest:onPort:withExecutor:", request=0x000060000216c400, port=0x0000600003478180, executor=0x0000600003a7ce10) at EDOClientService.m:273:9
    frame #6: 0x0000000103e66bfb DetoxHelper`-[EDOObject(self=0x0000600002178700, _cmd="edo_forwardInvocation:selector:returnByValue:", invocation=0x0000600002172d80, selector="waitForIdleWithCompletionHandler:", returnByValue=NO) edo_forwardInvocation:selector:returnByValue:] at EDOObject+Invocation.m:89:32
    frame #7: 0x0000000103e669f9 DetoxHelper`-[EDOObject(self=0x0000600002178700, _cmd="forwardInvocation:", invocation=0x0000600002172d80) forwardInvocation:] at EDOObject+Invocation.m:66:3
    frame #8: 0x00007fff23b9d566 CoreFoundation`___forwarding___ + 838
    frame #9: 0x00007fff23b9f6c8 CoreFoundation`__forwarding_prep_0___ + 120
    frame #10: 0x0000000103e65300 DetoxHelper`__23-[DetoxManager connect]_block_invoke(.block_descriptor=0x0000000103e9b4e0) at DetoxManager.m:58:3
    frame #11: 0x00007fff511fc7f9 libdispatch.dylib`_dispatch_client_callout + 8
    frame #12: 0x00007fff511fda25 libdispatch.dylib`_dispatch_once_callout + 20
    frame #13: 0x0000000103e65184 DetoxHelper`-[DetoxManager connect] [inlined] _dispatch_once(predicate=0x0000000103ea7830, block=0x0000000103e651b0) at once.h:84:3
    frame #14: 0x0000000103e65169 DetoxHelper`-[DetoxManager connect](self=0x000060000347c780, _cmd="connect") at DetoxManager.m:54
    frame #15: 0x0000000103e65036 DetoxHelper`+[DetoxManager load](self=DetoxManager, _cmd="load") at DetoxManager.m:36:3
    frame #16: 0x00007fff503bcdf0 libobjc.A.dylib`load_images + 1226
    frame #17: 0x0000000103dc2d79 dyld_sim`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
    frame #18: 0x0000000103dcf970 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 438
    frame #19: 0x0000000103dce786 dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #20: 0x0000000103dce826 dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #21: 0x0000000103dc3046 dyld_sim`dyld::initializeMainExecutable() + 129
    frame #22: 0x0000000103dc70fc dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3831
    frame #23: 0x0000000103dc21cd dyld_sim`start_sim + 122
    frame #24: 0x00000001094eb6b7 dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
    frame #25: 0x00000001094e9375 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 818
    frame #26: 0x00000001094e4227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #27: 0x00000001094e4025 dyld`_dyld_start + 37

On the other hand, if the block argument is called asynchronously after the remote method call has ended, nothing happens.

XPC has two ways of dealing with the future callback call. If a normal proxy is used, the resulting asynchronous call is performed on a background queue, and if a synchronous proxy is called, the the client waits until the callback block is invoked.

NSSecureCoding instead of NSCoding

Would it make sense for this project to use NSSecureCoding instead of NSCoding?
I'm not sure what all the issues are surrounding this, I just know our code base has a test that tries to make sure all the classes we depend on DO NOT use NSCoding, and it is finding the NSCoding usages in this project.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.