引言
在日常工作中,Mac 电脑上安装的应用主要来自两个渠道:一是通过 App Store 下载,由苹果审核团队保证内容、技术、隐私的合规性;二是从网站下载应用,以 pkg、dmg 格式提供,由安装者自行决定是否信任。这两种渠道下的应用可能在权限、操作方式、数据安全等方面都存在差异,而苹果的技术文档庞杂并且同时面向这两种情况,导致作为开发者,在开发需要上架 App Store 的 Mac 应用时,可能会面临技术、设计和上线合规等挑战。确切了解哪些技术合规、哪些权限需关注变得十分重要。本次分享旨在引导您解决这些挑战,助您成功构建一个适合上架 App Store 的 Mac 应用。
App Sandbox
要通过Mac App Store发布macOS应用,苹果要求必须启用应用沙盒功能。
App Sandbox : 应用程序沙箱是 macOS 在内核级提供并执行的一种访问控制技术。沙箱的主要功能是在用户执行受攻击的应用程序时,限制对系统和用户数据造成的损害。虽然沙箱并不能阻止针对你的应用程序的攻击,但它可以将你的应用程序限制在正常运行所需的最低权限范围内,从而减少攻击成功可能造成的危害。

开启沙盒权限:


如果添加了App Sandbox功能,则会将相应的权利添加到项目的配置中。.entitlements这是添加沙箱后的项目文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">100
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
当应用程序启用沙盒后,会限制其访问系统资源和执行某些操作。以下是一些主要的限制:
- 文件系统访问限制
- 网络访问限制
- 进程间通信限制
- 硬件访问限制
- 脚本执行限制
- 安装软件包限制
从App Sandbox中访问文件
应用程序沙盒通过限制应用程序对受保护资源的访问来提高Mac的整体安全性。 但 MacOS 允许你的应用程序不受限制地访问 Mac文件系统的有限部分。
应用程序对其沙盒容器(~/Library/Containers)有完全的读写权限,也可以运行位于那里的程序。

对于沙盒之外文件的访问,苹果提供了标准的用户交互式访问文件的方式:NSOpenPanel 和 NSSavePanel。操作系统会在一个单独的进程中显示打开和保存面板,并扩展我们应用程序的沙盒,以包括选定的URL ,让我们的应用能够对该URL进行安全范围的访问。
首先需要配置沙盒的文件访问权限为只读或读写:
用户选择文件选项允许访问用户使用 AppKit 的 NSOpenPanel 和 NSSavePanel 选择的任意位置。
xml
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
然后使用NSOpenPanel进行访问。 
通过NSOpenPanel 和 NSSavePanel实现安全范围内的文件访问,会随着程序的关闭而结束。如何让应用程序下次运行时仍旧保持对文件的访问?
Security-Scoped URL 书签持续访问文件
如果想为沙盒应用程序提供对文件系统资源的持久访问,则必须启用security-scoped bookmark。
在沙盒权利文件.entitlements配置
xml
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>

使用Foundation创建一个具有明确安全范围的URL书签,应用程序可以存储 并检索,无论我们的应用程序是否在访问期间退出,都可以在随后访问URL上的资源。
swift
func saveBookmarkWithFilePath(filePath: String) ->Data? {
let url = NSURL.fileURL(withPath: filePath)
//如果应用程序不需要在随后的访问中写入文件,
//请在函数的`options`字段传入`securityScopeAllowOnlyReadAccess`。
let data = try? url.bookmarkData(options: .withSecurityScope)
if let data {
UserDefaults.standard.set(data, forKey: filePath)
UserDefaults.standard.synchronize()
}
return data
}
访问文件的URL书签,必须遵循:解析书签为Url --> 重建书签(if need) --> 开启访问 startAccessingSecurityScopedResource()--> 执行操作 --> 停止访问 stopAccessingSecurityScopedResource()。详见下列代码注释
swift
///访问文件的URL书签
func accessingSecurityScopedResourceWithFilePath(filePath:String)->Bool {
let data = UserDefaults.standard.object(forKey: filePath) as? Data
var success = false
if let data {
var isStale = false
//1.解析书签
let url = try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
if isStale, let url { //2.bookmarkDataIsStale == true 更新书签
let data = try? url.bookmarkData(options: .withSecurityScope)
if let data {
UserDefaults.standard.set(data, forKey: filePath)
UserDefaults.standard.synchronize()
}
}
if let url {
///3. 开启安全范围内的文件访问
success = url.startAccessingSecurityScopedResource()
}
}
return success
}
///4. 执行文件访问操作
///.....
///5. 撤销安全范围内的文件访问
func stopAccessingSecurityScopedResourceWithFilePath(filePath:String) -> Bool {
let data = UserDefaults.standard.object(forKey: filePath) as? Data
var success = false
var isStale = false
if let data {
//1.解析书签
let url = try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
if let url {
///2. 撤销安全范围内的文件访问
url.stopAccessingSecurityScopedResource()
success = true
}
}
return success
}
进程之间共享 URL 书签
应用程序也可以把这个书签传递给另一个进程,比如启动代理或XPC服务。如果要访问接收到的进程中的资源,需要遵循以下示例:
swift
do {
let location = try URL(resolvingBookmarkData: bookmark)
defer {
location.stopAccessingSecurityScopedResource()
}
// Use the resource at the location URL.
}
catch let error {
// Handle any errors.
}
接收进程在解析书签时隐式地试图扩展其沙盒以覆盖书签资源,就像它在解析期间调用startAccessingSecurityScopedResource()一样。要避免在解析书签数据时扩展进程的沙盒,请通过选项 withoutImplicitStartAccessing。
swift
URL(resolvingBookmarkData:bookmarkData, bookmarkDataIsStale: &stale, options: .withoutImplicitStartAccessing)
应用程序组之间共享文件
应用程序组允许单个开发团队开发的多个应用程序访问共享容器并使用进程间通信 (IPC) 进行通信。应用程序可能属于一个或多个应用程序组。作为同一个应用组成员的应用程序都可以访问一个共享容器~/Library/Group\ Containers/,它们可以用来交换文件。它们的标识符格式如下:
swift
//iOS
group.<group name>
//macOS
<team identifier>.<group name>
共享容器位置的获取:
swift
// 格式let groupId = "TEAM_ID.com.domain"
let appGroupID = "88L2Q4487U.com.qihoo"
let sharedContainerFolderURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)
桌面、下载、文稿文件的访问
基于苹果审核准则5.1.1 法律 - 隐私 - 数据收集和存储,当我们访问Downloads、Desktop和Documents目录,需要在info.plist中配置隐私提示,解释应用为何需要访问这些位置。
配置完毕,当我们访问对应目录时,便会弹出对应的授权弹框。 
另外,Downloads还需在Target-> Signing & Capabilities -> App Sandbox中根据需要配置相应文件访问权限。

除此之外,图片、音乐和电影库的目录访问也需要在此处进行配置,这三个目录只有当应用程序需要管理图片、音乐或电影库,才可以写入 ,故而大多数情况下都是只读模式。Mac App Programming Guide
硬件访问
沙盒应用程序需要访问系统上的硬件服务,USB、打印或内置摄像头和麦克风,也需要开启对应的沙盒权利。

对应的沙盒权利文件.entitlements配置
xml
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
同时也不要忘记配置隐私访问的描述字符串,解释应用程序需要访问的原因

当然地址簿、位置、日历这些个人信息与硬件信息一样同属沙盒下受保护的资源,也需要开启权利并配置对应的隐私描述。

网络安全
苹果使用ATS(应用程序传输安全)来提高所有应用程序和应用程序扩展的隐私和数据完整性,要求应用所进行的网络连接必须通过传输层安全(TLS)协议,并使用可靠的证书和密码来确保安全 (HTTPS) 。ATS 会阻止不符合最低安全要求的连接。
ATS 在 iOS 9.0 或 macOS 10.11 或 更高版本的应用中是默认开启的。当应用程序链接到旧版的SDK 时,无论应用程序运行在哪个版本的操作系统上,ATS 都会被禁用。
如果在Mac应用中使用网络请求,则需要先配置权利:

xml
<!-- 允许传出连接 ->
<key>com.apple.security.network.client</key>
<true/>
<!-- 允许传入连接 ->
<key>com.apple.security.network.server</key>
<true/>
如果在应用中要使用HTTP,可以配置应用的元数据info.plist:
- 对所有网络连接禁用ATS,完全使用
HTTP进行网络连接
2. 针对部分内容连接禁用ATS,如:Web Content、Media

