Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Here is how I am doing Bi-Directional IPC using XPC.</p> <p>The Helper (login item) is the server or listener. The main app or any other app are considered clients.</p> <p>I created the following manager:</p> <p>Header:</p> <pre><code>@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 </code></pre> <p>Implementation:</p> <pre><code>@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 </code></pre> <p>Obviously, I am using some Category methods which are self explanatory. For example:</p> <pre><code>@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 </code></pre> <p>Okay, using this I set myself up an interface for both the client-side and server-side. The header looks like this:</p> <pre><code>@class CommXPCManager; @protocol AppXPCErrorHandler &lt;NSObject&gt; @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&lt;AppXPCErrorHandler&gt;)object forProtocol:(Protocol *)proto; - (id) initListenerWithDelegate:(id&lt;AppXPCErrorHandler&gt;)object forProtocol:(Protocol *)proto; - (void) observeListenerHello:(CommReceptionistNoteBlock)helloBlock; - (void) removeListenerObserver; - (void) startClientConnection; - (void) startListenerConnection; - (void) stopConnection; @end </code></pre> <p>Here is the implementation to start the listener:</p> <pre><code>- (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]; } </code></pre> <p>Here is the implementation to start the client:</p> <pre><code>- (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]; }; } </code></pre> <p>Now here is the order of things.</p> <ol> <li>Your main app starts its helper The helper starts listening using its bundleID &lt;--- Important! </li> <li>The main app listens for a global notification and then sends a message</li> <li>When the client sends a message the connection is established</li> </ol> <p>Now the server can send messages to the client and the client can send messages to the server (with or without a reply).</p> <p>It's very fast, it works well, and is designed for OS X 10.7.3 or greater.</p> <p>A few notes:</p> <ul> <li>The name of the helper must be the same name as the bundle ID</li> <li>The name must begin with your team ID</li> <li>For sandboxing, both the Main app and Helper app application group setting must be start with prefix of the helper Bundle ID </li> </ul> <p>e.g. Helper bundle id is: ABC123XYZ.CompanyName.GroupName.Helper App Group ID will be: ABC123XYZ.CompanyName.GroupName</p> <p>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.</p> <p>Ok, hope this helps. Arvin</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload