appmigrationkit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AppMigrationKit

AppMigrationKit

One-time cross-platform data transfer for app resources. Enables apps to export data from one platform (e.g., Android) and import it on iOS during device setup or onboarding. iOS 26+ / iPadOS 26+ / Swift 6.3.
Beta-sensitive. AppMigrationKit is new in iOS 26 and may change before GM. Re-check current Apple documentation before relying on specific API details.
AppMigrationKit uses an app extension model. The system orchestrates the transfer between devices. The app provides an extension conforming to export and import protocols, and the system calls that extension at the appropriate time. The app itself never manages the network connection between devices.
实现应用资源的一次性跨平台数据传输。支持应用在设备设置或新手引导阶段,从其他平台(例如Android)导出数据并导入到iOS系统。适配iOS 26+ / iPadOS 26+ / Swift 6.3。
测试版注意事项: AppMigrationKit是iOS 26新增的框架,在正式版(GM)发布前可能会发生变更。在依赖具体API细节前,请重新查阅最新的Apple官方文档。
AppMigrationKit采用应用扩展模型,由系统编排不同设备之间的传输流程。应用需要提供符合导出和导入协议的扩展,系统会在合适的时机调用该扩展,应用本身无需管理设备之间的网络连接。

Contents

目录

Architecture Overview

架构概述

AppMigrationKit operates through three layers:
  1. App extension -- An
    AppMigrationExtension
    conforming type that the system invokes during migration. It handles data export and import.
  2. System orchestration -- The OS manages the device-to-device session, transport, and scheduling. The extension does not control when it runs.
  3. Containing app -- After migration completes, the app checks
    MigrationStatus.importStatus
    on first launch to determine whether migration occurred and whether it succeeded.
Key types:
TypeRole
AppMigrationExtension
Protocol for the app extension entry point
ResourcesExportingWithOptions
Protocol for exporting files via archiver
ResourcesExporting
Simplified export protocol (no custom options)
ResourcesImporting
Protocol for importing files on the destination
ResourcesArchiver
Streams files into the export archive
MigrationDataContainer
Access to the containing app's data directories
MigrationStatus
Check import result from the containing app
MigrationPlatform
Identifies the other device's platform (e.g.,
.android
)
MigrationAppIdentifier
Identifies the source app by store and bundle ID
AppMigrationTester
Test-only actor for validating export/import logic
AppMigrationKit通过三层结构运行:
  1. 应用扩展 -- 遵循
    AppMigrationExtension
    协议的类型,系统会在迁移过程中调用该扩展处理数据的导出和导入。
  2. 系统编排层 -- 操作系统管理设备间会话、传输和调度,扩展无法控制自身的运行时机。
  3. 宿主应用 -- 迁移完成后,应用在首次启动时检查
    MigrationStatus.importStatus
    来判断是否发生过迁移以及迁移是否成功。
核心类型:
类型作用
AppMigrationExtension
应用扩展入口点的协议
ResourcesExportingWithOptions
通过归档器导出文件的协议
ResourcesExporting
简化版导出协议(无自定义选项)
ResourcesImporting
在目标设备上导入文件的协议
ResourcesArchiver
将文件流式写入导出归档包
MigrationDataContainer
访问宿主应用的数据目录
MigrationStatus
从宿主应用中查询导入结果
MigrationPlatform
标识另一台设备的平台(例如
.android
MigrationAppIdentifier
通过应用商店和包ID标识源应用
AppMigrationTester
仅用于测试的Actor,用于验证导出/导入逻辑

Setup and Entitlements

设置与权限

Entitlement

权限声明

The app extension requires the
com.apple.developer.app-migration.data-container-access
entitlement. Its value is a single-element string array containing the bundle identifier of the containing app:
xml
<key>com.apple.developer.app-migration.data-container-access</key>
<array>
    <string>com.example.myapp</string>
</array>
No other values are valid. This entitlement grants the extension read access to the containing app's data container during export and write access during import.
应用扩展需要
com.apple.developer.app-migration.data-container-access
权限,其值为仅包含一个字符串元素的数组,内容为宿主应用的包ID:
xml
<key>com.apple.developer.app-migration.data-container-access</key>
<array>
    <string>com.example.myapp</string>
</array>
不支持其他值。该权限授予扩展在导出阶段读取宿主应用数据容器、在导入阶段写入数据容器的权限。

Extension Target

扩展Target

Add a new App Extension target to the Xcode project. The extension conforms to one or more of the migration protocols (
ResourcesExportingWithOptions
,
ResourcesExporting
,
ResourcesImporting
).
在Xcode项目中添加一个新的App Extension target,扩展需要遵循一个或多个迁移协议(
ResourcesExportingWithOptions
ResourcesExporting
ResourcesImporting
)。

App Migration Extension

App迁移扩展

The extension entry point conforms to
AppMigrationExtension
. During migration, the system prevents launching the containing app and its other extensions to ensure exclusive data access.
扩展的入口点遵循
AppMigrationExtension
协议。迁移过程中,系统会禁止启动宿主应用及其它扩展,以保证独占数据访问权限。

Accessing the Data Container

访问数据容器

The extension accesses the containing app's files through
appContainer
:
swift
import AppMigrationKit

struct MyMigrationExtension: ResourcesExportingWithOptions, ResourcesImporting {
    // Access the containing app's directories
    let container = appContainer

    // container.bundleIdentifier     -- app's bundle ID
    // container.containerRootDirectory -- root of the app container
    // container.documentsDirectory    -- Documents/
    // container.applicationSupportDirectory -- Application Support/
}
MigrationDataContainer
provides
containerRootDirectory
,
documentsDirectory
, and
applicationSupportDirectory
as
URL
values pointing into the containing app's sandbox.
扩展通过
appContainer
访问宿主应用的文件:
swift
import AppMigrationKit

struct MyMigrationExtension: ResourcesExportingWithOptions, ResourcesImporting {
    // 访问宿主应用的目录
    let container = appContainer

    // container.bundleIdentifier     -- 应用的包ID
    // container.containerRootDirectory -- 应用容器的根目录
    // container.documentsDirectory    -- Documents/目录
    // container.applicationSupportDirectory -- Application Support/目录
}
MigrationDataContainer
提供
containerRootDirectory
documentsDirectory
applicationSupportDirectory
三个
URL
属性,指向宿主应用沙箱内的对应路径。

Exporting Resources

导出资源

Conform to
ResourcesExportingWithOptions
(or
ResourcesExporting
for no custom options) to package files for transfer. The system calls
exportResources(to:request:)
with a
ResourcesArchiver
and a
MigrationRequestWithOptions
.
遵循
ResourcesExportingWithOptions
协议(无需自定义选项时可遵循
ResourcesExporting
协议)来打包待传输的文件。系统会传入
ResourcesArchiver
MigrationRequestWithOptions
参数,调用
exportResources(to:request:)
方法。

Declaring Export Properties

声明导出属性

swift
struct MyMigrationExtension: ResourcesExportingWithOptions {
    typealias OptionsType = MigrationDefaultSupportedOptions

    var resourcesSizeEstimate: Int {
        // Return estimated total bytes of exported data
        calculateExportSize()
    }

    var resourcesVersion: String {
        "1.0"
    }

    var resourcesCompressible: Bool {
        true  // Let the system compress during transport
    }
}
  • resourcesSizeEstimate
    -- Estimated total bytes. The system uses this for progress UI and free-space checks.
  • resourcesVersion
    -- Format version string. The import side receives this to handle versioned data formats.
  • resourcesCompressible
    -- When
    true
    , the archiver may compress files during transport.
swift
struct MyMigrationExtension: ResourcesExportingWithOptions {
    typealias OptionsType = MigrationDefaultSupportedOptions

    var resourcesSizeEstimate: Int {
        // 返回导出数据的预估总字节数
        calculateExportSize()
    }

    var resourcesVersion: String {
        "1.0"
    }

    var resourcesCompressible: Bool {
        true  // 允许系统在传输过程中压缩数据
    }
}
  • resourcesSizeEstimate
    -- 预估总字节数,系统会用该值计算进度UI和剩余存储空间检查。
  • resourcesVersion
    -- 格式版本字符串,导入端会接收该值来处理不同版本的数据格式。
  • resourcesCompressible
    -- 设为
    true
    时,归档器可以在传输过程中压缩文件。

Implementing Export

实现导出逻辑

swift
func exportResources(
    to archiver: sending ResourcesArchiver,
    request: MigrationRequestWithOptions<MigrationDefaultSupportedOptions>
) async throws {
    let docsDir = appContainer.documentsDirectory

    // Check destination platform if needed
    if request.destinationPlatform == .android {
        // Platform-specific export logic
    }

    // Append files one at a time -- make continuous progress
    let userDataURL = docsDir.appending(path: "user_data.json")
    try await archiver.appendItem(at: userDataURL)

    // Append with a custom archive path
    let settingsURL = docsDir.appending(path: "settings.plist")
    try await archiver.appendItem(at: settingsURL, pathInArchive: "preferences/settings.plist")

    // Append a directory
    let photosDir = docsDir.appending(path: "photos")
    try await archiver.appendItem(at: photosDir, pathInArchive: "media/photos")
}
The archiver streams files incrementally. Call
appendItem(at:pathInArchive:)
repeatedly as each resource is ready. The system may terminate the extension if it appears hung, so avoid long gaps between append calls.
swift
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:)
。如果扩展长时间无响应,系统可能会终止扩展,因此要避免两次append调用之间间隔过长。

