iOS Runtime:什么是 iPhone 应用程序运行时环境

作者: IT Sectr 发布日期: 2026-05-17 阅读时间: 12 分钟

iOS Runtime — Apple iOS 操作系统上的应用程序运行时环境,包括 Objective-C Runtime、Swift Runtime、Cocoa Touch 框架以及通过 Automatic Reference Counting (ARC) 的内存管理机制。iOS Runtime 负责方法的动态绑定(消息传递)、类加载、内存管理以及通过 iOS 框架与硬件交互。根据 Apple Developer Documentation,了解运行时对于性能优化、调试和开发稳定的 iOS 应用程序至关重要。

要点

  • iOS Runtime — 应用程序运行时环境,包括 Objective-C Runtime、Swift Runtime 和 Cocoa Touch
  • Objective-C Runtime — 通过消息传递 (objc_msgSend) 实现方法的动态绑定
  • Swift Runtime — 通过 value types 和 generics 进行优化的静态派发
  • ARC(自动引用计数)— 编译阶段的自动内存管理
  • dyld — 在应用程序启动时加载框架和库的动态加载器

什么是 iOS Runtime?

iOS Runtime — 是在 iOS 下的 Apple 设备上确保应用程序执行的系统组件集合。包括 Objective-C Runtime(libobjc.A.dylib 库)、Swift Runtime(libswiftCore.dylib)、Core Foundation、Cocoa Touch 框架(UIKit、Foundation)、动态加载器 dyld 以及用于管理内存、线程和进程间通信的运行时环境。

从架构上讲,iOS Runtime 在三个层次上工作。底层 — Mach-O 二进制格式和 dyld,加载可执行文件和库。中间层 — Objective-C Runtime 和 Swift Runtime,负责方法派发和对象管理。上层 — Cocoa Touch 框架(UIKit、Foundation、Core Data、Metal),为开发人员提供 API。

了解 iOS Runtime 使开发人员能够解决复杂的任务:用于 A/B 测试和分析的方法混淆(Method Swizzling)、动态类加载、通过理解 ARC 进行内存优化、调试 retain cycle 和内存泄漏、通过 dyld 优化应用程序启动时间。没有运行时知识,系统级别的性能分析和优化是不可能的。

iOS Runtime 组件

组件用途
Objective-C Runtimelibobjc.A.dylib消息传递、动态类、混淆
Swift RuntimelibswiftCore.dylibValue types、泛型、protocol witnesses
Core FoundationCoreFoundation.frameworkCFType、toll-free bridging
dylddyld (usr/lib/dyld)加载 Mach-O、链接库
libSystemlibSystem.B.dylibPOSIX 线程、libc、libdispatch (GCD)

Mach-O 格式

iOS 应用程序被编译为 Mach-O(Mach Object)格式。Mach-O 文件包含头部(header)、加载命令(load commands)和段(segments):__TEXT(代码、常量)、__DATA(全局变量、Objective-C 元数据)、__LINKEDIT(符号、重定位表)。dyld 分析 Mach-O 并在执行第一条指令之前加载依赖项。

Objective-C Runtime:消息传递和动态派发

Objective-C Runtime — iOS Runtime 中最强大的部分。与使用早期绑定(early binding)的 C++ 不同,Objective-C 通过消息传递使用晚期绑定(late binding)。方法调用 [receiver message] 不是编译为直接函数调用,而是编译为 objc_msgSend(receiver, @selector(message)),它在对象的类中动态查找方法的实现。

每个 Objective-C 对象都存储一个指向其类的 isa 指针。类包含方法列表(method list)、方法缓存(method cache)和指向父类的指针。objc_msgSend 遍历继承链:检查类的缓存,然后检查方法列表,然后转向父类。如果找不到该方法,则启动转发:resolveInstanceMethod、forwardingTargetForSelector 和 forwardInvocation。

Method Swizzling — 通过在运行时交换 IMP(实现指针)来即时替换方法实现的技术。用于 A/B 测试、分析(自动跟踪屏幕)和监控。除非万不得已,否则不建议在生产环境中使用,因为它可能与操作系统更新冲突。

示例:Objective-C 中的 Method Swizzling

objective-c
// 用于跟踪 viewDidLoad 的 Method Swizzling
#import 

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(swizzled_viewDidLoad);

        Method originalMethod = class_getInstanceMethod(
            class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(
            class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(
            class,
            originalSelector,
            method_getImplementation(swizzledMethod),
            method_getTypeEncoding(swizzledMethod)
        );

        if (didAddMethod) {
            class_replaceMethod(
                class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod)
            );
        } else {
            method_exchangeImplementations(
                originalMethod, swizzledMethod);
        }
    });
}

