19

I was successful in creating XPC service and communicating with XPC service by sending messages from main application. But what I want to know is, whether its possible to initiate a communication from XPC service to the main application. The Apple documentation says XPC is bidirectional. It would be much appreciated if someone can point me in right direction with an example.

Please note,

  • I want to launch the XPC from main application.
  • communicate with XPC from main application.
  • when some events occur, XPC should send a message to main application.

I succeeded in first two, but couldn't find any resource on the third one.

Thanks. :)

MjZac
  • 3,476
  • 1
  • 17
  • 28
  • 1
    What have you tried in terms of calling back the main program? Did you try using `exportedMethods` and `exportedObjects` from the main program? – gaige Feb 07 '14 at 09:42
  • 1
    Yes, I am experimenting with `remoteObjectInterface` from XPC service and `exportedObjects` from main application. I would be glad if you can provide an example. – MjZac Feb 07 '14 at 09:54
  • Show us what you have tried and we will attempt to help you fix it. – gaige Feb 07 '14 at 12:57
  • https://stackoverflow.com/questions/24040765/communicate-with-another-app-using-xpc – Klajd Deda Oct 24 '17 at 22:31
  • https://stackoverflow.com/questions/24040765/communicate-with-another-app-using-xpc – Klajd Deda Oct 24 '17 at 22:32

1 Answers1

24

Figured everything out. The following should be a decent example:

ProcessorListener.h (included in both client and server):

@protocol Processor

- (void) doProcessing: (void (^)(NSString *response))reply;

@end

@protocol Progress

- (void) updateProgress: (double) currentProgress;
- (void) finished;

@end

@interface ProcessorListener : NSObject<NSXPCListenerDelegate, Processor>

@property (weak) NSXPCConnection *xpcConnection;

@end

ProcessorListener.m: (Included in just the server)

#import "ProcessorListener.h"

@implementation ProcessorListener

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
    [newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:@protocol(Processor)]];
    [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) doProcessing: (void (^)(NSString *g))reply
{
    dispatch_async(dispatch_get_global_queue(0,0), ^{
      for(int index = 0; index < 60; ++index)
      {
         [NSThread sleepWithTimeInterval: 1];
         [[self.xpcConnection remoteObjectProxy] updateProgress: (double)index / (double)60 * 100];
      }

      [[self.xpcConnection remoteObjectProxy] finished];
    }

    // nil is a valid return value.
    reply(@"This is a reply!");
}

@end

MainApplication.m (your main app):

#import "ProcessorListener.h"

- (void) executeRemoteProcess
{
    // Create our connection
    NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: @protocol(Processor)];

    NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: kServiceName];

    [connection setRemoteObjectInterface: myCookieInterface];

    connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(Progress)];
    connection.exportedObject = self;

    [connection resume];

    id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err)
                       {
                           NSAlert *alert = [[NSAlert alloc] init];
                           [alert addButtonWithTitle: @"OK"];
                           [alert setMessageText: err.localizedDescription];
                           [alert setAlertStyle: NSWarningAlertStyle];

                           [alert performSelectorOnMainThread: @selector(runModal) withObject: nil waitUntilDone: YES];
                       }];

    [theProcessor doProcessing: ^(NSString * response)
     {
        NSLog(@"Received response: %@", response);
     }];
}

#pragma mark -
#pragma mark Progress

- (void) updateProgress: (double) currentProgress
{
    NSLog(@"In progress: %f", currentProgress);
}

- (void) finished
{
    NSLog(@"Has finished!");
}

@end

Note that this code is is a condensed version based on my working code. It may not compile 100% but shows the key concepts used. In the example, the doProcessing runs async to show that the callbacks defined in the Progress protocol still get executed even after the initial method has return and the Received response: This is a reply! has been logged.

Kyle
  • 17,317
  • 32
  • 140
  • 246
  • What if there are several connections to the processor? How do I know which connection called `doProcessing` so I could report progress to it? – pointum Nov 12 '15 at 14:23
  • 2
    @pointum you could pass another argument which identifies the process to `doProcessing`. – Kyle Nov 13 '15 at 18:15
  • This is a great answer but why not talk about app and service instead of client and server when defining the different pieces and what goes where? – Miguel Garcia Apr 18 '16 at 13:01
  • the secret sauce on this is that the 'the server' has to run in a certain context. by that i mean, if you run the server from gdb or command line this does not work. – Klajd Deda Oct 24 '17 at 22:30