Cancellation

取消处理

ResourcesArchiver
handles task cancellation automatically by throwing cancellation errors. Do not catch these errors -- doing so causes the system to kill the extension.
ResourcesArchiver
会自动处理任务取消,抛出取消错误。不要捕获这些错误,否则会导致系统杀死扩展。

Migration Platform

迁移平台

MigrationRequestWithOptions
exposes
destinationPlatform
as a
MigrationPlatform
value. Use this to tailor exported data:
swift
if request.destinationPlatform == .android {
    // Export in a format the Android app expects
}
MigrationPlatform
provides
.android
as a static constant. Custom platforms can be created with
MigrationPlatform("customPlatform")
.
MigrationRequestWithOptions
暴露
destinationPlatform
属性,类型为
MigrationPlatform
,可以用该属性定制导出的数据:
swift
if request.destinationPlatform == .android {
    // 导出为Android应用期望的格式
}
MigrationPlatform
提供
.android
静态常量,也可以通过
MigrationPlatform("customPlatform")
创建自定义平台。

Importing Resources

导入资源

Conform to
ResourcesImporting
to receive transferred files on the destination device. The system calls
importResources(at:request:)
after app installation but before the app is launchable.
swift
struct MyMigrationExtension: ResourcesImporting {
    func importResources(
        at importedDataURL: URL,
        request: ResourcesImportRequest
    ) async throws {
        let sourceVersion = request.sourceVersion
        let sourceApp = request.sourceAppIdentifier

        // sourceApp.platform        -- e.g., .android
        // sourceApp.bundleIdentifier -- source app's bundle ID
        // sourceApp.storeIdentifier  -- e.g., .googlePlay

        // Copy imported files into the app container
        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")
            )
        }
    }
}
遵循
ResourcesImporting
协议在目标设备上接收传输的文件。系统会在应用安装完成后、应用可启动前调用
importResources(at:request:)
方法。
swift
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 -- 源应用的包ID
        // sourceApp.storeIdentifier  -- 例如 .googlePlay

        // 将导入的文件复制到应用容器
        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")
            )
        }
    }
}

Error Handling During Import

导入过程中的错误处理

On import error, the system clears the containing app's data container to prevent partial state. However, app group containers are not cleared. The import implementation should clear any app group containers before writing imported content:
swift
func importResources(
    at importedDataURL: URL,
    request: ResourcesImportRequest
) async throws {
    // Clear shared app group data first
    let groupURL = FileManager.default.containerURL(
        forSecurityApplicationGroupIdentifier: "group.com.example.myapp"
    )
    if let groupURL {
        try? FileManager.default.removeItem(at: groupURL.appending(path: "shared_data"))
    }

    // Then import
    try await performImport(from: importedDataURL)
}
如果导入出错,系统会清空宿主应用的数据容器以避免部分状态残留,但应用组容器不会被清空。因此导入实现应该在写入导入内容前先清空所有应用组容器:
swift
func importResources(
    at importedDataURL: URL,
    request: ResourcesImportRequest
) async throws {
    // 首先清空共享应用组数据
    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)
}

Source App Identifier

源应用标识

ResourcesImportRequest
provides
sourceAppIdentifier
as a
MigrationAppIdentifier
with three properties:
  • platform
    -- The source device's platform (e.g.,
    .android
    )
  • bundleIdentifier
    -- The source app's bundle identifier
  • storeIdentifier
    -- The app store (e.g.,
    .googlePlay
    )