- 针对特定域名禁用ATS

进程
在 Mac OS 应用的沙盒环境下,进程间通信(IPC)会变得更加复杂,因为传统的IPC方法可能会受到限制。以下是一些进程间通信的方案:
-
XPC
XPC 是 Apple 提供的一种基于消息传递的进程间通信机制。XPC 支持在应用间创建安全的连接。 在沙盒环境中,XPC 可以通过权限分离进一步提高安全性,将一个应用程序分成更小的部分,负责应用程序的一部分行为。每个XPC服务都有自己的沙盒,所以XPC服务可以更容易实现适当的权限分离。这使得它在沙盒环境中非常安全可行。同时,XPC 也是苹果官方推荐的进程间通信方法。Creating XPC services
-
NSDistributedNotificationCenter
NSDistributedNotificationCenter 允许应用之间发送和接收通知。这种方法在沙箱环境中是有限制的,在苹果文档 Protecting user data with App Sandbox 与沙盒不兼容的条款中有提到:沙盒环境是不能使用
NSDistributedNotificationCenter向其他任务发送 userInfo。 -
App Groups 和共享容器
使用App Groups和共享容器,可以在沙盒应用之间共享文件和数据。虽然不是直接的IPC,无法实时通信,但可以用于共享信息。这个在上文有提到。
-
Mach Ports
Mach ports 是底层的进程间通信机制,用于在Mac OS内核中进行进程通信。它们可以在沙盒应用之间使用,前提是多个进程必须是同一个应用程序组,同时需要谨慎处理权限和隔离。缺点是 Mach ports 过于底层,编程复杂。且苹果文档 Mac Technology Overview 有提到:
Note: Mach ports are another technology for transferring messages between processes. However, messaging with Mach port objects is the least desirable way to communicate between processes. Mach port messaging relies on knowledge of the kernel interfaces, which might change in a future version of OS X. The only time you might consider using Mach ports directly is if you are writing software that runs in the kernel.
-
Sockets
UNIX Domain Sockets 也是一种用于进程间通信的方式,与 Mach ports 情况一样,IPC的进程必须是同一个应用程序组,同时需要谨慎处理苹果关心的沙盒下权限和安全问题。
除了上述五种方式还有共享内存、AppleEvents、Pasteboard等,具体的实现细节可以参考书籍:【Interprocess Communication with macOS - Apple IPC Methods】
最后在引用一段苹果文档 App Groups Entitlement,关于沙盒进程IPC的表述:
Apps within a group can communicate with other members in the group using IPC mechanisms including Mach IPC, POSIX semaphores and shared memory, and UNIX domain sockets. In macOS, use app groups to enable IPC communication between two sandboxed apps, or between a sandboxed app and a non-sandboxed app.
脚本
MacOS 支持的脚本语言有许多:bash、csh、sh、zsh、Perl、PHP、AppleScript等。如果在应用程序中使用这样的脚本语言,则必须将其内置到应用程序中,且只能访问应用程序容器中的数据。使用时注意不能违反以下规定:
- 应用程序必须是独立的、单个应用程序安装包,并且不能在共享位置安装代码或资源
- 下载或安装额外代码或资源以添加功能或改变其主要用途的应用程序将被拒绝
- 请求升级到 root 权限或使用 setuid 属性的应用程序将被拒绝
应用程序内置脚本执行示例
swift
let process = Process()
process.launchPath = "/bin/sh" // 设置执行路径为shell
process.arguments = ["-c", "echo 'Hello from script'"] // 设置要执行的命令
let pipe = Pipe()
process.standardOutput = pipe
process.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let output = String(data: data, encoding: .utf8) {
print("Script Output: \(output)")
}
process.waitUntilExit()
如果需要在沙盒应用程序中嵌入命令行工具,请依照文档 Embedding a command-line tool in a sandboxed app 进行使用。
AppleScript
简述
AppleScript 是专为 macOS 系统设计的强大的脚本语言,可用于自动化和控制应用程序的操作。
应用程序也可以使用 AppleScript 编写脚本来执行操作 或提供数据来响应各种 Apple Event ;Apple Event 是一种进程间的消息,可以封装任意复杂度的命令和数据,正如上文所述使用它也可以实现进程通信。

