概述
XPC Service
是一种整合了GCD
与launchd
的一种轻量级进程间通信机制,其主要目的是提供:权限分离和稳定性。
权限分离:利用xpc服务具有自己的沙箱环境,将应用程序分割为若干个小组件来控制权限,来实现严格的沙箱环境,减小被攻击的风险;其中xpc服务是私有的,仅用于包含它的主应用有效;其运行在更严格的沙箱环境中,如最小的文件访问权限、网络访问等,且不支持将其服务权限提升为
root
权限;稳定性:通过将应用不稳定的功能与应用核心功能分开,来避免稳定性对整个应用的影响;
XPC Service由launchd
来管理其启动、监视及终止,比如崩溃恢复,服务完成或者闲置会被kill -9
终止,更好地管理XPC服务的生命周期。
通过find /System/Library/Frameworks -name \*.xpc
或者find /Applications -name \*.xpc
搜索系统框架及应用下的XPC
服务,可以发现:XPC
被广泛使用在系统框架、系统应用及第三方应用中,如Xcode
、Chrome
、CleanMyMac
等。不过对于iOS
,只能苹果使用对于第三方开发者无法使用(xcode中也未提供相应的模版)。
使用及原理实现
苹果提供了两种处理xpc服务端api:
-
基于纯c实现的服务api(包含在
libSystem
中),包含两部分:xpc.h
,定义了XPC支持的对象和数据类型及其操作接口,以及服务启动、事务管理接口;connection.h
,定义了用于XPC服务连接的建立/激活/暂停、消息发送/响应、连接属性信息获取及设置事件处理程序及目标队列等的接口;
-
基于
Objective-C
实现的NSXPCConnection
接口,提供了远程过程调用机制,允许本地进程通过代理对象调用另一个进程的方法,并自动处理数据序列化及反序列化,主要由以下几部分:-
NSXPCConnection
,用于两个进程的双向通信; -
NSXPCInterface
,用于约定通信双方的调用行为; -
NSXPCListener
,用于监听传入的连接并设定NSXPCListenerDelegate
协议的代理对象来接收处理传入的连接; -
NSXPCListeneEndpoint
,一个可以唯一标识NSXPCListener实例的类,可以使用NSXPCConnection
将其发送到远程进程。这允许一个进程在其他两个进程之间构造一个直接的通信通道;
-
下面将介绍下典型的使用上述接口来创建使用XPC
服务。
创建XPC服务
XPC
服务典型应用就是应用内组件之间通信以实现权限分离保证核心功能稳定性,其创建相对简单,xcode已经提供了相应的模版,编译后会直接添加到相应的应用包中,路径为/Contens/XPCSercices
,其包结构与应用包结构类似,都包含二进制程序、Info.plist
文件及添加的资源文件;
xcode创建服务模版默认使用NSXPCConnection
方式,Info.plist
中配置的XPCService
字典服务类型ServiceType
为Application
,Info.plist
还包含其他定义服务属性及类型字段,如下:
-
CFBundleIdentifier
,服务bundle id
,命名应符合反向DNS风格,如com.fengyunsky.xpc.xpcmain
; -
CFBundlePackageType
,服务类型,必须为XPC!
; -
XPCService
,服务属性字段,包含-
EnvironmentVariables
字典属性来设置环境变量值; -
JoinExistingSession
BOOL
类型属性,用于指示是否需要创建新的安全会话,默认为false
,即创建新会话;若为true
,则可以访问当前用户的keychain
、剪贴板及其他用户的资源及服务; -
RunLoopType
,字符串类型,用于指示服务runloop
类型,默认为dispatch_main
,或者NSRunLoop
; -
ServiceType
,服务类型,默认为Application
,也可以为User
或者System
;
-
dispatch_main()
是pthread_exit
的包装,其主要作用是退出主线程并继续执行其他子线程,对于添加到主队列的block由workqueue
线程来处理;
创建服务并监听连接典型使用如下:
static void XPCService_peer_event_handler(xpc_connection_t peer, xpc_object_t event)
{
xpc_type_t type = xpc_get_type(event);
if (type == XPC_TYPE_ERROR)
{
if (event == XPC_ERROR_CONNECTION_INVALID)
{
//连接无效
}
else if (event == XPC_ERROR_TERMINATION_IMMINENT)
{
//即将终止
}
} else {
//处理连接业务,如发送消息
const char* data = "hello world!";
xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(dictionary, "msg", data, strlen(data));
xpc_connection_send_message(peer, dictionary);
}
}
static void XPCService_event_handler(xpc_connection_t peer)
{
xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
XPCService_peer_event_handler(peer, event);
});
xpc_connection_resume(peer);
}
int main(int argc, const char *argv[]) {
xpc_main(XPCService_event_handler);
// The xpc_main() function never returns.
exit(EXIT_FAILURE);
}
由xpc_main
函数来启动服务并设置事件处理函数,其中事件处理函数XPCService_event_handler
主要设置事件连接处理函数并xpc_connection_resume
恢复连接;默认连接处理队列是DISPATCH_TARGET_QUEUE_DEFAULT
,可以通过xpc_connection_set_target_queue
修改GCD
处理队列;xpc_connect
提供了同步或者异步的发送/接收消息接口,如下:
其中xpc_connection_send_message_with_reply_sync
为同步接口,阻塞直到收到应答消息,其余为异步接口;xpc_connection_send_barrier
可以设定最后一条消息发送完成后的执行block
,类似dispatch
栅栏接口。
注意:消息发送形式必须为字典对象;
消息发送实现如图:
其实质是通过mach_msg
发送消息,即通过mach
消息机制实现;
基于Objective-c
形式如下:
//Objective-C接口形式创建
@interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
@end
@implementation ServiceDelegate
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(xpcmainProtocol)];
xpcmain *exportedObject = [xpcmain new];
newConnection.exportedObject = exportedObject;
[newConnection resume];
return YES;
}
@end
int main(int argc, const char *argv[]) {
MyDelegateClass *myDelegate = ...
NSXPCListener *listener =
[NSXPCListener serviceListener];
listener.delegate = myDelegate;
[listener resume];
// The resume method never returns.
exit(EXIT_FAILURE);
}
NSXPCConnection
相关的类提供更为高级的接口,通过NSXPCListener
来监听服务连接,并通过指定NSXPCListenerDelegate
代理方法listener:shouldAcceptNewConnection:
来处理新的连接请求;NSXPCConnection
属性exportedInterface
及exportedObject
来指定导出接口(通过协议实现)及对象,用于对端服务进程分别通过指定的NSXPCConnection
属性remoteObjectInterface
及remoteObjectProxy
来获取远端约定的导出接口及导出对象,进而实现本地调用远端方法,即远程过程调用;
消息发送
基于c实现的接口
典型使用接口如下:
//创建xpc_connection_t对象
//其中"com.fengyunsky.xpc.demo"为xpc服务的bundleID,需要指定正确否则launchd无法找到相应的服务
_connection = xpc_connection_create("com.fengyunsky.xpc.demo", NULL);
xpc_connection_set_event_handler(_connection, ^(xpc_object_t object){
size_t len = 0;
const char *data = xpc_dictionary_get_data(object, "msg", &len);
fwrite(data, len, 1, stdout);
fflush(stdout);
});
xpc_connection_resume(_connection);
//发送消息
const char *data = "hello world!";
xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(dictionary, "msg", data, strlen(data));
xpc_connection_send_message_with_reply(_connection, dictionary, DISPATCH_TARGET_QUEUE_DEFAULT, ^(xpc_object_t _Nonnull object) {
xpc_type_t type = xpc_get_type(object);
if (type == XPC_TYPE_ERROR)
{
if (object == XPC_ERROR_CONNECTION_INVALID)
{
//连接无效
}
else if (object == XPC_ERROR_TERMINATION_IMMINENT)
{
//即将终止
}
} else {
const void *data = xpc_data_get_bytes_ptr(object);
NSLog(@"reply:%s", (char *)data);
}
});
//消息发送完成后可终止连接
xpc_connection_cancel(_connection);
主要的流程如下:
-
xpc_connection_create
创建xpc
连接对象; -
xpc_connection_set_event_handler
设置连接事件处理函数; -
xpc_connection_resume
启动服务开启通信; -
xpc_connect_send_xxx
调用发送消息接口来异步/同步等待响应消息; -
launchd
守护进程搜索主应用包匹配的CFBundleIdentifier
服务并启动服务程序; - 连接的
xpc
服务程序连接设定的处理函数并调用; - 使用
xpc_connect_send_xxx
响应消息并发送消息; - 如果出现错误,就会通过
xpc_connection_set_event_handler
设置的事件处理函数来处理异常错误; - 可以通过
xpc_connection_suspend
暂停连接,但必须与xpc_connection_resume
配对使用; - 连接完成后,就通过
xpc_connection_cancel
来终止连接;
对于创建的xpc_connection_t
连接对象可通过全局对象保存,用于后续消息同一连接消息发送/接收;
基于Objective-C实现的接口
典型使用如下:
//创建NSXPCConnection对象,指定协议接口,启动连接
_connectionToService = [[NSXPCConnection alloc] initWithServiceName:"com.fengyunsky.xpc.demo"];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(xpcmainProtocol)];
[_connectionToService resume];
//发送消息
[[_connectionToService remoteObjectProxy] upperCaseString:@"hello" withReply:^(NSString *aString) {
// We have received a response. Update our text field, but do it on the main thread.
NSLog(@"Result string was: %@", aString);
}];
//终止连接
[_connectionToService invalidate];
大致的流程如下图;
通过initWithServiceName
来创建NSXPCConnection
连接对象,interfaceWithProtocol
来指定约定的协议方法,resume
来启动连接;当通过remoteObjectProxy
对象来调用xpc服务的方法时,launchd
会搜索应用包中匹配的xpc服务并启动该服务,通过创建xpc服务的代理方法来接口连接,并执行导出接口方法。
总结
相比其他的进程间通信方式,如NSDistributedNotificationCenter
、Mach Port
、域套接字等,XPC
服务实现更轻量,无需管理子进程的生命周期,并且能实现子进程崩溃恢复功能,通过NSXPCConnection
相关的高级接口方便实现远程过程调用,使用更为简洁易用;