ResourcesImportRequest
提供
sourceAppIdentifier
属性,类型为
MigrationAppIdentifier
,包含三个属性:
  • platform
    -- 源设备的平台(例如
    .android
  • bundleIdentifier
    -- 源应用的包ID
  • storeIdentifier
    -- 应用商店标识(例如
    .googlePlay

Migration Status

迁移状态

After migration completes, the containing app checks the result on first launch:
swift
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
    is
    nil
    if no migration occurred.
  • Call
    clearImportStatus()
    after handling the result to prevent showing the notification on subsequent launches.
  • The enum has two cases:
    .success
    and
    .failure(any Error)
    .
迁移完成后,宿主应用在首次启动时检查迁移结果:
swift
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()
    ,避免后续启动时再次展示迁移通知。
  • 枚举包含两个case:
    .success
    .failure(any Error)

Progress Tracking

进度跟踪

The import side exposes a
Progress
object via
resourcesImportProgress
. The system uses this to display transfer progress to the user. Update
completedUnitCount
incrementally during import:
swift
var resourcesImportProgress: Progress {
    Progress(totalUnitCount: 100)
}

func importResources(
    at importedDataURL: URL,
    request: ResourcesImportRequest
) async throws {
    let progress = resourcesImportProgress
    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)
        progress.completedUnitCount += increment
    }
    progress.completedUnitCount = 100
}
导入端通过
resourcesImportProgress
暴露
Progress
对象,系统会用该对象向用户展示传输进度。导入过程中增量更新
completedUnitCount
swift
var resourcesImportProgress: Progress {
    Progress(totalUnitCount: 100)
}

func importResources(
    at importedDataURL: URL,
    request: ResourcesImportRequest
) async throws {
    let progress = resourcesImportProgress
    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)
        progress.completedUnitCount += increment
    }
    progress.completedUnitCount = 100
}

Testing

测试

AppMigrationTester
is a test-only actor for validating migration logic in unit tests hosted by the containing app. Do not use it in production.
swift
import Testing
import AppMigrationKit

@Test func testExportImportRoundTrip() async throws {
    let tester = try await AppMigrationTester(platform: .android)

    // Export
    let result = try await tester.exportController.exportResources(
        request: nil, progress: nil
    )
    #expect(result.exportProperties.uncompressedBytes > 0)

    // Import the exported data
    try await tester.importController.importResources(
        from: result.extractedResourcesURL,
        importRequest: nil, progress: nil
    )
    try await tester.importController.registerImportCompletion(with: .success)
}
DeviceToDeviceExportProperties
on the result exposes
uncompressedBytes
,
compressedBytes
(nil if not compressible),
sizeEstimate
, and
version
.
See references/appmigrationkit-patterns.md for additional test patterns.
AppMigrationTester
是仅用于测试的Actor,用于在宿主应用托管的单元测试中验证迁移逻辑,不要在生产环境中使用。
swift
import Testing
import AppMigrationKit

@Test func testExportImportRoundTrip() async throws {
    let tester = try await AppMigrationTester(platform: .android)

    // 导出
    let result = try await tester.exportController.exportResources(
        request: nil, progress: nil
    )
    #expect(result.exportProperties.uncompressedBytes > 0)

    // 导入导出的数据
    try await tester.importController.importResources(
        from: result.extractedResourcesURL,
        importRequest: nil, progress: nil
    )
    try await tester.importController.registerImportCompletion(with: .success)
}
返回结果中的
DeviceToDeviceExportProperties
暴露
uncompressedBytes
compressedBytes
(不可压缩时为nil)、
sizeEstimate
version
属性。
更多测试模式请查看 references/appmigrationkit-patterns.md

Common Mistakes

常见错误

DON'T: Catch cancellation errors from ResourcesArchiver

不要:捕获ResourcesArchiver抛出的取消错误

swift
// WRONG -- system kills the extension if cancellation is swallowed
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    do {
        try await archiver.appendItem(at: fileURL)
    } catch is CancellationError {
        // Swallowing this causes termination
    }
}

// CORRECT -- let cancellation propagate
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    try await archiver.appendItem(at: fileURL)
}
swift
// 错误 -- 如果吞掉取消错误,系统会杀死扩展
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    do {
        try await archiver.appendItem(at: fileURL)
    } catch is CancellationError {
        // 吞掉该错误会导致进程终止
    }
}

// 正确 -- 让取消错误向上传播
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    try await archiver.appendItem(at: fileURL)
}

DON'T: Leave long gaps between archiver append calls

不要:两次归档器append调用之间间隔过长

swift
// WRONG -- system may assume the extension is hung and terminate it
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    let allFiles = gatherAllFiles()  // Takes 30 seconds
    for file in allFiles {
        try await archiver.appendItem(at: file)
    }
}

// CORRECT -- interleave file preparation with archiving
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    for file in knownFilePaths() {
        try await archiver.appendItem(at: file)
    }
}
swift
// 错误 -- 系统可能认为扩展已挂起并终止它
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    let allFiles = gatherAllFiles()  // 耗时30秒
    for file in allFiles {
        try await archiver.appendItem(at: file)
    }
}

// 正确 -- 文件准备和归档交错执行
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    for file in knownFilePaths() {
        try await archiver.appendItem(at: file)
    }
}

DON'T: Convert files to intermediate format during export

不要:导出过程中将文件转换为中间格式

