另一個分發渠道 — Mac App Store

前一系列介紹了用 Developer ID 直接分發 macOS 應用程式的一次性準備工作。配齊憑證、公證、Sparkle 自動更新和更新 Feed 託管後,使用者無需經過 App Store 即可直接下載 .dmg 檔案。

本系列介紹將同一應用程式上架 Mac App Store (MAS) 的一次性準備工作。兩種分發方式並非二選一,可以同時運營直接分發渠道和 App Store 渠道。App Store 由 Apple 代勞處理付款、退款和搜尋曝光,使用者信任度也更高,因此與直接分發並行運營的情況很常見。

本系列以應用程式已透過 Developer ID 直接分發、且已整合 Sparkle 自動更新為前提。該設定從 Developer ID 直接分發系列第 1 篇開始介紹。若尚未閱讀,建議先行查閱。

為何 MAS 需要「另一個目標」

直接分發的應用程式包含 Sparkle 自動更新——應用程式自行檢查更新 Feed,下載新版本並替換自身的功能。

然而,Mac App Store 的審核規定禁止此類行為。上架 App Store 的應用程式不得從外部下載程式碼更新自身,更新必須且只能透過 App Store 進行。也就是說,提交 MAS 的建置中不能包含 Sparkle

但直接分發建置必須包含 Sparkle。同一建置產物無法同時滿足兩種要求。因此,解決方案是:

同一程式碼庫,兩個建置目標。

