Port your Mac app to Apple silicon
System & Services 进阶 26m

将你的 Mac 应用移植到 Apple Silicon

Port your Mac app to Apple silicon

2020年6月24日

在 Apple 官方观看视频

一句话判断

把 Mac 应用移植到 Apple Silicon 在大多数情况下就是”在 Xcode 12 中重新编译”这么简单——但那些”不简单”的部分(Intel 汇编、32位代码、硬编码架构假设)才是真正需要你花时间的地方。

这场 Session 讲了什么

这是 WWDC 2020 最具历史意义的 Session 之一。Apple 宣布 Mac 将从 Intel 处理器过渡到自研的 Apple Silicon(基于 ARM 架构),这场 Session 详细讲解了开发者需要做什么来确保应用在新架构上正常运行。

Session 将移植工作分为三个难度层级。第一层:“直接重编译”——使用纯 Swift/Objective-C 和标准框架的应用,只需在 Xcode 12 中选择 arm64 目标重新编译即可。Apple 声称大部分应用(约 80%)属于这一层。第二层:“小改动”——使用了少量架构相关代码的应用(如硬编码的字节序假设、sizeof 指针),需要修几行代码。第三层:“重写”——大量使用了 Intel 汇编、Intel 特定指令(如 AVX)、或者 32 位代码的应用,需要较深的重构。

Session 还详细介绍了 Universal Binary 的创建流程。Universal Binary 同时包含 x86_64 和 arm64 代码,Apple 推荐所有开发者都分发 Universal Binary 以确保在 Intel Mac 和 Apple Silicon Mac 上都能原生运行。创建方法非常简单:在 Xcode 的 Build Settings 中将 Architectures 设置为 Standard Architectures (Apple Silicon, Intel) 即可。

值得深挖的点

字节序(Endianness)和指针大小的隐含假设

Intel x86_64 是小端序(little-endian),ARM 也是小端序,所以字节序不是问题。但指针大小可能在某些场景下有影响——虽然 x86_64 和 arm64 都是 64 位架构,但如果你在某些数据结构中假设 sizeof(void*) == 8 并且用了硬编码的偏移量,移植时需要验证。Session 建议用编译器的 #if arch(arm64) 条件来处理架构差异。

Rosetta 2 的能力与局限

Rosetta 2 可以在 Apple Silicon Mac 上运行 Intel 应用,大部分情况下用户感知不到差异。但有几个限制:不支持虚拟化(不能运行 Intel 虚拟机)、不支持内核扩展(KEXT)、某些 JIT 编译器的性能可能下降。如果你的应用依赖这些能力,必须提供原生 arm64 版本。

代码片段

// 条件编译处理架构差异
import Foundation

struct PlatformAdapter {
    
    // 编译期架构检测
    static var isAppleSilicon: Bool {
        #if arch(arm64)
        return true
        #elseif arch(x86_64)
        return false
        #endif
    }
    
    // 运行时检测是否通过 Rosetta 运行
    static var isRunningUnderRosetta: Bool {
        var size = 0
        sysctlbyname("sysctl.proc_translated", nil, &size, nil, 0)
        return size > 0
    }
    
    // 架构相关的优化路径
    static func optimizedProcessing(data: [Float]) -> [Float] {
        #if arch(arm64)
        // Apple Silicon: 使用 Accelerate 框架的 ARM NEON 优化
        return accelerateProcessing(data)
        #elseif arch(x86_64)
        // Intel: 使用 Accelerate 框架的 Intel 优化
        return accelerateProcessing(data)
        #endif
    }
    
    static func accelerateProcessing(_ data: [Float]) -> [Float] {
        // Accelerate 框架在两种架构上都有优化实现
        // vDSP 和 BNNS 会自动选择最佳路径
        var result = [Float](repeating: 0, count: data.count)
        var scalar: Float = 2.0
        vDSP_vsmul(data, 1, &scalar, &result, 1, vDSP_Length(data.count))
        return result
    }
}
// Universal Binary 的构建配置
/*
 Xcode 12 中的配置步骤:
 
 1. 打开项目的 Build Settings
 2. 找到 "Architectures" 设置
 3. 设置为 "Standard Architectures (Apple Silicon, Intel)"
    - 这会编译为 Universal Binary (x86_64 + arm64)
 
 4. 验证构建结果:
    lipo -archs MyUniversalApp
    输出: x86_64 arm64
    
 5. 检查单个架构的切片:
    lipo -info MyUniversalApp
    输出: Architectures in the fat file: x86_64 arm64
 
 命令行构建:
 xcodebuild -project MyProject.xcodeproj \
     -scheme MyScheme \
     -configuration Release \
     -destination 'platform=macOS' \
     ONLY_ACTIVE_ARCH=NO \
     ARCHS="x86_64 arm64" \
     build
 */
// 检测和处理代码中的架构假设
struct MigrationChecker {
    
    // 修复: 指针大小硬编码
    // 反模式:
    // let offset = 8  // 假设指针是 8 字节
    // 正确做法:
    static let pointerSize = MemoryLayout<UnsafeRawPointer>.size
    
    // 修复: 数据结构的对齐假设
    struct NetworkPacket {
        var header: UInt32
        // 反模式:假设特定的 padding
        // var padding: UInt32  
        // 正确做法:使用 Swift 的内存布局工具
        var payload: UInt64
        
        static var offsetOfPayload: Int {
            MemoryLayout<NetworkPacket>.offset(of: \.payload)!
        }
    }
    
    // 检测不安全的位操作
    static func safeBitcast<T, U>(_ value: T, to type: U.Type) -> U {
        // 不要直接用 unsafeBitCast 除非你确定两种架构上布局一致
        // 使用 withMemoryRebound 更安全
        return withUnsafePointer(to: value) { ptr in
            ptr.withMemoryRebound(to: U.self, capacity: 1) { rebound in
                rebound.pointee
            }
        }
    }
}

最佳实践

  • 立即在 Xcode 12 中将项目的 Architectures 设置为 Universal Binary,确保同时支持 Intel 和 Apple Silicon
  • 搜索代码中的 #if arch(x86_64) 和硬编码的 8(指针大小),替换为架构无关的写法
  • 使用 Accelerate 框架做数学运算——它在两种架构上都有高度优化的实现
  • 如果你的应用包含 Intel 汇编代码,用 Swift 或 C 重写为架构无关的实现
  • 在 DTK(Developer Transition Kit)或 Apple Silicon Mac 上做实际测试,不要只依赖 Rosetta 2

还有什么值得关注

  • Xcode 12 可以在 Intel Mac 上交叉编译 arm64 二进制,不需要 Apple Silicon 硬件就能测试编译
  • file 命令可以检查二进制支持的架构:file MyUniversalApp 会显示 Mach-O universal binary with 2 architectures
  • Session 提到 SwiftUI 和 Catalyst 应用在 Apple Silicon 上的性能表现优于 Intel Mac——特别是动画渲染和 JavaScript 执行速度
  • 对于游戏开发者,Metal 在 Apple Silicon 上原生运行,性能开销接近于零
WWDC 2020