macOS上的进程间通信的方案很多,总的来说分为以下三种,如下图所示
- 第一种也是历史最悠久的是基于POSIX的实现,其中常见的是基于Socket (TCP/IP)的实现。同时这种方案和平台无关。
- 第二种基于 底层信号机制,mac系统的内核组件就是使用mach_mag来传递消息。参考
- 第三种就是Foundation库里提供的一些类库。github上有个不错的范例实现基于CFMachPort—https://github.com/Syphon/Simple
XPC方式
估计是苹果觉得这些方案,都不够好,要么不够易用,要么不够安全。所以在2011年的时候推出的全新的XPC进程间通信的方案。
The XPC Services API provides a low-level (libSystem) interprocess communication mechanism based on serialized property lists.
这是一套C的API,范例代码可以参考https://github.com/objcio/issue-14-xpc。
但是这个技术有一个挺大的局限性,XPC进程只能是一个不带界面的控制台进程。关于 XPC 比较特别的尝试可以看看这个代码: https://github.com/OpenEmu/OpenEmuXPCCommunicator。
这可能一般的简单的程序架构是没问题的,但是有些特殊的要求,比如我就要求主程序和子程序都是界面程序 并且可以互相调用,这样子就完全搞不定了,由于XPC这套东西并不开源,所以要实现这种更灵活的互相调用,只能另辟蹊径。
为了实现这种两个界面程序,主程序和子程序可以互相调用的需求推荐就采取 Socket的方式实现,因为这种最标准的方式和操作系统无关,相比其他方案 不会因为 系统API被废弃而 变得不可用。
Socket方式
一个理想的调用关系大概是这样的:
子程序放在主程序的程序包内。
当主程序需要调用子程序时 主程序先创建一个 TCP Server,并且把Server的地址和端口 作为参数 调起子程序(NSTask)。
子程序启动后,获取参数中的TCP地址和端口, 然后创建TCP Client进行连接。然后通过TCP的通信来互相调用。
至此 通信层的功能和大体框架基本确定了。
那应用层应该以何种方式调用呢?是否要定义一个特别的通信规约?
如果定义了通信规约,那每次别人要使用这套框架的时候还要先熟悉一下规约,这就显得不灵活 和 不易用了。有没有可能借用XPC的思路,直接以Objc消息传递的方式来互相调用呢?借助于Objective-C的runtime,这是可以实现的。我个人XPC的内部实现思路估计也是一样的。
先复习一下Objc的消息转发流程:
注意最后一步 forwardInvocation 可以获取函数的所有调用参数。
-(void)forwardInvocation:(NSInvocation *)anInvocation;
假如 主程序利用 forwardInvocation 把需要调用的子程序的 函数信息 序列化 通过 Tcp 传递给子程序,子程序 反序列化后再根据函数信息调用对应功能。这就完成了一个互相调用的过程。
实现对应功能的时候注意一下 NSInvocation 在Swift已经被废弃了,所以相关功能只能使用OC编写。
另外 NSInvocation 在ARC环境下跑的时候有个坑,可以参考一下 nsinvocation-and-arc-automatic-reference-counting。
具体的代码就不放上来,大家有兴趣可以按照上述思路 实现一下看看,一方面可以熟悉一下TCP流程,另一方面也可以熟悉 OC的 转发流程,另外实现完这一套之后 也会 对苹果XPC内部实现有一定的了解。
参考链接:
- https://www.objc.io/issues/14-mac/xpc/
- https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html#//apple_ref/doc/uid/10000172i
- https://www.mikeash.com/pyblog/friday-qa-2009-01-16.html
- https://www.mikeash.com/pyblog/friday-qa-2009-02-20-the-good-and-bad-of-distributed-objects.html