macOS 多应用开发

macOS 的一个 App 中可以有多个 Helper App,最典型的应用就是做应用自启动这个功能时,主工程中包含一个自启动的 Helper App。在自启动时,由 Helper App 来启动主 App。

还有一些应用场景,比如 QQ 的截图功能是作为一个 Helper App 独立运行的。在 Activity Monitor 中可以看到:

在 QQ.app 中也能看到,QQ jietu plugin 作为一个单独的 Helper App 存在:

QQ 的截图设置中可以设置在退出 QQ 后截图功能继续保持运行,就是通过把截图功能作为一个独立的 Helper App 实现的。

实现

工程配置

实现过程参考自 The Launch At Login Sandbox Project

首先将工程设置为 Workspace

然后在 Workspace 中新建一个 Helper App 的 Project,然后把 Helper App 的 Project 拖到主 Project 下面,像这样:

接下来设置主工程的 Target,在 Build Phases 中设置,编译时把 HelperApp 复制到最终生成的 .app 包中:

Target Dependencies 中也添加对应的 Helper App:

在 Helper App 的 Build Settings 中,设置 Skip Install 为 Yes:

这个设置的作用是在打包项目时, Archive 中只有一个 App, 否则是无法提交到 App Store 的

接下来在生成的 .app 中就可以看到我们的 Helper App 了:

如果要启动 Helper App,可以使用如下代码

1
2
3
4
5
6
7
8
NSString *path = [[NSBundle mainBundle] bundlePath];
NSArray *p = [path pathComponents];
NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:p];
[pathComponents addObject:@"Contents"];
[pathComponents addObject:@"MacOS"];
[pathComponents addObject:@"Helper.app"];
NSString *appPath = [NSString pathWithComponents:pathComponents];
[[NSWorkspace sharedWorkspace] launchApplication:appPath];

共享代码

把需要共享的代码放到一个 Framework 工程中,主 App 和 Helper App 都引用这个 Framework,就可以实现共用代码了。

但是这样的缺点是 Framework 在最终的 App 包中会被会重复包含,主 App 的程序包中有一份 Framework,Helper App 的程序包中也会有一份,如果包含资源文件的话,也会有两份。

暂时没找到其他更好的方式,如果有谁知道更好的方法,欢迎分享。

共享资源

共享资源最容易的方法就是在工程跟目录建立一个文件夹,把要共享的 Assets.xcassets 以及其他资源文件都放进去,把这个文件夹分别拖入 HelperApp 和主 App ,选择 CreateGroup,建立引用。

国际化资源也可以这样共享。

缺点与共享代码一样,会有两份。

共享沙盒、进程间通讯

工程配置

使用 Application Groups 来进行 Helper App 与主 App 共享沙盒。

配置过程蛮简单,在苹果开发者网页上注册一个 App Group,Xcode 的主 App 和 Helper App 的 Capabilities 中打开 App Groups,填入对应的 App Group Id,就配置好了。这里有一点要注意的是,官方的 Application Groups 教程中,说 .entitlements 的文件里对应的 App Group 字段应该是如下形式:

1
2
3
4
5
<key>com.apple.security.application-groups</key>
<array>
<string>DG29478A379Q6483R9214.HolstFirstAppSuite</string>
<string>DG29478A379Q6483R9214.HolstSecondAppSuite</string>
</array>

可参考 OneV’s 的博文,按照下面这样,输入自定义的 App Group 的 ID 就可以了

共享 UserDefaults

使用如下代码可以获取到 App 间共用的 UserDefaults

1
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@"group.com.xxx.xxx"];

App Groups 的 UserDefaults 存储的路径:

关于一个报错:

1
2
[User Defaults] Couldn't read values in CFPrefsPlistSource ....
Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

直接忽略即可,https://forums.developer.apple.com/thread/51348#discussion-186721

Helper App 获取 主 App 以前的 UserDefaults 内容是无法直接获取的到的,我们需要把以前主 App 的 UserDefaults 移动到 App Groups 的 UserDefaults 才行,Migrating to App Groups

共享文件访问权限

本来主 App 通过 Security-Scoped Bookmark 获得了一个文件夹的访问权限,现在 HelperApp 也要得到这个文件访问权限时,需要

进程间通信

暂略

IPC and POSIX Semaphores and Shared Memory

参考

Adding Login Items

The Launch At Login Sandbox Project

Application Groups

Reading NSUserDefaults from helper app in the sandbox

iOS学习笔记-APP之间数据共享空间_APPGroup

[Mac编程之:进程间通信两种方式——NSDistributedNotificationCenter和NSConnection