- (void)swizzled_viewDidLoad {
    // 事件跟踪
    NSLog(@"View Did Load: %@", self.class);
    // 调用原始实现
    [self swizzled_viewDidLoad];
}

@end

UIViewController (Tracking) 类别将应用程序中所有 UIViewController 的 viewDidLoad 替换为 swizzled_viewDidLoad。dispatch_once 保证一次性 swizzling。class_addMethod 防止双重 swizzling 和与父类的冲突。用于在不更改控制器源代码的情况下在分析中自动跟踪屏幕显示。

isa 指针和 tagged pointers

在现代 iOS(arm64)中,Apple 优化了 isa 指针:它不仅仅是类的地址,而是一个包含内存管理标志和类信息的位字段(non-pointer isa)。Tagged pointers — 另一项优化:小尺寸的 NSNumber、NSDate 和 NSString 值不作为堆上的对象存储,而是直接存储在指针中,从而消除了 malloc 和 retain/release 的开销。tagged pointer 通过 isa 的最低有效位来识别。

Swift Runtime:静态派发和优化

Swift Runtime 与 Objective-C Runtime 有根本不同:Swift 默认使用静态派发(static dispatch),通过 vtable 进行类方法派发,通过 direct call 进行 value types 和 extension methods 派发。动态派发(dynamic dispatch)仅用于标记为 @objc 或 dynamic 的方法。与 Objective-C 相比,性能提升高达 40%。

Value types(struct、enum)在 Swift 中 — 与 Objective-C 的关键区别。它们存储在栈(stack)上或其他对象内部,不使用 retain/release,并且不参与 ARC 的引用计数。Struct 没有 isa 指针,不能通过 objc_msgSend 发送。Protocol witnesses — 协议对应的 vtable,允许 existential container 的动态派发。

Swift Runtime 还包括 泛型(通过 mangled symbols 实现 reified generics)和 COW(写时复制)以优化 string、array、dictionary、set。在复制集合时,实际复制仅发生在修改其中一个副本时。这最大限度地减少了在函数之间传递集合时的开销。

Swift 与 Objective-C 派发

swift
import Foundation

// Swift:静态派发(class 的 vtable)
class Animal {
    func makeSound() { print("...") }  // vtable
}

class Dog: Animal {
    override func makeSound() { print("Woof") }  // vtable override
}

// @objc dynamic:Objective-C Runtime 派发
class Cat: Animal {
    @objc dynamic override func makeSound() {
        print("Meow")
    }  // objc_msgSend
}

// Struct — 无运行时派发
struct Cow {
    func makeSound() { print("Moo") }  // direct call
}

// 带协议见证的协议
protocol SoundMaker {
    func makeSound()
}

struct Duck: SoundMaker {
    func makeSound() { print("Quack") }
}

// 使用 existential container
let soundMakers: [SoundMaker] = [Dog(), Cow(), Duck()]
for maker in soundMakers {
    maker.makeSound()  // protocol witness dispatch
}

// 性能测试
func testDispatch() {
    let dog = Dog()
    let cat = Cat()
    var cow = Cow()

    let start = CFAbsoluteTimeGetCurrent()
    for _ in 0..<1000000 {
        dog.makeSound()  // vtable:~3ns
        cat.makeSound()  // objc_msgSend:~15ns
        cow.makeSound()  // direct:~1ns
    }
    let elapsed = CFAbsoluteTimeGetCurrent() - start
    print("Elapsed: (elapsed) sec")
}

该示例演示了 Swift 中的三种派发类型:class(Dog)的 vtable、@objc dynamic(Cat)的 objc_msgSend 和 struct(Cow)的 direct call。Existential container([SoundMaker])中的 protocol witnesses 增加了开销。在实践中,Swift 在可能的情况下选择静态派发,提供接近 C 的性能。

Swift Runtime 和 Objective-C 桥接

Swift Runtime 旨在与 Objective-C Runtime 完全兼容。任何继承 NSObject 的 Swift 类都会自动注册到 Objective-C Runtime 中,并可以通过 objc_msgSend 调用。@objc 属性使 Swift 方法可以从 Objective-C 访问。字符串桥接:Swift String 在传递给 Objective-C API 时会自动桥接到 NSString(toll-free bridging)。

ARC:自动引用计数和内存管理

ARC(自动引用计数)— iOS 中的内存管理系统,在编译阶段工作。编译器(Clang)分析对象的生命周期并自动插入 retain/release/autorelease 调用。开发人员无需手动调用它们 — 与 iOS 5 之前的 Manual Retain-Release (MRR) 不同。ARC 在 Objective-C 对象和 Swift class 级别工作,但不适用于 value types(struct、enum)。

每个 Objective-C 对象和 Swift 类都有一个引用计数器(retain count),存储在 non-pointer isa 内的 extra_rc 字段中。创建对象时,retain count = 1。retain 时计数器增加,release 时减少。当计数器达到 0 时,对象通过 dealloc(Objective-C)或 deinit(Swift)释放。ARC 是线程安全的:retain/release 使用原子操作(OSAtomicIncrement32/OSAtomicDecrement32)。

Retain cycles — ARC 的主要问题。如果对象 A 持有对 B 的 strong 引用,而 B 持有对 A 的 strong 引用,两个对象都不会被释放,因为它们的引用计数器不会归零。解决方案 — 弱引用(__weak 在 Objective-C 中,weak 在 Swift 中)或 unowned。弱引用不增加 retain count,并在对象释放时自动归零(nil)。

通过 Instruments 调试 retain cycles

swift
import Foundation

// Retain cycle 示例
class Parent {
    var child: Child?
    deinit { print("Parent 已释放") }
}

class Child {
    var parent: Parent?  // strong — 创建 retain cycle!
    deinit { print("Child 已释放") }
}

var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent  // 循环:Parent -> Child -> Parent
parent = nil
child = nil
// deinit 未被调用 — 内存泄漏!

// 修复:weak
class WeakChild {
    weak var parent: Parent?  // weak — 不增加 retain count
    deinit { print("WeakChild 已释放") }
}

// 修复:unowned(用于保证生命周期)
class UnownedChild {
    unowned let parent: Parent
    init(parent: Parent) { self.parent = parent }
    deinit { print("UnownedChild 已释放") }
}

// 通过 Instruments 检查
func profileMemory() {
    // 1. 启动 Instruments > Leaks
    // 2. 执行创建对象的操作
    // 3. 检查 Leaks 是否有泄漏
    // 4. 在 Allocations 中找到没有 dealloc 的对象
    for _ in 0..<1000 {
        let p = Parent()
        let c = WeakChild()
        p.child = c as? Child
        // c.parent = p — 不添加,weak
    }
}

Parent 和 Child 之间的 retain cycle 示例:双方持有彼此的 strong 引用,ARC 无法将计数器归零。修复 — 在 Child 中使用 weak parent。weak 在 parent 释放时自动归零。unowned — 用于 parent 的生命周期保证比 child 长的情况(例如,viewController 和 view)。使用 Instruments > Leaks 在早期阶段检测 retain cycles。

自动释放池

自动释放池 — 为没有显式所有权的对象提供延迟释放机制。Swift 和 Objective-C 中的 @autoreleasepool { } 创建一个池,该池在块结束时清空,向池中的每个对象发送 release。在循环中(创建数千个临时对象)和没有 RunLoop 的后台线程上至关重要。UIKit RunLoop 在每次迭代时自动清空主自动释放池。

dyld:动态加载器和应用程序启动

dyld(动态链接编辑器)— 负责在 iOS 应用程序启动时加载 Mach-O 可执行文件和相关动态库(dylib)的系统加载器。dyld 位于 /usr/lib/dyld,是 libSystem 的一部分。加载过程包括几个阶段:解析 Mach-O、加载依赖项(Library Loader、LC_LOAD_DYLIB)、地址重定位(ASLR)、初始化 Objective-C Runtime 和调用 main()。

应用程序启动时间(launch time)关键取决于 dyld:动态库和 Objective-C 类越多,pre-main time 越长。Apple 建议尽量减少 +load 方法的数量(在 main 之前执行),用 +initialize(惰性初始化)替代它们。自 2020 年起,Apple 在 iOS 上使用预构建的 dyld 缓存:系统库预先链接到单个缓存中,加快了加载速度。

测量 pre-main time

swift
import Foundation

// 通过 DYLD_PRINT_STATISTICS 测量启动时间
// 在 Xcode 中:Edit Scheme > Run > Arguments > Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1

