跳到正文
返回博客
·约 1 分钟读完

Mac App Store 2.4.5 拒审:沙盒存文件与自带更新检查

Guideline 2.4.5 是 Mac App Store 应用的额外规则清单,其中两条最常卡住独立开发者的 Mac 应用。Apple 就 2.4.5(i)(文件存进沙盒容器)和 2.4.5(vii)(自带更新检查)给我们的原话,以及各自的改法。

Guideline 2.4.5 是在 Mac App Store 上架的应用要额外遵守的一组规定。我们的 Mac 小工具撞过其中两条:(i) "They must be appropriately sandboxed"(必须正确使用沙盒),和 (vii) "They must use the Mac App Store to distribute updates; other update mechanisms are not allowed."(更新必须通过 Mac App Store 分发,不允许其他更新机制)。这两条在本地构建和测试里都不会报错,因为它们管的是分发渠道,而不是功能对不对。

2.4.5(i):用户文件存进了容器

Apple 怎么说。 我们的一款 Mac 应用把导出的文件存在了自己的沙盒容器里。Apple 的原话是 "The app saves user data to the app's container, which is not user accessible"(应用把用户数据存进了用户无法访问的容器),并引用了 Apple 自己的沙盒指南:"the container is not for user documents"(容器不是放用户文档的地方)。给出的改法是 "save user files to a location selected by or available to users, using standard Save dialogs"(用标准的存储对话框,把文件存到用户选择或能访问的位置)。

为什么会这样。 沙盒应用直接往 Documents 目录写文件,写入是成功的,文件也确实存在,所以什么都不会报错。只是它落在了应用的容器里,一个用户从来不会打开的隐藏位置。容器是给数据库、缓存和应用自用数据准备的,不是给用户的作品准备的。

怎么改。

  • AppKit: 用 NSSavePanel 让用户选位置。Apple 文档说明,存储面板运行在独立进程里,保存后 macOS 会把这个文件加入应用的沙盒授权,所以应用可以写入它返回的 URL。
let panel = NSSavePanel()
panel.nameFieldStringValue = "Notes.txt"
panel.allowedContentTypes = [.plainText]
if panel.runModal() == .OK, let url = panel.url {
    try text.write(to: url, atomically: true, encoding: .utf8)
}
  • SwiftUI: 用 fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:) 修饰符,配合一个 FileDocument。
  • 权限: 在 Xcode 里开启 App Sandbox,把 User Selected File 设为 Read/Write(com.apple.security.files.user-selected.read-write),应用才能写入用户选定的位置。

(i) 的另一半:权限开得太宽。 我们另外还有两次 2.4.5(i) 拒审,原因是权限开得比应用实际需要的宽。提交前读一遍签名后构建的 entitlements,确认没有 get-task-allow(这一项很容易漏),任何一句话解释不清用途的权限都删掉。

2.4.5(vii):应用里自带更新检查

Apple 怎么说。 我们的一款 Mac 小工具带了“检查更新”功能。Apple 写道 "The app contains frameworks or APIs that may be used to update the app outside of the Mac App Store"(应用包含可在 Mac App Store 之外更新应用的框架或 API),以及 "You should not provide additional update checks or updates through the app."(不应在应用内提供额外的更新检查或更新。)审核还附了截图,标出它出现的位置。

为什么会这样。 同一份代码常常走两条渠道:官网直接下载的版本需要自己的更新器,而 Mac App Store 本来就会提醒用户更新。更新器默认就跟着进了商店版。

怎么改。 注意措辞:Apple 点名的是框架和 API,不只是菜单项。光把按钮藏起来不够。

  1. 给直接下载版单独建一个配置或 target,在 Xcode 的 Active Compilation Conditions 里加一个 Swift 编译条件,比如 DIRECT_DOWNLOAD。
  2. 把更新相关代码和菜单项包进 #if DIRECT_DOWNLOAD ... #endif。
  3. 更新框架只链接进直接下载版的 target,商店版二进制里根本不包含它。
  4. 商店版里的“检查更新”设置项、偏好和引导文案也一并去掉。

一分钟自检

拿准备上传的那个 archive:在应用里保存一次,确认弹出了存储对话框让你选位置;打开应用菜单和设置,确认没有更新相关的选项;列出包内 Contents/Frameworks 目录,确认没有更新框架;再用 codesign -d --entitlements - YourApp.app 打印签名后的权限,逐行看一遍。

Mac 应用还有一种 iPhone 应用碰不到的设计类拒审:关掉主窗口后没法再打开。更多发布检查见 工具箱页面;最常见的首次拒审是 2.1 应用完整性。

以上来自 Obelisk Club 自己的送审记录。Apple 的措辞和工具会变,请以当前 App Review 指南和 Apple 文档为准。

作者

独立开发者,在 Obelisk Club 做本地优先的 Mac 与 iOS 效率应用。 关于作者 →