沙盒限制
MacOS 10.8之前,应用可以使用 AppleScript 向任何应用程序发送 AppleEvents,正如下图展示的,可以轻松获取用户的数据。甚至还能执行 JavaScript 代码。

基于这种安全问题苹果对其进行的限制,要求必须由用户决定是否要运行你的脚本,且应用程序只能在特定文件夹下运行脚本,该文件夹位于~/Library/Application Scripts/ 中,并以bundleId命名。 解决办法:
- 开启沙盒权利
User Selected File to Read/Write - 使用
NSOpenPanel授权该脚本运行的特定文件目录。 - 安装脚本:将来自包内或下载的
AppleScript脚本复制到该授权的特定文件下。 - 程序内执行此脚本任务。
脚本任务执行
Apple Script脚本的执行有两种:
- NSUserAppleScriptTask : MacOS 10.8+ / 应用内执行外部的 Apple Script 文件/ 兼容沙盒/独立进程/异步
- NSAppleScript : macOS 10.2+ / 应用内执行从URL或文本字符串加载的Apple Script代码/ 不支持沙盒 / 当前进程 / 同步
接下来主要以NSUserAppleScriptTask为主,示例如何执行外部的Apple Script文件,假定外部有test.scpt文件位于应用的脚本目录中,功能是为了执行同目录下的脚本文件,以下是test.scpt的内容
sh
on runScript(bundleid,shellFileName)
do shell script "~/Library/'Application Scripts'/" & bundleid & "/" & shellFileName
end runScript
脚本任务执行示例:
objc
- (void)runAppScript {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSError *error = nil;
NSURL *scrptDirUrl = [fileMgr URLForDirectory:NSApplicationScriptsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSURL *appleScriptUrl = [scrptDirUrl URLByAppendingPathComponent:@"test.scpt"];
NSAppleEventDescriptor *listDescriptor = [NSAppleEventDescriptor listDescriptor];
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
NSAppleEventDescriptor *bundleIdDescriptor = [NSAppleEventDescriptor descriptorWithString:bundleId];
[listDescriptor insertDescriptor:bundleIdDescriptor atIndex:1];
NSAppleEventDescriptor *scriptName = [NSAppleEventDescriptor descriptorWithString:@"some.sh"];
[listDescriptor insertDescriptor:scriptName atIndex:2];
//'ascr':AppleScript 事件类,用于执行 AppleScript 脚本 'pbsr',表示运行脚本的事件标识符
ProcessSerialNumber PSN = {0, kCurrentProcess};
NSAppleEventDescriptor *theAddress = [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber
bytes:&PSN length:sizeof(PSN)];
NSAppleEventDescriptor *params = [NSAppleEventDescriptor appleEventWithEventClass:(AEEventClass)'ascr' eventID:(AEEventID)'psbr' targetDescriptor:theAddress returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
//'snam':表示脚本的名称或标识符。
[params setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:@"runScript"] forKeyword:(AEKeyword)'snam'];
//keyDirectObject:---- : 占位符
[params setParamDescriptor:listDescriptor forKeyword:keyDirectObject];
NSUserAppleScriptTask *userAppleScriptTask = [[NSUserAppleScriptTask alloc]initWithURL:appleScriptUrl error:&error];
[userAppleScriptTask executeWithAppleEvent:params completionHandler:^(NSAppleEventDescriptor * _Nullable result, NSError * _Nullable error) {
if (error) {
NSLog(@"执行失败:%@",error);
} else {
NSLog(@"执行成功✅");
}
}];
}
与其他脚本系统结合使用
- 使用
AppleScript执行shell脚本。AppleScript 提供do shell script命令来支持将 shell 命令作为 AppleScript 脚本的一部分执行
sh
# 将当前用户主目录下的文件信息存储到`fileInfo`
set fileInfo to do shell script "cd ~; ls"
- 将 AppleScript 脚本当作 Shell 命令执行
sh
osascript -e 'tell application "Safari" to quit'
参考资料
辅助程序
MacOS系统的辅助程序主要有 LoginItems、LaunchDaemons 和 launchAgents,他们是运行在后台的服务,当MacOS系统启动时,会代表用户启动这些辅助程序,可扩展应用程序的功能或为用户提供额外的功能。以下图片内容来自 Daemons and Services Programming Guide ,阐述了这几种后台服务的区别:

launchd
在介绍这些辅助程序之前我们需要先了解下 launchd,它很重要。
launchd 是Apple Inc.创建的初始化和操作系统服务管理守护程序,详见wikipedia。
launchd 系统主要有两个程序:launchd和launchctl:
-
launchd在系统和用户级别管理守护进程;可以监控守护进程以确保它们保持运行。 需要配置文件.plist文件定义launchd运行的服务的参数,但launchd本身不具备解析的能力 -
launchctl命令行应用程序,解析配置文件,并通过Mach IPC发送launchd可以理解的数据到launchd;还具备加载和卸载守护进程、启动和停止launchd控制的作业的能力等。
当Mac系统关闭时,launchd向所有它启动的守护进程发送一个SIGTERM信号进行关闭。
Login Item
LoginItem 在用户登录时启动,并继续运行,直到用户注销或手动退出。它的主要目的是让用户自动打开经常使用的应用程序。
特点
- 用户身份运行
- 应用程序的一部分
- 兼容沙盒
配置
- 在辅助应用程序包的Info.plist文件中设置LSUIElement或LSBackgroundOnly键

- Target-> Build phases 配置编译产物拷贝到主程序包的特定目录:
/Contents/Library/LoginItems

安装
使用苹果推荐的合法API。
- 使用 CoreServices / LSSharedFileListRef
deprecated macOS(10.5, 10.11) No longer supported - 使用 Service Management / SMLoginItemSetEnabled
deprecated macOS(10.6, 13.0) Please use SMAppService instead - 使用 SMAppService
macOS(13.0, *)详见 Apple 文档
交互合规
Login Item 属于后台服务,当主应用中包含 Login Item 时,UI交互上需要为用户提供可以关闭它的选项。否则便会被苹果审核拒绝。

Launch Daemon & Launch Agents
launchd区分了代理(Launch Agents)和守护进程(Launch Daemon)。
守护进程(Launch Daemon)是严格意义上的后台进程,响应低级别的请求。由 launchd 代表操作系统在系统上下文中进行管理,这意味着它们不知道登录到系统中的用户,守护进程不能直接发起与用户进程的联系;它只能响应用户进程提出的请求。
启动代理(Launch Agents)也由 launchd 管理,但代表当前登录的用户运行(即在用户上下文中)。代理可以与同一用户会话中的其他进程以及系统上下文中的全系统守护进程进行通信。它可以显示一个可视化界面。
Launch Daemon 和 Launch Agents 的行为都是需要在launchd所需要的配置文件.plist中定义,然后系统根据他们存储的位置的不同,将其视为守护进程或代理。
| 位置 | 类型 | 运行权限 | 运行时机 | Provided |
|---|---|---|---|---|
| /System/Library/LaunchDaemons | System Daemons | root / 指定用户 | 开机时 | Apple |
| /System/Library/LaunchAgents | System Agents | 当前登录用户 | 任意用户登录 | Apple |
| /Library/LaunchDaemons | Global Daemons | root / 指定用户 | 开机时 | Administrator |
| /Library/LaunchAgents | Global Agents | 当前登录用户 | 任意用户登录 | Administrator |
| ~/Library/LaunchAgents | User Agents | 当前登录用户 | 指定用户登录时 | User |
特点
Launch Daemon
- 根用户身份运行 通常是系统中的单一实例
- 系统中的单一实例
- 独立运行
- 任何可执行程序(命令行、捆绑程序或脚本)
- 不支持沙盒
Remove the App Sandbox capability (if present). The App Sandbox is, as the name suggests, not appropriate for daemons. Signing a daemon with a restricted entitlement
Launch Agents
- 以特定用户身份运行
- 系统中通常有多个实例(一个用户一个进程)
- 独立运行
- 任何可执行文件(命令行、捆绑程序或脚本)
- 不支持沙盒
配置
在macOS系统中,守护进程本身由一个描述守护进程的配置(.plist)和 可执行文件表示:

守护进程的配置(.plist)主要的键:
- Label : 包含一个唯一的字符串,用于标识守护进程。 (
required) - ProgramArguments : 包含用于启动守护程序的参数。(
required) - KeepAlive :这个键指定了守护进程是按需启动还是必须一直运行。建议把守护进程设计成按需启动。(
optional) - RunAtLoad :指定作业应该何时运行,true 表示它被加载后立即运行。(
optional) - 更多键...... 参见launchd man page
示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.myprogram</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/myprogram</string>
<string>--arg1</string>
<string>--arg2</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
启动过程
Launch Daemon
系统启动和内核运行后,会运行 launchd 来完成系统初始化。作为初始化的一部分,它会经历以下步骤:
- 从
/System/Library/LaunchDaemons/和/Library/LaunchDaemons/中的plist文件加载每个按需启动的系统级守护进程的参数 - 注册这些守护进程请求的套接字和文件描述符。
- 启动任何要求一直运行的守护进程。
- 当对特定服务的请求到达时,它会启动相应的守护程序并将请求传递给它。
- 当系统关闭时,它会向它启动的所有守护进程发送 SIGTERM 信号。
Launch Agents 用户代理的启动和守护进程相似,当用户登录时,将启动每个用户的 Launchd, 执行以下操作:
- 从
/System/Library/LaunchAgents、/Library/LaunchAgents和~/Library/LaunchAgents中的plist文件加载每个按需启动的用户代理的参数。 - 注册这些用户代理请求的套接字和文件描述符。
- 启动任何请求始终运行的用户代理。
- 当对特定服务的请求到达时,它启动相应的用户代理并将请求传递给它。
- 用户注销时,它会向其启动的所有用户代理发送 SIGTERM 信号。
安装
macOS 13 之前
- 根据服务类型将其对应的 launchd 配置文件 plist 通过脚本放到目录 /Library/LaunchDaemons 或 /Library/LaunchAgents 、 ~/Library/LaunchAgents
- 准备好守护进程或代理要运行的可执行文件
- 使用 launchctl bootstrap + params 立即启动服务 详见
macOS 13 及以后
在 macOS 13 及更高版本中,应用程序捆绑包中的新结构简化了相关属性列表的安装。这种新结构允许你将辅助应用程序资源保存在应用程序捆绑包内,从而减少了对专门安装脚本或将文件写入系统目录权限的需求。
- 在应用包中安装辅助程序可执行文件,例如在 Contents/Resources
- 在应用程序 Contents/Library/LaunchAgents 目录下安装 LaunchAgent 配置文件 。
- 在应用程序 Contents/Library/LaunchDaemons 目录下安装 LaunchDaemon 配置文件。
- 在代理和守护程序配置文件 plist 中,将键
Program替换为BundleProgram并设置相对于包的路径,例如:Contents/Resources/mydaemon
使用新的框架 SMAppService 进行管理。 详见 Apple 文档。
启动提速
正如上文所述,这些辅助程序会随着系统启动或用户登录时进行启动,太多的启动程序可能会导致Mac速度变慢并且启动时间更长。
Login Item
登录项可以在系统的偏好设置中进行查看并设置是否关闭。具体流程如下图。

Launch Daemon & Launch Agents
通过 Finder -> 前往 ---> 前往文件 ,可以前往 /Library/LaunchDaemons 、/Library/LaunchAgents 、 ~/Library/LaunchAgents 目录下,检查是否有未使用或可疑配置项,并移动废纸篓。

参考资料
updating_helper_executables_from_earlier_versions_of_macos
Signing a daemon with a restricted entitlement
Daemons and Services Programming Guide
其他注意事项
创建DMG/PKG
Important: The authorization services API is not supported within an app sandbox because it allows privilege escalation. developer.apple.com/library/arc...
默认情况下,应用程序以当前登录用户的身份运行。不同的用户在访问文件、更改系统范围设置等方面拥有不同的权限,具体取决于他们是管理员用户还是普通用户。有些任务需要额外的权限,甚至超出了管理员用户默认情况下可以执行的权限。具有此类附加权限的应用程序或其他进程被称为以提升的权限运行。使用 root 或管理员运行代码特权会加剧安全漏洞带来的危险。本章解释了风险,提供了特权提升的替代方案,并描述了在无法避免时如何安全地提升特权。
参考
Preventing Insecure Network Connections
IOS Using App Groups for communication between macOS/iOS apps from the Same Vendor
Best Practices for Submitting Scriptable and AppleScript Apps to the Mac App Store