使用 AppMigrationKit 跨平台轉移 App 資料。適用於實作系統協調調度的 iOS 與 Android(或其他平台)間一次性轉移、開發 AppMigrationExtension、利用 ResourcesArchiver 打包可轉移資源、在目標裝置上匯入資源、回報匯入進度、處理轉移錯誤與 App Group 清除作業、檢查 MigrationStatus,或使用 AppMigrationTester 測試轉移程式碼。
AppMigrationKit
App 資源的一次性跨平台資料轉移方案。讓 App 能在裝置設定或開機導覽(onboarding)期間,匯出資料至另一個平台(例如 Android)或從中匯入資料。AppMigrationKit API 適用於 iOS 26.0+ / iPadOS 26.0+;資料容器權限(data-container entitlement)則適用於 iOS 26.1+ / iPadOS 26.1+ / Mac Catalyst 26.1+。Swift 6.3。
Beta 測試版注意事項:AppMigrationKit 為 iOS 26 新增功能,在 GM 正式版釋出前可能有所變動。在依賴特定 API 細節前,請重新確認最新的 Apple 官方說明文件。
AppMigrationKit 採用 App Extension(應用程式擴充套件)架構模型。系統會協調調度裝置之間的轉移作業。App 需提供符合匯出與匯入協定(protocol)的 Extension,系統會在適當的時機呼叫該 Extension。App 本身絕不直接管理裝置間的網絡連線。
目錄
架構總覽
AppMigrationKit 分為三個層級運作:
- App extension -- 符合
AppMigrationExtension協定的型別,系統會在轉移期間呼叫它,負責處理資料匯出與匯入。 - 系統協調調度(System orchestration) -- 由 OS 管理裝置對裝置的連線階段(session)、傳輸與排程。Extension 無法控制自身的執行時機。
- 主 App(Containing app) -- 轉移完成後,App 在首次啟動時會檢查
MigrationStatus.importStatus,以確認是否發生過轉移以及轉移是否成功。
主要型別:
| 型別 | 角色 |
|---|---|
AppMigrationExtension |
App extension 進入點的協定 |
ResourcesExportingWithOptions |
透過封檔器(archiver)匯出檔案的協定 |
ResourcesExporting |
簡化版匯出協定(不含自訂選項) |
ResourcesImporting |
在目標裝置匯入檔案的協定 |
ResourcesArchiver |
將檔案串流寫入匯出封檔 |
MigrationDataContainer |
存取主 App 的資料目錄 |
MigrationStatus |
從主 App 檢查匯入結果 |
MigrationPlatform |
識別另一台裝置的平台(例如 .android) |
MigrationAppIdentifier |
透過商店與 Bundle ID 識別來源 App |
AppMigrationTester |
僅供測試使用、用來驗證匯出/匯入邏輯的 Actor |
設定與 Entitlements
Entitlement
App extension 需要配置 com.apple.developer.app-migration.data-container-access Entitlement。其值為僅包含單一元素的字串陣列,內容為主 App 的 Bundle Identifier:
<key>com.apple.developer.app-migration.data-container-access</key>
<array>
<string>com.example.myapp</string>
</array>
其他值皆無效。此 Entitlement 能賦予 Extension 在匯出期間對主 App 資料容器的讀取權限,以及在匯入期間的寫入權限。雖然 AppMigrationKit 核心 API 支援 iOS 26.0+ 和 iPadOS 26.0+,但此 Entitlement 本身僅支援 iOS 26.1+、iPadOS 26.1+ 及 Mac Catalyst 26.1+。
Extension Target
在 Xcode 專案中新增 App Extension target。該 Extension 需符合一個或多個轉移協定(ResourcesExportingWithOptions、ResourcesExporting、ResourcesImporting)。
App Migration Extension
Extension 的進入點符合 AppMigrationExtension。轉移期間,系統會阻止啟動主 App 及其其他 Extension,以確保獨佔資料存取權。
存取資料容器
Extension 透過 appContainer 存取主 App 的檔案:
import AppMigrationKit
struct MyMigrationExtension: ResourcesExporting {
var resourcesSizeEstimate: Int { estimateTotalExportSize() }
var resourcesVersion: String { "1.0" }
var resourcesCompressible: Bool { true }
func exportResources(
to archiver: sending ResourcesArchiver,
request: MigrationRequest
) async throws {
let container = appContainer
// container.bundleIdentifier -- App 的 Bundle ID
// container.containerRootDirectory -- App 容器根目錄
// container.documentsDirectory -- Documents/
// container.applicationSupportDirectory -- Application Support/
}
}
MigrationDataContainer 提供 containerRootDirectory、documentsDirectory 和 applicationSupportDirectory,這些皆為指向主 App 沙盒內部的 URL 值。
匯出資源
實作 ResourcesExportingWithOptions(若無自訂選項則實作 ResourcesExporting)以打包傳輸檔案。系統會傳入 ResourcesArchiver 與 MigrationRequestWithOptions 並呼叫 exportResources(to:request:)。
宣告匯出屬性
struct MyMigrationExtension: ResourcesExportingWithOptions {
typealias OptionsType = MigrationDefaultSupportedOptions
var resourcesSizeEstimate: Int {
// 回傳預估的匯出資料總 Bytes 數
calculateExportSize()
}
var resourcesVersion: String {
"1.0"
}
var resourcesCompressible: Bool {
true // 允許系統在傳輸過程中進行壓縮
}
}
resourcesSizeEstimate-- 預估的總位元組數(Bytes)。系統會以此作為進度 UI 展示與剩餘空間檢查的依據。resourcesVersion-- 格式版本字串。匯入端會接收此資訊以處理具備版本控管的資料格式。resourcesCompressible-- 當值為true時,封檔器可在傳輸過程中壓縮檔案。
實作匯出
func exportResources(
to archiver: sending ResourcesArchiver,
request: MigrationRequestWithOptions<MigrationDefaultSupportedOptions>
) async throws {
let docsDir = appContainer.documentsDirectory
// 必要時檢查目標平台
if request.destinationPlatform == .android {
// 平台專屬的匯出邏輯
}
// 逐一附加檔案 -- 保持持續性的處理進度
let userDataURL = docsDir.appending(path: "user_data.json")
try await archiver.appendItem(at: userDataURL)
// 使用自訂的封檔路徑進行附加
let settingsURL = docsDir.appending(path: "settings.plist")
try await archiver.appendItem(at: settingsURL, pathInArchive: "preferences/settings.plist")
// 附加整個目錄
let photosDir = docsDir.appending(path: "photos")
try await archiver.appendItem(at: photosDir, pathInArchive: "media/photos")
}
封檔器會以串流方式漸進式打包檔案。當每個資源準備就緒時,重複呼叫 appendItem(at:pathInArchive:)。若 Extension 看起來像卡住無回應,系統可能會將其終止,因此請避免在多次 append 呼叫之間產生過長的空檔。
取消機制
ResourcesArchiver 透過拋出取消錯誤(cancellation errors)自動處理任務取消。請勿捕捉(catch)這些錯誤 -- 否則系統會直接強制關閉(kill)Extension。
轉移平台
MigrationRequestWithOptions 將 destinationPlatform 開放為 MigrationPlatform 型別的值。可用此客製化匯出的資料:
if request.destinationPlatform == .android {
// 以 Android App 預期的格式匯出
}
MigrationPlatform 提供靜態常數 .android。亦可使用 MigrationPlatform("customPlatform") 建立自訂平台。
匯入資源
實作 ResourcesImporting 以在目標裝置上接收傳輸的檔案。系統會在安裝 App 後、但 App 尚未可啟動之前呼叫 importResources(at:request:)。
struct MyMigrationExtension: ResourcesImporting {
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
let sourceVersion = request.sourceVersion
let sourceApp = request.sourceAppIdentifier
// sourceApp.platform -- 例如 .android
// sourceApp.bundleIdentifier -- 來源 App 的 Bundle ID
// sourceApp.storeIdentifier -- 例如 .googlePlay
// 將匯入的檔案複製到 App 容器中
let docsDir = appContainer.documentsDirectory
let userData = importedDataURL.appending(path: "user_data.json")
if FileManager.default.fileExists(atPath: userData.path()) {
try FileManager.default.copyItem(
at: userData,
to: docsDir.appending(path: "user_data.json")
)
}
}
}
匯入期間的錯誤處理
發生匯入錯誤時,系統會清空主 App 的資料容器以避免留下殘缺狀態。然而,App Group 容器並不會被自動清空。匯入實作應在寫入匯入內容前先自行清空所有 App Group 容器:
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
// 先清除共享的 App Group 資料
let groupURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.example.myapp"
)
if let groupURL {
try? FileManager.default.removeItem(at: groupURL.appending(path: "shared_data"))
}
// 接著執行匯入
try await performImport(from: importedDataURL)
}
來源 App 識別碼
ResourcesImportRequest 提供 sourceAppIdentifier 作為 MigrationAppIdentifier,包含三個屬性:
platform-- 來源裝置的平台(例如.android)bundleIdentifier-- 來源 App 的 Bundle IdentifierstoreIdentifier-- App 商店(例如.googlePlay)
轉移狀態
轉移完成後,主 App 會在首次啟動時檢查結果:
import AppMigrationKit
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if let status = MigrationStatus.importStatus {
switch status {
case .success:
showMigrationSuccessUI()
MigrationStatus.clearImportStatus()
case .failure(let error):
showMigrationFailureUI(error: error)
MigrationStatus.clearImportStatus()
}
}
return true
}
- 若未進行任何轉移,
MigrationStatus.importStatus為nil。 - 處理完結果後請呼叫
clearImportStatus(),以避免在後續啟動時重複顯示通知。 - 此列舉(enum)有兩個 Case:
.success與.failure(any Error)。
進度追蹤
匯入端透過 resourcesImportProgress 開放 Progress 物件。系統會用它來向使用者展示傳輸進度。請在匯入過程中漸進式更新 completedUnitCount:
struct MyMigrationExtension: ResourcesImporting {
private let importProgress = Progress(totalUnitCount: 100)
var resourcesImportProgress: Progress { importProgress }
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
let files = try FileManager.default.contentsOfDirectory(
at: importedDataURL, includingPropertiesForKeys: nil
)
let increment = Int64(100 / max(files.count, 1))
for file in files {
try processFile(file)
importProgress.completedUnitCount += increment
}
importProgres






