How to send a message from XPC helper app to main application?
Asked Answered
B

1

21

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. :)

Bluh answered 7/2, 2014 at 9:11 Comment(5)
What have you tried in terms of calling back the main program? Did you try using exportedMethods and exportedObjects from the main program?German
Yes, I am experimenting with remoteObjectInterface from XPC service and exportedObjects from main application. I would be glad if you can provide an example.Bluh
Show us what you have tried and we will attempt to help you fix it.German
#24041265Millen
#24041265Millen
B
25

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.

Berthaberthe answered 4/3, 2014 at 12:32 Comment(4)
What if there are several connections to the processor? How do I know which connection called doProcessing so I could report progress to it?Huoh
@Huoh you could pass another argument which identifies the process to doProcessing.Berthaberthe
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?Triplet
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.Millen

© 2022 - 2024 — McMap. All rights reserved.