swift
// WRONG -- may exhaust disk space creating temporary copies
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    let converted = try convertToJSON(originalDatabase)  // Doubles disk usage
    try await archiver.appendItem(at: converted)
}

// CORRECT -- export files as-is, convert on import side if needed
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    try await archiver.appendItem(at: originalDatabase)
}
swift
// 错误 -- 创建临时副本可能耗尽磁盘空间
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    let converted = try convertToJSON(originalDatabase)  // 磁盘占用翻倍
    try await archiver.appendItem(at: converted)
}

// 正确 -- 按原格式导出文件,如需转换在导入端执行
func exportResources(to archiver: sending ResourcesArchiver, request: ...) async throws {
    try await archiver.appendItem(at: originalDatabase)
}

DON'T: Ignore app group containers during import error recovery

不要:导入错误恢复时忽略应用组容器

swift
// WRONG -- system clears app container but not app groups on error
func importResources(at url: URL, request: ResourcesImportRequest) async throws {
    try writeToAppGroup(data)
    try writeToAppContainer(data)  // If this throws, app group has stale data
}

// CORRECT -- clear app group data before importing
func importResources(at url: URL, request: ResourcesImportRequest) async throws {
    try clearAppGroupData()
    try writeToAppGroup(data)
    try writeToAppContainer(data)
}
swift
// 错误 -- 出错时系统会清空应用容器,但不会清空应用组
func importResources(at url: URL, request: ResourcesImportRequest) async throws {
    try writeToAppGroup(data)
    try writeToAppContainer(data)  // 如果这里抛出错误,应用组会残留旧数据
}

// 正确 -- 导入前先清空应用组数据
func importResources(at url: URL, request: ResourcesImportRequest) async throws {
    try clearAppGroupData()
    try writeToAppGroup(data)
    try writeToAppContainer(data)
}

DON'T: Forget to clear import status after handling it

不要:处理完迁移状态后忘记清空

swift
// WRONG -- migration UI shows every launch
if let status = MigrationStatus.importStatus {
    showMigrationResult(status)
    // Missing clearImportStatus()
}

// CORRECT
if let status = MigrationStatus.importStatus {
    showMigrationResult(status)
    MigrationStatus.clearImportStatus()
}
swift
// 错误 -- 每次启动都会展示迁移UI
if let status = MigrationStatus.importStatus {
    showMigrationResult(status)
    // 缺少clearImportStatus()调用
}

// 正确
if let status = MigrationStatus.importStatus {
    showMigrationResult(status)
    MigrationStatus.clearImportStatus()
}

Review Checklist

审核清单

  • Extension target added with
    com.apple.developer.app-migration.data-container-access
    entitlement
  • Entitlement array contains exactly one string: the containing app's bundle identifier
  • Extension conforms to
    ResourcesExportingWithOptions
    or
    ResourcesExporting
    for export
  • Extension conforms to
    ResourcesImporting
    for import
  • resourcesSizeEstimate
    returns a reasonable byte estimate
  • resourcesVersion
    is set and will be checked on import for format compatibility
  • Export calls
    appendItem
    incrementally without long pauses
  • Cancellation errors from
    ResourcesArchiver
    are not caught
  • Import clears app group containers before writing new data
  • Containing app checks
    MigrationStatus.importStatus
    on first launch
  • clearImportStatus()
    called after handling the migration result
  • AppMigrationTester
    used in unit tests to validate export and import
  • Files are exported as-is without intermediate format conversion on the export side
  • sourceVersion
    from import request used to handle versioned data formats
  • 已添加扩展target,并配置了
    com.apple.developer.app-migration.data-container-access
    权限
  • 权限数组仅包含一个字符串:宿主应用的包ID
  • 导出功能已遵循
    ResourcesExportingWithOptions
    ResourcesExporting
    协议
  • 导入功能已遵循
    ResourcesImporting
    协议
  • resourcesSizeEstimate
    返回合理的字节预估
  • 已设置
    resourcesVersion
    ,导入时会检查该值保证格式兼容性
  • 导出时增量调用
    appendItem
    ,无长时间停顿
  • 没有捕获
    ResourcesArchiver
    抛出的取消错误
  • 导入时写入新数据前已清空应用组容器
  • 宿主应用首次启动时会检查
    MigrationStatus.importStatus
  • 处理完迁移结果后调用了
    clearImportStatus()
  • 单元测试中使用
    AppMigrationTester
    验证导出和导入逻辑
  • 导出端按原格式导出文件,未做中间格式转换
  • 导入时使用请求中的
    sourceVersion
    处理不同版本的数据格式

References

参考资料