How to debug communication between XPC service and client application in OSX

I am trying to write a simple pair of “client applications” and “XPC services”. I managed to start the xpc service from the client (i.e., I see the service running in the Activity Monitoring process list), but when I try to send any request that has a response block, I get the error message: "Could not contact assistant application. "

Worst of all, the error does not give me any information about what went wrong. And I also cannot debug the service properly. As far as I understand, the correct way to do this is to connect a debugger for processing (Debug-> Attach to process, see here ). I have both client and service projects in one workspace.

When I start the client from xcode and try to connect the debugger to the running service, it ends with the error "Could not connect to pid: X".

If I archive the client application, run it from the application file, and then try to connect the debugger for maintenance, the result will be the same.

The only way I can write something from a service that I could imagine is to write a log class that will write data to a file. I have not tried this approach yet, however it looks crazy for me.

So my question is:

a) How do I find out what went wrong when I received an uninformative response such as: "Failed to contact the assistive application"?

b) And also, what is the proper way to debug an xpc service in the first place? The link is above 5 years, but I see that some people said that "attach to the debugger" does not work.

The code itself is pretty simple:

XPC service, listener implementation:

#import "ProcessorListener.h" @implementation ProcessorListener - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { [newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)]]; [newConnection setExportedObject: self]; self.xpcConnection = newConnection; newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol: @protocol(Progress)]; // connections start suspended by default, so resume and start receiving them [newConnection resume]; return YES; } - (void) sendMessageWithResponse:(NSString *)receivedString reply:(void (^)(NSString *))reply { reply = @"This is a response"; } - (void) sendMessageWithNoResponse:(NSString *)mString { // no response here, dummy method NSLog(@"%@", mString); } 

And the main file for maintenance:

 #import <Foundation/Foundation.h> #import "TestService.h" @interface ServiceDelegate : NSObject <NSXPCListenerDelegate> @end @implementation ServiceDelegate - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection. // Configure the connection. // First, set the interface that the exported object implements. newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(TestServiceProtocol)]; // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object. TestService *exportedObject = [TestService new]; newConnection.exportedObject = exportedObject; // Resuming the connection allows the system to deliver more incoming messages. [newConnection resume]; // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO. return YES; } @end int main(int argc, const char *argv[]) { // [NSThread sleepForTimeInterval:10.0]; // Create the delegate for the service. ServiceDelegate *delegate = [ServiceDelegate new]; // Set up the one NSXPCListener for this service. It will handle all incoming connections. NSXPCListener *listener = [NSXPCListener serviceListener]; listener.delegate = delegate; // Resuming the serviceListener starts this service. This method does not return. [listener resume]; return 0; } 

For a client application, the user interface contains a bunch of buttons:

 - (IBAction)buttonSendMessageTap:(id)sender { if ([daemonController running]) { [self executeRemoteProcessWithName:@"NoResponse"]; } else { [[self.labelMessageResult cell] setTitle: @"Error"]; } } - (IBAction)buttonSendMessage2:(id)sender { if ([daemonController running]) { [self executeRemoteProcessWithName:@"WithResponse"]; } else { [[self.labelMessageResult cell] setTitle: @"Error"]; } } - (void) executeRemoteProcessWithName: (NSString*) processName { // Create connection NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: @protocol(Processor)]; NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: @"bunldeID"]; // there a correct bundle id there, really [connection setRemoteObjectInterface: myCookieInterface]; connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(Progress)]; connection.exportedObject = self; [connection resume]; // NOTE that this error handling code is not called, when debugging client, ie connection seems to be established id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err) { NSAlert *alert = [[NSAlert alloc] init]; [alert addButtonWithTitle: @"OK"]; [alert setMessageText: err.localizedDescription]; [alert setAlertStyle: NSAlertStyleWarning]; [alert performSelectorOnMainThread: @selector(runModal) withObject: nil waitUntilDone: YES]; }]; if ([processName containsString:@"NoResponse"]) { [theProcessor sendMessageWithNoResponse:@"message"]; } else if ([processName containsString:@"WithResponse"]) { [theProcessor sendMessageWithResponse:@"message" reply:^(NSString* replyString) { [[self.labelMessageResult cell] setTitle: replyString]; }]; } } 
+5
source share

Source: https://habr.com/ru/post/1265384/


All Articles