渠道建置目標Sparkle分發途徑
Developer IDFocusTimer包含直接分發(.dmg
Mac App StoreFocusTimer MAS排除App Store

共享同一份原始碼,將建置目標分為兩個,只在其中一個中連結 Sparkle。本系列介紹建立第二個目標(FocusTimer MAS)的一次性準備工作。

本系列將完成的內容

共三篇,逐步建置以下內容:

  • (第 1 篇,本文) 註冊 MAS 專用 Bundle ID + 複製 Xcode 建置目標
  • (第 2 篇) 區分兩個渠道的設定檔(entitlements 和 Info.plist)與程式碼分支
  • (第 3 篇) 用於上傳的 ExportOptions-MAS.plist + App Store Connect 註冊 + 建置驗證

系列結束時,你應該已經準備好以下內容:

  • 在 Apple Developer Portal 註冊的 MAS 專用 Bundle ID
  • 已移除 Sparkle 相依性的 FocusTimer MAS 建置目標
  • MAS 專用 entitlements 檔案和 Info.plist 檔案
  • #if canImport(Sparkle) 分支的程式碼
  • 用於 App Store 上傳的 ExportOptions-MAS.plist

範例應用程式 — FocusTimer

與前一系列相同,以虛構的 macOS 應用程式 FocusTimer(管理專注時間的簡單計時器應用程式)為例。FocusTimer、Bundle Identifier com.example.FocusTimer、Team ID ABCDE12345均為範例值,實際使用時請替換為你自己的應用程式和帳戶資訊。

本文基於 Xcode 26 編寫。

第 1 步 — 註冊 MAS 專用 Bundle ID

為何需要獨立的 Bundle ID

若直接分發渠道的 Bundle ID 為 com.example.FocusTimer,則 MAS 渠道使用不同的 Bundle ID

渠道Bundle ID
Developer IDcom.example.FocusTimer
Mac App Storecom.example.FocusTimer.mas

在原有 Bundle ID 後追加 .mas。為何要分開?

  • 同一台 Mac 上兩個渠道可以共存 — Bundle ID 相同時,macOS 會將兩個應用程式識別為同一個並產生衝突。若不同,直接分發版和 App Store 版可以同時安裝在同一台機器上(便於開發和測試)。
  • UserDefaults 網域隔離 — 設定儲存空間以 Bundle ID 為單位劃分,兩個渠道的設定不會混淆。
  • 避免 Launch Services 衝突 — 防止 macOS 在處理「開啟此應用程式」等請求時產生的混亂。

保持直接分發渠道的 Bundle ID 不變。這是識別現有使用者的基準,不能更改。MAS 用的是額外註冊的一個新 ID

註冊流程

  1. 登入 developer.apple.com/account(使用 Apple Developer Program 帳戶)
  2. Certificates, Identifiers & Profiles → 左側選單 Identifiers+ 按鈕
  3. 選擇 App IDsContinue → 類型選擇 AppContinue
  4. Description:輸入易於識別的名稱(如 FocusTimer MAS
  5. Bundle ID
    • 選擇 Explicit(明確)
    • 輸入:com.example.FocusTimer.mas
  6. Capabilities:關閉應用程式不使用的所有功能。若不使用 iCloud、推播通知、App 內購買、Sign in with Apple 等,無需開啟任何項目。(App Sandbox 由 Xcode 端的 entitlements 處理,此處無需開啟。)
  7. ContinueRegister

驗證

Identifiers 清單中顯示 FocusTimer MAS — com.example.FocusTimer.mas 條目,則註冊完成。

第 2 步 — 複製 Xcode 建置目標

現在在 Xcode 專案中建立 FocusTimer MAS 建置目標。最快的方法是複製現有目標。

2-1. 複製 (Duplicate) 目標

  1. 在 Xcode 中開啟 FocusTimer.xcodeproj
  2. 點按 Project navigator(左側面板)最上方的專案圖示(藍色)
  3. 在中間編輯區域的 TARGETS 清單中按右鍵點按 FocusTimerDuplicate
  4. 彈出對話框時選擇 Duplicate Only
  5. 新目標名稱為 FocusTimer copy,雙按名稱將其改為 FocusTimer MAS
  6. 若彈出「是否新增 Scheme?」提示,選擇 Activate(或稍後在 Manage Schemes 中新增)

2-2. 立即修改 Bundle ID

複製後最先要做的是替換 Bundle ID。若保持不變,兩個目標將擁有相同的 ID。

TARGETS → FocusTimer MAS → General → Identity → Bundle Identifier 改為第 1 步中註冊的值:

com.example.FocusTimer.mas

2-3. 關鍵陷阱 — Duplicate 留下的「清理負擔」

這是本篇中最棘手的部分。Xcode 的 Duplicate Target 以**「安全預設值」**運行,但這種安全反而留下了需要手動處理的清理工作。複製完成後需要檢查以下四點:

① 讓新目標包含原始碼檔案

為了安全,Duplicate 會自動將所有原始碼檔案從新目標中排除。若保持不變,FocusTimer MAS 目標就是一個空殼,建置時不會包含任何程式碼。需要重新整理目標成員關係,使新目標重新包含現有原始碼檔案。

② 從 MAS 目標中移除 Sparkle 相依性

Duplicate 會原樣複製原始目標的 Swift Package 相依性。因此 Sparkle 也會隨之進入 FocusTimer MAS 目標。本系列的出發點正是「MAS 建置中不能有 Sparkle」,所以需要從 MAS 目標的套件相依性清單和 Frameworks 建置階段中刪除 Sparkle。

③ 清理暫時性 Info.plist 檔案

Duplicate 會建立類似 FocusTimer copy-Info.plist暫時性 Info.plist 檔案。由於我們將在第 2 篇中單獨建立 MAS 專用的 Info.plist,這些暫時性檔案(包括專案參照和實際檔案)應全部刪除。

④ 更新 MAS 目標的建置設定

Bundle Identifier、Info.plist 路徑、entitlements 路徑等建置設定需要調整為 MAS 專用。此工作將在第 2 篇建立專用設定檔後統一處理。

①·② 大多可透過 Xcode UI 中的目標成員關係和套件相依性介面處理,但若複製過程留下的重複參照無法徹底清除,有時需要直接查看專案檔案(.pbxproj)。清理完成後,分別建置兩個目標,確認 MAS 目標建置中 import Sparkle 是否報錯,是最可靠的驗證方式(第 2 篇中介紹此程式碼分支)。

第 1 篇小結

跟到這裡,你現在已經準備好以下內容:

  • ✅ 在 Apple Developer Portal 註冊了 MAS 專用 Bundle ID(com.example.FocusTimer.mas
  • ✅ 透過複製建立了 FocusTimer MAS 建置目標
  • ✅ 了解了複製留下的清理負擔(原始碼成員關係、Sparkle 相依性、暫時性檔案)

目標這個「容器」已經建立好了。但這個目標目前本質上還只是 FocusTimer 的副本,並未真正成為「MAS 專用」。MAS 建置需要使用與直接分發建置不同的權限 (entitlements)不同的 Info.plist,程式碼也需要分支以便在沒有 Sparkle 時不報錯。

下一篇將介紹區分兩個渠道的設定檔與程式碼分支