-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support asynchronous block callback arguments in methods #156
Comments
can you provide how when you mentioned block, you mean the actual eDO doesn't have |
Yes, block = lambda = anon function. Let's look at a simple example: I have a shared protocol: @protocol TestServiceProtocol
- (void)upperCaseString:(NSString *)aString withReply:(void (^)(NSString *))reply;
@end On the server (listener), when a new connection arrives, the protocol is explicitly given to the XPC system: - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
} Finally on the client, the protocol is again provided explicitly. Without providing this, calling a remote method fails with unknown selector. NSXPCConnection* c = [x initWithServiceName:@"com.LeoNatan.XPCTester.XPCService"];
c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)];
[c resume];
[[c synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
NSLog(@"%@", error);
}] upperCaseString:@"hello" withReply:^(NSString *aString) {
NSLog(@"Result string was: %@", aString);
}]; On the server, when the method is invoked by the connection, the supplied block is a block created at runtime during decoding of the XPC connection:
Interestingly, if I use
But if I use
I'm guessing this is the reason they require a protocol, because this type of type metadata is only output for protocols if I recall correctly. Once the reply block is called on the server, it is serialized and the originally provided client block is then called using |
There also appears to exist |
are you saying that you declare your own method signature where you have a callback block? then in your impl in the server side, the callback is, for the purpose of this discussion, invoked after the method returns. and NSXPCConnection is able to invoke a remote block as is?
I also found in typedefs, more detailed encoding info is provided compared to the encoding directly retrieved from the method or class itself. I would guess this is just how the runtime is programmed right now, |
I only declare a protocol. The system deduces the type of the block and creates a proxy. NSXPC provides two types of proxy objects for clients, an async one and a sync one. The sync one blocks the client call until the reply block is called on the server; the reply block is called on the same thread as method call in client process. The async one does not block, and the reply block is called on a background queue. Regarding +load, +initialize and attr(constructor), I've hit similar issues of order. I'm not sure this hack specifically will fit the usecase here, as eDO is using a category. But the current behavior is a bug. |
I will open a new issue regarding the load order. |
Did you declare the last
Thanks for the sharing, it's a smart workaround. I don't know if there is an order issue in eDO, because when it forwards a block, it should add those two forwarding methods to NSBlock, unless it doesn't because you load it from dylib? because it's a dummy empty block placeholder, it doesn't do anything and just pass through quietly. |
The issue here is that +load is called earlier in the start process, before attr(constructor). So the functionality there hasn't had a chance to run yet. |
but that only matters after the block is invoked remotely? as long as by the time you invoke a remote block, the attr(constructor) is set, you should be good, right? |
It's not the behavior I observed. In my case, the remote call was blocking (so blocking in a +load), and so the start process was blocked. |
Oh I see. Let's continue the discussion on a different thread, and focus on the async call for this one. Both look like legitimate issues to resolve to me. |
Regarding the |
oh so you are saying, Apple forces the method to be a pattern that has a |
Right, forgot to mention. In NSXPX, method return value can only be void or NSProgress. Reply block must be used to return a value, but the block doesn’t necessarily have to include arguments. |
I read some of the doc here, looks like the way NSXPC supports eDO works more like native ObjC, so it doesn't require you to change your signature to have [[remoteTestObject asyncWithCallback:^(NSInvocation *invocation) {
__unsafe_unretained NSString *result;
[invocation getReturnValue:&result];
}] upperCaseString]; This somehow looks really ugly to me. |
I’m not sure I understand why this is necessary. It does look ugly indeed. One idea I had was to use the remote block proxy’s lifecycle to control when the connection ends. So if a method is called with block arguments, on the server create proxy block arguments, and add some dealloc handler to each proxy block (using associated objects). Then, as long as these blocks are alive (proxy blocks have not been released), keep the connection open. If blocks are called on server, forward to client. Once all block proxies are released, close connection. This then is exactly like normal Objective C methods/blocks. |
The remote object can hold on to the connection/ my earlier comment was only meant for an organic way to support async call, such that your method would be only |
Your last comment is not a feasible scenario in many cases, especially when GCD is invoked. upperCaseString is a trivial example, not indicative of real-world usage. Completion handlers are needed in many cases where the server needs to perform asynchronous tasks. NSXPC (through XPC) retains connections too, and it is the most used IPC mechanism on macOS and iOS. I am not familiar with any issues that has. I guess a major difference is the usage of a bad socket rather than a mach port. |
The completion handler should be already supported with an exception that the service is not held by the remote object such that you have to create a service explicitly. NSXPC API has a parent NSXPCConnection interface for the client so it can be bi-directional channel, whereas in eDO, there will be two services and two separate client/service pairs. The one feature that eDO has is to automatically turn your local objects to remote objects, but it also hides a lot of details that become harder to debug and understand when there is any issue like this. Maybe making a paired service would work better or easier to understand and API-wise. At least at a very minimum, the |
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 byNSConnection
/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 inEDOInvocationMessage
: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.
The text was updated successfully, but these errors were encountered: