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

后端 未结 3 1262
孤街浪徒
孤街浪徒 2021-01-30 02:55

According to Apple, the new XPC Services API, introduced in Lion, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (G

3条回答
  •  清酒与你
    2021-01-30 03:20

    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 
    @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)object forProtocol:(Protocol *)proto;
    - (id) initListenerWithDelegate:(id)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.

    1. Your main app starts its helper The helper starts listening using its bundleID <--- Important!
    2. The main app listens for a global notification and then sends a message
    3. 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

提交回复
热议问题