How to send a message from XPC helper app to main application? - macos

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

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.

Related

OSX LaunchAgent LaunchDaemon Communication over XPC

I'm trying to communicate my xpc service from LaunchDaemon.
LaunchDaemon is command line executable and LaunchAgent is xpc service.
Initiating my connection from main service(LaunchDaemon) like this:
NSXPCConnection * _connectionToService;
_connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:#"com.XpcService" options:NSXPCConnectionPrivileged];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(XpcServiceProtocol)];
_connectionToService.interruptionHandler = ^{ NSLog(#"Connection Terminated"); };
_connectionToService.invalidationHandler = ^{ NSLog(#"Connection Invalidated"); };
[_connectionToService resume];
//here calling required functions
Xpc-service listening like this:
#interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
#end
#implementation ServiceDelegate
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection: (NSXPCConnection *)newConnection {
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(XpcServiceProtocol)];
XpcService *exportedObject = [XpcService new];
newConnection.exportedObject = exportedObject;
[newConnection resume];
return YES;
}
#end
int main(int argc, const char *argv[]) {
ServiceDelegate *delegate = [ServiceDelegate new];
NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:#"com.XpcService"];
listener.delegate = delegate;
[listener resume];
return 0;
}
In my case i'm getting Connection Invalidated error, my xpc couldn't even be started.
LaunchAgent & LaunchDaemon loaded perfectly, code signing was also be done. help me to find out what might be caused the problem? Thanks in advance.

XPC Between two cocoa applications in workspace, the NSXPCConnection is immediately being invalidated

I have two Cocoa Applications, one is going to be the sender and another the receiver in this XPC relationship.
In the applicationDidFinishLaunching in the sender, I first open the second receiver application
NSError* error = nil;
NSURL* url = [[NSBundle mainBundle] bundleURL];
url = [url URLByAppendingPathComponent:#"Contents" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"MacOS" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"TestXPCHelper.app" isDirectory:YES];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:url
options:NSWorkspaceLaunchWithoutActivation
configuration:[NSDictionary dictionary]
error:&error];
if ( error )
{
NSLog(#"launchApplicationAtURL:%# error = %#", url, error);
[[NSAlert alertWithError:error] runModal];
}
Then I create my NSXPCConnection
assert([NSThread isMainThread]);
if (self.testConnection == nil) {
self.testConnection = [[NSXPCConnection alloc] initWithMachServiceName:NEVER_TRANSLATE(#"com.TechSmith.TestXPCHelper") options:NSXPCConnectionPrivileged];
self.testConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
self.testConnection.interruptionHandler = ^{
NSLog(#"Connection Terminated");
};
self.testConnection.invalidationHandler = ^{
self.testConnection.invalidationHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.testConnection = nil;
}];
};
[self.testConnection resume];
}
Then I try to send a message over the connection (the connection is already invalidated by here)
id<TestXPCProtocol> testRemoteObject= [self.testConnection remoteObjectProxy];
[testRemoteObject testXPCMethod2];
[[self.testConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError){
NSLog(#"%#", proxyError);
}] testXPCMethod:^(NSString* reply) {
NSLog(#"%#", reply);
}];
And here is the app delegate for my receiver application:
#interface AppDelegate () <NSXPCListenerDelegate, TestXPCProtocol>
#property (weak) IBOutlet NSWindow *window;
#property NSXPCListener *xpcListener;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"TESTING123");
self.xpcListener = [[NSXPCListener alloc] initWithMachServiceName:#"com.TechSmith.TestXPCHelper"];
self.xpcListener.delegate = self;
[self.xpcListener resume];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
NSLog(#"ACTIVE234");
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)run
{
NSLog(#"RUNNING");
// Tell the XPC listener to start processing requests.
[self.xpcListener resume];
// Run the run loop forever.
[[NSRunLoop currentRunLoop] run];
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
NSLog(#"LISTENING");
assert(listener == self.xpcListener);
#pragma unused(listener)
assert(newConnection != nil);
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
- (void)testXPCMethod:(void(^)(NSString * version))reply
{
NSLog(#"HEY");
reply(#"REPLY HERE");
}
- (void)testXPCMethod2
{
NSLog(#"TWO!");
}
Here is the proxyError when I try to send a message over the connection:
Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service
named com.TechSmith.TestXPCHelper was invalidated." UserInfo={NSDebugDescription=The
connection to service named com.TechSmith.TestXPCHelper was invalidated.}
So I think I am doing something wrong with my instantiation of the NSXPCConnection. I can't find a good example of two applications speaking to eachother-- it's always one application and a service. Is that what my problem is? I need a service inbetween the applications talking?
Is there any way to get more information on why this connection is being invalidated? That would also help a lot
So pretty straight forward problem here,
Turns out initWithMachServiceName is explicitly looking for a mach service. I was using an identifier of another application process.
If I actually use an identifier of a valid mach service, there is no issue
Note that there are two other ways to create an NSXPCConnection,
with an NSXPCEndpoint or with a XPCService identifier

Why do NSFilePresenter protocol methods never get called?

I am trying to monitor file changes in local and iCloud directories and have implemented the NSFilePresenter protocol methods but the only method that gets called is presentedItemAtURL.
Am I correct in assuming that I should be able to monitor a local or an iCloud directory and get notified any time any process adds, modifies or deletes a file in the directory.
Here is the basic code for the OS X App:
- (void)awakeFromNib {
_presentedItemURL = myDocumentsDirectoryURL;
_presentedItemOperationQueue = [[NSOperationQueue alloc] init];
[_presentedItemOperationQueue setMaxConcurrentOperationCount: 1];
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
}
- (NSURL*) presentedItemURL {
FLOG(#" called %#", _presentedItemURL);
return _presentedItemURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
FLOG(#" called");
return _presentedItemOperationQueue;
}
- (void)presentedItemDidChange {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
-(void)accommodatePresentedItemDeletionWithCompletionHandler:(void (^)(NSError *errorOrNil))completionHandler
{ FLOG(#" called");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self reloadData];
}];
completionHandler(nil);
}
-(void)presentedSubitemDidChangeAtURL:(NSURL *)url {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
-(void)presentedSubitemDidAppearAtURL:(NSURL *)url {
FLOG(#" called");
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
});
}
Long time ago, I know, but perhaps this will still help. NSFilePresenter will only notify you about changes made by another process that makes changes to a directory or file USING AN NSFileCoordinator. If another process (eg: iTunes file sharing) makes changes without an NSFileCoordinator, you won't be notified.
This is in no way my final implementation and I will edit/update as I improve. But since there is nil examples on how to do this, i figured i'd share something that works!!! That's right, it works. I am able to read the file in my app, and at the same time make a change in textedit and the changes propagate to my app. Hope this helps bud.
PBDocument.h
#interface PBDocument : NSObject <NSFilePresenter>
#property (nonatomic, strong) NSTextView *textView;
#pragma mark - NSFilePresenter properties
#property (readonly) NSURL *presentedItemURL;
#property (readonly) NSOperationQueue *presentedItemOperationQueue;
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView;
#end
PBDocument.m
#interface PBDocument ()
#property (readwrite) NSURL *presentedItemURL;
#property (readwrite) NSOperationQueue *presentedItemOperationQueue;
#property (readwrite) NSFileCoordinator *fileCoordinator;
#end
#implementation PBDocument
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError *__autoreleasing *)outError textView:(NSTextView*)textView {
self = [super init];
if (self) {
_textView = textView;
_presentedItemURL = url;
_presentedItemOperationQueue = [NSOperationQueue mainQueue];
[NSFileCoordinator addFilePresenter:self];
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
[self readWithCoordination];
}
return self;
}
- (void)readWithCoordination {
NSError *error = nil;
[self.fileCoordinator coordinateReadingItemAtURL:_presentedItemURL options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *newURL) {
NSLog(#"Coordinating Read");
NSError *error = nil;
NSFileWrapper *wrapper = [[NSFileWrapper alloc] initWithURL:newURL options:0 error:&error];
if (!error) {
[self readFromFileWrapper:wrapper ofType:[self.presentedItemURL pathExtension] error:&error];
}
if (error) #throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:#"%#", error] userInfo:nil];
}];
if (error) #throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:#"%#", error] userInfo:nil];
}
- (void)presentedItemDidChange {
[self readWithCoordination];
}
#end
If it's any help to anyone this is the approach (FSEvents) I ended up using recently for a file sync solution and it seems to work for any file system. I have not done any research recently on NSFileCoordinator to see whether this is better worse or what the use cases are as a comparison.
I also did not test every use case so your mileage may vary.
https://github.com/eonil/FSEvents

Is possible to use Mac OS X XPC like IPC to exchange messages between processes? How?

According to Apple, the new XPC Services API, introduced in Lion, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (GCD) and launchd.
It seems possible to use this API as a kind of IPC, like the POSIX IPC, however, I cannot find how to do it.
I am trying to communicate two processes using the XPC API so I can pass messages between them but I always get a "XPC connection invalid" error in the server side.
I don't want an XPC Service, I just want to exchange messages using a client-server architecture.
I am using two BSD-like processes, so there is no Info.plist or whatever...
I have been following this discussion http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html but this topic seems a bit obscure and undocumented.
Thanks!
Yes, that is possible, but not the way you'd expect.
You can not have a (non launchd) process vend a service. That is for security reasons, since it would make it easy to do man-in-the-middle attacks.
You can still achieve what you want, though: You have to set up a launchd service that vends an XPC / mach service. Both process A and B then connect to your launchd service. Process A can then create a so called anonymous connection and send that to the launchd service which will forward it to process B. Once that has happened, processes A and B can talk to each other directly through that connection (i.e. the launchd service can exit without the connection breaking).
This may seem round-about, but it's necessary for security reasons.
See the xpc_object(3) man page for details about anonymous connections.
It's a bit counter intuitive, because process A will create a listener object with xpc_connection_create(). A then creates an endpoint object from the listener with xpc_endpoint_create() and sends that endpoint across the wire (over XPC) to process B. B can then turn that object into a connection with xpc_connection_create_from_endpoint(). A's event handler for the listener will then receive a connection object matching the connection that B created with xpc_connection_create_from_endpoint(). This works similar to the way that the event handler of xpc_connection_create_mach_service() will receive connection objects when clients connect.
Here is how I am doing Bi-Directional IPC using XPC.
The Helper (login item) is the server or listener. The main app or any other app are considered clients.
I created the following manager:
Header:
#class CommXPCManager;
typedef NS_ENUM(NSUInteger, CommXPCErrorType) {
CommXPCErrorInvalid = 1,
CommXPCErrorInterrupted = 2,
CommXPCErrorTermination = 3
};
typedef void (^XPCErrorHandler)(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error);
typedef void (^XPCMessageHandler)(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message);
typedef void (^XPCConnectionHandler)(CommXPCManager *peerConnection);
#interface CommXPCManager : NSObject
#property (readwrite, copy, nonatomic) XPCErrorHandler errorHandler;
#property (readwrite, copy, nonatomic) XPCMessageHandler messageHandler;
#property (readwrite, copy, nonatomic) XPCConnectionHandler connectionHandler;
#property (readonly, nonatomic) BOOL clientConnection;
#property (readonly, nonatomic) BOOL serverConnection;
#property (readonly, nonatomic) BOOL peerConnection;
#property (readonly, nonatomic) __attribute__((NSObject)) xpc_connection_t connection;
#property (readonly, strong, nonatomic) NSString *connectionName;
#property (readonly, strong, nonatomic) NSNumber *connectionEUID;
#property (readonly, strong, nonatomic) NSNumber *connectionEGID;
#property (readonly, strong, nonatomic) NSNumber *connectionProcessID;
#property (readonly, strong, nonatomic) NSString *connectionAuditSessionID;
- (id) initWithConnection:(xpc_connection_t)aConnection;
- (id) initAsClientWithBundleID:(NSString *)bundleID;
- (id) initAsServer;
- (void) suspendConnection;
- (void) resumeConnection;
- (void) cancelConnection;
- (void) sendMessage:(NSDictionary *)dict;
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event;
#end
Implementation:
#interface CommXPCManager ()
#property (readwrite, nonatomic) BOOL clientConnection;
#property (readwrite, nonatomic) BOOL serverConnection;
#property (readwrite, nonatomic) BOOL peerConnection;
#property (readwrite, strong, nonatomic) __attribute__((NSObject)) dispatch_queue_t dispatchQueue;
#end
#implementation CommXPCManager
#synthesize clientConnection, serverConnection, peerConnection;
#synthesize errorHandler, messageHandler, connectionHandler;
#synthesize connection = _connection;
#synthesize dispatchQueue = _dispatchQueue;
#pragma mark - Message Methods:
- (void) sendMessage:(NSDictionary *)dict {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message( _connection, message );
xpc_release( message );
});
}
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message_with_reply( _connection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! #discussion Reply: XPC Error */
reply( [NSDictionary dictionary], [NSError errorFromXObject:object] );
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! #discussion Reply: XPC Dictionary */
reply( [NSDictionary dictionaryFromXObject:object], nil );
}
}); xpc_release( message );
});
}
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event {
xpc_object_t message = [dict xObjectReply:event];
xpc_connection_t replyConnection = xpc_dictionary_get_remote_connection( message );
xpc_connection_send_message( replyConnection, message );
xpc_release( message );
}
#pragma mark - Connection Methods:
- (void) suspendConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_suspend( _connection ); });
}
- (void) resumeConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_resume(_connection); });
}
- (void) cancelConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_cancel(_connection); });
}
#pragma mark - Accessor Overrides:
- (void) setDispatchQueue:(dispatch_queue_t)queue {
if ( queue ) dispatch_retain( queue );
if ( _dispatchQueue ) dispatch_release( _dispatchQueue );
_dispatchQueue = queue;
xpc_connection_set_target_queue( self.connection, self.dispatchQueue );
}
#pragma mark - Getter Overrides:
- (NSString *) connectionName {
__block char* name = NULL;
dispatch_sync(self.dispatchQueue, ^{ name = (char*)xpc_connection_get_name( _connection ); });
if(!name) return nil;
return [NSString stringWithCString:name encoding:[NSString defaultCStringEncoding]];
}
- (NSNumber *) connectionEUID {
__block uid_t uid = 0;
dispatch_sync(self.dispatchQueue, ^{ uid = xpc_connection_get_euid( _connection ); });
return [NSNumber numberWithUnsignedInt:uid];
}
- (NSNumber *) connectionEGID {
__block gid_t egid = 0;
dispatch_sync(self.dispatchQueue, ^{ egid = xpc_connection_get_egid( _connection ); });
return [NSNumber numberWithUnsignedInt:egid];
}
- (NSNumber *) connectionProcessID {
__block pid_t pid = 0;
dispatch_sync(self.dispatchQueue, ^{ pid = xpc_connection_get_pid( _connection ); });
return [NSNumber numberWithUnsignedInt:pid];
}
- (NSNumber *) connectionAuditSessionID{
__block au_asid_t auasid = 0;
dispatch_sync(self.dispatchQueue, ^{ auasid = xpc_connection_get_asid( _connection ); });
return [NSNumber numberWithUnsignedInt:auasid];
}
#pragma mark - Setup Methods:
- (void) setupConnectionHandler:(xpc_connection_t)conn {
__block CommXPCManager *this = self;
xpc_connection_set_event_handler( conn, ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! #discussion Client | Peer: XPC Error */
NSError *xpcError = [NSError errorFromXObject:object];
if ( object == XPC_ERROR_CONNECTION_INVALID ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInvalid, xpcError );
} else if ( object == XPC_ERROR_CONNECTION_INTERRUPTED ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInterrupted, xpcError );
} else if ( object == XPC_ERROR_TERMINATION_IMMINENT ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorTermination, xpcError );
}
xpcError = nil; return;
} else if ( type == XPC_TYPE_CONNECTION ) {
/*! #discussion XPC Server: XPC Connection */
CommXPCManager *xpcPeer = [[CommXPCManager alloc] initWithConnection:object];
if ( this.connectionHandler )
this.connectionHandler( xpcPeer );
xpcPeer = nil; return;
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! #discussion Client | Peer: XPC Dictionary */
if ( this.messageHandler )
this.messageHandler( this, object, [NSDictionary dictionaryFromXObject:object] );
}
});
}
- (void) setupDispatchQueue {
dispatch_queue_t queue = dispatch_queue_create( xpc_connection_get_name(_connection), 0 );
self.dispatchQueue = queue;
dispatch_release( queue );
}
- (void) setupConnection:(xpc_connection_t)aConnection {
_connection = xpc_retain( aConnection );
[self setupConnectionHandler:aConnection];
[self setupDispatchQueue];
[self resumeConnection];
}
#pragma mark - Initialization:
- (id) initWithConnection:(xpc_connection_t)aConnection {
if ( !aConnection ) return nil;
if ( (self = [super init]) ) {
self.peerConnection = YES;
[self setupConnection:aConnection];
} return self;
}
- (id) initAsClientWithBundleID:(NSString *)bundleID {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [bundleID UTF8String], nil, 0 );
if ( (self = [super init]) ) {
self.clientConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
- (id) initAsServer {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [[[NSBundle mainBundle] bundleIdentifier] UTF8String],
dispatch_get_main_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER );
if ( (self = [super init]) ) {
self.serverConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
#end
Obviously, I am using some Category methods which are self explanatory.
For example:
#implementation NSError (CategoryXPCMessage)
+ (NSError *) errorFromXObject:(xpc_object_t)xObject {
char *description = xpc_copy_description( xObject );
NSError *xpcError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:#{
NSLocalizedDescriptionKey:
[NSString stringWithCString:description encoding:[NSString defaultCStringEncoding]] }];
free( description );
return xpcError;
}
#end
Okay, using this I set myself up an interface for both the client-side and server-side. The header looks like this:
#class CommXPCManager;
#protocol AppXPCErrorHandler <NSObject>
#required
- (void) handleXPCError:(NSError *)error forType:(CommXPCErrorType)errorType;
#end
static NSString* const kAppXPCKeyReturn = #"AppXPCInterfaceReturn"; // id returnObject
static NSString* const kAppXPCKeyReply = #"AppXPCInterfaceReply"; // NSNumber: BOOL
static NSString* const kAppXPCKeySEL = #"AppXPCInterfaceSelector"; // NSString
static NSString* const kAppXPCKeyArgs = #"AppXPCInterfaceArguments"; // NSArray (Must be xObject compliant)
#interface AppXPCInterface : NSObject
#property (readonly, strong, nonatomic) CommXPCManager *managerXPC;
#property (readonly, strong, nonatomic) NSArray *peerConnections;
- (void) sendMessage:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (void) sendMessageToPeers:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (id) initWithBundleID:(NSString *)bundleID andDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (id) initListenerWithDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (void) observeListenerHello:(CommReceptionistNoteBlock)helloBlock;
- (void) removeListenerObserver;
- (void) startClientConnection;
- (void) startListenerConnection;
- (void) stopConnection;
#end
Here is the implementation to start the listener:
- (void) startListenerConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsServer];
__block AppXPCInterface *this = self;
self.managerXPC.connectionHandler = ^(CommXPCManager *peerConnection) {
[(NSMutableArray *)this.peerConnections addObject:peerConnection];
peerConnection.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
peerConnection.errorHandler = ^(CommXPCManager *peer, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
[(NSMutableArray *)this.peerConnections removeObject:peer];
};
};
[CommReceptionist postGlobalNote:kAppXPCListenerNoteHello];
}
Here is the implementation to start the client:
- (void) startClientConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsClientWithBundleID:self.identifierXPC];
__block AppXPCInterface *this = self;
self.managerXPC.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
self.managerXPC.errorHandler = ^(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
};
}
Now here is the order of things.
Your main app starts its helper The helper starts listening using
its bundleID <--- Important!
The main app listens for a global notification and then sends a message
When the client sends a message the connection is established
Now the server can send messages to the client and the client can send messages to the server (with or without a reply).
It's very fast, it works well, and is designed for OS X 10.7.3 or greater.
A few notes:
The name of the helper must be the same name as the bundle ID
The name must begin with your team ID
For sandboxing, both the Main app and Helper app application group setting must be start with prefix of the helper Bundle ID
e.g.
Helper bundle id is:
ABC123XYZ.CompanyName.GroupName.Helper
App Group ID will be:
ABC123XYZ.CompanyName.GroupName
There are additional details I left out so as not to bore anyone. But if it's still unclear just ask and I will answer.
Ok, hope this helps.
Arvin
Alright for anyone that has been struggling with this, I was finally able to 100% get communication working between two application processes, using NSXPCConnection
The key to note is that you can only create an NSXPCConnection to three things.
An XPCService. You can connect to an XPCService strictly through
a name
A Mach Service. You can also connect to a Mach Service
strictly through a name
An NSXPCEndpoint. This is what we're
looking for, to communicate between two application processes.
The problem being that we can't directly transfer an NSXPCListenerEndpoint from one application to another.
It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCListenerEndpoint property. One application can connect to the machservice, and set that property to it's own [NSXPCListener anonymousListener].endpoint
Then the other application can connect to the machservice, and ask for that endpoint.
Then using that endpoint, an NSXPCConnection can be created, which successfully established a bridge between the two applications. I have tested sending objects back and forth, and it all works as expected.
Note that if your application is sandboxed, you will have to create an XPCService, as a middle man between your Application and the Machservice
I'm pretty pumped that I got this working-- I'm fairly active in SO, so if anybody is interested in source code, just add a comment and I can go through the effort to post more details
Some hurdles I came across:
You have to launch your machservice, these are the lines:
OSStatus err;
AuthorizationExternalForm extForm;
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
NSLog(#"SUCCESS AUTHORIZING DAEMON");
}
assert(err == errAuthorizationSuccess);
Boolean success;
CFErrorRef error;
success = SMJobBless(
kSMDomainSystemLaunchd,
CFSTR("DAEMON IDENTIFIER HERE"),
self->_authRef,
&error
);
Also, every time you rebuild your daemon, you have to unload the previous launch agent, with these bash commands:
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool
(With your corresponding identifiers, of course)