// 程序化测量 pre-main time
@main
struct AppMain {
    static func main() {
        let launchStart = CFAbsoluteTimeGetCurrent()

        // UIApplicationMain 在此发生
        AppDelegate.main()

        let launchEnd = CFAbsoluteTimeGetCurrent()
        let preMainTime = launchEnd - launchStart
        print("Pre-main time: (preMainTime) sec")
    }
}

// 优化:将 +load 替换为 +initialize
class OptimizedClass {
    // ❌ +load 在 main 之前执行
    // override class func load() { }

    // ✅ +initialize 在首次使用时执行
    static let shared = OptimizedClass()
    private init() {
        // 在此初始化
    }
}

// 优化 dylib 数量
// 合并静态库减少 LC_LOAD_DYLIB 数量
// 使用 -ObjC 标志仅链接使用的 Objective-C 类
// Xcode:Build Settings > Mach-O Type > Static Library

要测量 pre-main time,请在 Xcode 方案中使用 DYLD_PRINT_STATISTICS。输出将显示 total time、dylib loading time、rebase/bind time、Objective-C setup time 和 initializer time。目标值:冷启动 total < 400ms,热启动 < 200ms。优化:合并库、将 +load 替换为 +initialize、减少 Objective-C 类的数量(使用 Swift)、动态框架数量最小化。

dsc(dyld 共享缓存)

dyld shared cache — iOS 上预先链接的系统库缓存。所有系统 dylib(UIKit、Foundation、CoreGraphics)都合并到一个文件中:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64。这消除了单独加载每个系统库的需要 — dyld 访问缓存,大大加快了启动速度。具有 10 个以上动态框架的应用程序会遇到最大的延迟,因为自定义 dylib 不属于 dsc 的一部分。

常见问题

什么是 iOS Runtime,它由哪些组件组成?

iOS Runtime — iOS 上的应用程序运行时环境,包括 Objective-C Runtime (libobjc.dylib)、Swift Runtime (libswiftCore.dylib)、Cocoa Touch 框架、dyld(动态加载器)和 ARC(内存管理)。为 Objective-C 提供消息传递,为 Swift 提供静态派发,加载 Mach-O 文件以及自动内存管理。

Objective-C Runtime 与 Swift Runtime 有何不同?

Objective-C Runtime 通过 objc_msgSend(消息传递)使用动态绑定,具有晚期绑定。Swift Runtime 使用静态派发(类的 vtable,struct 的 direct call)以获得性能。@objc dynamic 为 Swift 类启用 Objective-C Runtime。Swift struct 没有 isa 指针,不使用 retain/release。

ARC 在 iOS 中如何工作?

ARC(自动引用计数)— 编译阶段的内存管理。Clang 编译器自动插入 retain/release 调用。每个对象都有一个引用计数器,归零时调用 dealloc。Retain cycles(相互 strong 引用)通过 weak/unowned 引用防止。使用 Instruments > Leaks 检测泄漏。

什么是 dyld,它如何影响应用程序启动?

dyld — Mach-O 文件的动态加载器。加载可执行文件和所有依赖的 dylib,执行重定位(ASLR),初始化 Objective-C Runtime 并调用 main()。Pre-main time 取决于 dylib 数量和 +load 方法。使用 DYLD_PRINT_STATISTICS 进行测量。优化:合并库,将 +load 替换为 +initialize。

什么是 Method Swizzling,何时使用它?

Method Swizzling — 通过 Objective-C Runtime class_getInstanceMethod 和 method_exchangeImplementations 即时替换方法 IMP(实现指针)的技术。用于 A/B 测试、分析(自动屏幕跟踪)和监控。除非万不得已,否则不建议在生产中使用。在 Swift 中被 @objc dynamic + Method Swizzling 替代。

总结

  • iOS Runtime — iOS 应用程序运行时环境,包括 Objective-C Runtime、Swift Runtime、dyld 和 ARC
  • Objective-C Runtime — 消息传递 (objc_msgSend)、isa 指针、混淆、动态类
  • Swift Runtime — 静态派发 (vtable、direct call)、value types、protocol witnesses
  • ARC(自动引用计数)— 编译阶段通过 retain/release 进行自动内存管理
  • dyld — 动态 Mach-O 加载器,决定应用程序启动速度(pre-main time)
  • Retain cycles — 通过 weak/unowned 引用防止;通过 Instruments Leaks 调试
  • 优化 — 最小化 +load、合并 dylib、对 value types 使用 Swift struct

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读