Intercept web requests from a WebView Flash plugin

I've got a desktop browser app which uses a WebView to host a Flash plugin. The Flash plugin makes regular requests to an external website for new data, which it then draws as fancy graphics.
I'd like to intercept these web requests and get at the data (so I can display it via Growl, instead of keeping a desktop window around). But best I can tell, requests made by Flash don't get picked up by the normal WebView delegates.
Is there another place I can set a hook? I tried installing a custom NSURLCache via [NSURLCache setSharedURLCache] but that never got called. I also tried method swizzling a few of the other classes (like NSCachedURLResponse) but couldn't find a way in. Any ideas? Many thanks!
Surprised no one answered this, it is actually pretty easy. Create a subclass of NSURLProtocol, and then call registerClass to start intercepting.
[NSURLProtocol registerClass:[MyCustomURLProtocol class]];
Here are the important bits of the subclass:
#define REQUEST_HEADER_TAG #"x-mycustomurl-intercept"
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
// Check for the custom header on the request to break the
// infinite loop created by the [startLoading] below.
if ([theRequest valueForHTTPHeaderField:REQUEST_HEADER_TAG]) {
return NO;
}
if ([theRequest.URL.scheme caseInsensitiveCompare:#"http"] == NSOrderedSame) {
return YES;
}
return NO;
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest
{
return theRequest;
}
- (id)initWithRequest:(NSURLRequest*)theRequest
cachedResponse:(NSCachedURLResponse*)cachedResponse
client:(id<NSURLProtocolClient>)client
{
// Add a custom header on the request to break the
// infinite loop created by the [startLoading] below.
NSMutableURLRequest* newRequest = [theRequest mutableCopy];
[newRequest setValue:#"" forHTTPHeaderField:REQUEST_HEADER_TAG];
// Now continue the process with this "tagged" request
self = [super initWithRequest:theRequest
cachedResponse:cachedResponse
client:client];
if (self) {
// capture the data received
[self setRequest:newRequest];
receivedData = [[NSMutableData data] retain];
}
[newRequest release];
return self;
}
- (void)dealloc
{
[connection release];
[request release];
[receivedData release];
[super dealloc];
}
- (void)startLoading
{
// Load the data off the web as usual, but set myself up as the delegate
// so I can intercept the response data as it comes in.
[self setConnection:[NSURLConnection connectionWithRequest:request delegate:self]];
}
- (void)stopLoading
{
[connection cancel];
}
#pragma mark NSURLConnection delegate implementation
- (void)connection:(NSURLConnection*)conn
didReceiveResponse:(NSURLResponse*)response
{
[[self client] URLProtocol:self
didReceiveResponse:response
cacheStoragePolicy:[request cachePolicy]];
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[[self client] URLProtocol:self didLoadData:data];
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)conn
{
[[self client] URLProtocolDidFinishLoading:self];
[self setConnection:nil];
if (requestTag != 0) {
if (requestDelegate &&
[requestDelegate respondsToSelector:
#selector(finishedLoadingData:forURL:taggedWith:)]) {
[requestDelegate finishedLoadingData:receivedData
forURL:[request URL]
taggedWith:requestTag];
}
}
}
- (void)connection:(NSURLConnection*)conn didFailWithError:(NSError*)error
{
[[self client] URLProtocol:self didFailWithError:error];
[self setConnection:nil];
}

Resources