App启动流程

启动类型概览 iOS应用启动分为三种类型: 启动类型 描述 特点 冷启动(Cold Launch) App完全不在内存中,需要从头开始加载 耗时最长,需要完整执行所有启动流程 热启动(Warm Launch) App在后台被挂起(Suspended),重新进入前台 最快,只需恢复App状态,不需要重新创建进程 预热启动(Pre-warm Launch) 系统预测用户可能启动App,提前在后台执行部分启动流程 iOS 15+引入,介于冷启动和热启动之间 graph LR subgraph "冷启动(耗时最长)" A1[创建进程] --> A2[加载dylib] A2 --> A3[Rebase/Bind] A3 --> A4[+load] A4 --> A5[main] A5 --> A6[首帧渲染] end subgraph "预热启动(iOS 15+)" B1[系统提前完成(后台):创建进程 → 加载dylib → Rebase/Bind] --> B2[用户点击后执行:+load → main → 首帧渲染] end subgraph "热启动(最快)" C1[唤醒进程] --> C2[WillEnterForeground] C2 --> C3[DidBecomeActive] C3 --> C4[恢复UI] end 一、冷启动(Cold Launch) 冷启动是最完整的启动流程,也是启动优化的主要关注点。 冷启动流程概览 flowchart TD Start([App 冷启动流程]) --> PreMain subgraph PreMain["Pre-main 阶段"] direction TB A[加载可执行文件] --> B[加载动态库(包括 Swift Runtime)] B --> C[Rebase & Bind] C --> D[ObjC Runtime 初始化(类注册、Category 附加)] D --> D2[Swift Runtime 元数据注册(类型元数据、协议遵循表)] D2 --> D3[调用 +load 方法] D3 --> E[执行 Initializers(C++静态构造函数、__attribute__)] end PreMain --> MainPhase subgraph MainPhase["main 阶段"] direction TB F[main函数] --> G[UIApplicationMain] G --> H[AppDelegate回调] H --> I[首帧渲染(创建Window、RootViewController、首屏UI)] end MainPhase --> End([启动完成]) Pre-main 阶段 Pre-main阶段是指从用户点击App图标到main函数执行之前的过程,这个阶段主要由dyld(动态链接器)负责。 ...

May 2, 2026

Block底层原理

基础概念 Block 是 Apple 在 C 语言基础上扩展的语法特性(也称为 Closure / 闭包),它允许将一段代码和其执行时需要的上下文环境封装为一个可传递的对象。Block 在 Objective-C 和 C/C++ 中均可使用,是 GCD、动画 API、回调等场景的核心机制。 // Block 的声明与调用 void (^myBlock)(NSString *) = ^(NSString *name) { NSLog(@"Hello, %@", name); }; myBlock(@"World"); 一个核心问题是:Block 是函数指针还是对象? 答案是——Block 本质上是一个 OC 对象。它的底层是一个包含 isa 指针的 C 结构体,满足 OC 对象的基本条件;同时它也包含一个函数指针(FuncPtr),指向 Block 实际执行的代码。因此可以说,Block 是一个用对象形式包装了函数指针和捕获变量的特殊 OC 对象。 底层数据结构 通过 clang -rewrite-objc 可以将 Block 语法转换为 C++ 代码,观察其底层结构。这个转换虽然不完全等同于最终编译产物,但准确反映了 Block 的数据布局和调用机制。 编译器转换示例 对以下源码: int main() { int a = 10; void (^block)(void) = ^{ NSLog(@"%d", a); }; block(); return 0; } Clang 会将其转换为如下结构: ...

May 2, 2026

Codable底层原理

Codable 是 Swift 4 引入的序列化方案,官方定位是替代 Objective-C 时代的 NSCoding、以及各种第三方字典转模型框架(MJExtension、YYModel、JSONModel 等)。与运行时反射方案不同,Codable 通过"编译器自动合成 + 标准库协议抽象 + 具体格式实现"三层解耦,在编译期就把类型与序列化逻辑绑定好,兼具类型安全、性能和扩展性。 本文基于以下 Swift 源码展开分析: 协议定义:swift/stdlib/public/core/Codable.swift 编译器合成:swift/lib/Sema/DerivedConformanceCodable.cpp、DerivedConformanceCodingKey.cpp Foundation 实现:swift-corelibs-foundation/Sources/Foundation/JSONEncoder.swift、JSONDecoder.swift、PropertyListEncoder.swift 整体架构 在开始深入之前,先建立整体印象。Codable 可以拆成三层角色: 用户类型层:业务中的 struct / class / enum,声明遵循 Codable 标准库与编译器层:标准库定义 Encodable / Decodable / Encoder / Decoder 等协议,编译器为符合条件的类型合成 encode(to:) 和 init(from:) 格式实现层:Foundation 或第三方提供具体 Encoder / Decoder,负责把容器操作落到 JSON、Plist、BSON 等格式上 flowchart LR subgraph L1[用户类型层] A[struct / class / enum声明遵循 Codable] end subgraph L2[标准库与编译器层] B[Encodable / Decodable协议要求] C["编译器合成encode(to:) / init(from:)"] D[Encoder / Decoder容器协议] B --> C C --> D end subgraph L3[格式实现层] E[JSONEncoder / JSONDecoderPropertyListEncoder / 第三方实现] F[Data / 字符串 / 其他格式] E --> F end A -->|conforms to| B D -->|由具体类型实现| E 核心思想是:标准库只定义"如何描述一个可编码类型"和"编码器需要提供什么能力",具体的格式(JSON、Plist、BSON…)由 Foundation 或第三方实现。用户类型只需要遵循协议,剩下的由编译器在 SIL 层自动生成。 ...

June 8, 2026

KVC底层原理

基础概念 KVC(Key-Value Coding,键值编码)是 Apple 提供的一种通过字符串 key 间接访问对象属性的机制,定义在 NSKeyValueCoding 协议中,NSObject 默认遵循该协议。 // 直接访问 person.name = @"Tom"; NSString *name = person.name; // KVC 访问 [person setValue:@"Tom" forKey:@"name"]; NSString *name = [person valueForKey:@"name"]; KVC 的核心价值在于:通过字符串动态访问属性,不需要在编译期知道属性名。这使得字典转模型、序列化/反序列化、Interface Builder 的 Runtime Attributes 等场景成为可能。 setValue:forKey: 的底层流程 当调用 [obj setValue:value forKey:@"name"] 时,Runtime 按照以下顺序查找: flowchart TD A["setValue:forKey:@'name'"] --> B{"查找 setter 方法"} B -->|"找到"| C["调用 setter"] B -->|"未找到"| D{"accessInstanceVariablesDirectly返回 YES?"} D -->|"YES"| E{"按顺序查找实例变量_name → _isName → name → isName"} D -->|"NO"| F["调用 setValue:forUndefinedKey:默认抛出 NSUndefinedKeyException"] E -->|"找到"| G["直接设置实例变量值"] E -->|"未找到"| F 第一步:查找 setter 方法 Runtime 按以下顺序查找 setter 方法: ...

May 2, 2026

KVO底层原理

基础概念 KVO(Key-Value Observing,键值观察)是 Apple 基于 KVC 实现的一种观察者模式,允许对象监听另一个对象特定属性的变化。当被观察对象的属性值发生改变时,观察者会收到通知。 // 注册观察 [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 接收通知 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context { if ([keyPath isEqualToString:@"name"]) { NSLog(@"name changed: %@ → %@", change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]); } } // 移除观察 - (void)dealloc { [person removeObserver:self forKeyPath:@"name"]; } KVO 的底层实现原理:isa-swizzling KVO 的核心实现机制是 isa-swizzling(isa 指针替换)。当对象被添加观察者时,Runtime 会在运行时动态创建该对象所属类的一个子类,并将对象的 isa 指针指向这个新的子类。 完整流程 flowchart TD A["addObserver:forKeyPath:"] --> B["Runtime 动态创建子类NSKVONotifying_ClassName"] B --> C["重写被观察属性的 setter 方法"] B --> D["重写 class 方法返回原始类"] B --> E["重写 dealloc 方法"] B --> F["重写 _isKVOA 方法返回 YES"] C --> G["将对象的 isa 指向NSKVONotifying_ClassName"] G --> H["当属性值改变时"] H --> I["调用 willChangeValueForKey:"] I --> J["调用原始 setter 方法"] J --> K["调用 didChangeValueForKey:"] K --> L["通知所有观察者"] 第一步:动态创建子类 当首次对某个类的实例调用 addObserver:forKeyPath:options:context: 时,Runtime 会: ...

May 2, 2026

Mach-O的链接、装载与库

本文深入介绍iOS/macOS开发中的Mach-O文件格式、动态库与静态库,帮助理解程序的链接、装载过程,以及Rebase/Bind的底层原理。 一、基础概念 在深入学习之前,先了解几个核心概念。 1.0 从源代码到可执行文件 一段源代码要变成可执行文件,需要经历以下几个阶段: flowchart TB A["源代码 (.m/.swift)"] --> B["预处理展开宏、处理#import/#include"] B --> C["编译源代码 → 汇编代码"] C --> D["汇编汇编代码 → 目标文件(.o)"] D --> E["链接目标文件 + 库 → 可执行文件"] E --> F["可执行文件"] 1.1 什么是符号(Symbol) 符号是程序中函数、变量、类等实体的名称标识。当你写下一个函数调用时,编译器需要知道这个函数在哪里,符号就是用来定位它的。 // 这段代码中包含多个符号 void myFunction(void) { // 定义了符号 _myFunction NSLog(@"Hello"); // 引用了外部符号 _NSLog } int globalVar = 10; // 定义了符号 _globalVar 符号分为两类: 已定义符号:在当前编译单元(即当前源文件编译生成的.o文件)中有具体代码或数据定义的符号(如上例中的 _myFunction 和 _globalVar) 未定义符号:当前编译单元只是引用,实际定义在其他编译单元或库中的符号(如上例中的 _NSLog),需要在链接时解析 1.2 什么是链接(Linking) 链接是将多个编译后的目标文件(.o)组合成一个可执行文件的过程。链接器的核心工作是符号解析——把所有"未定义符号"与其他编译单元中的"已定义符号"关联起来。 flowchart LR subgraph 输入 A["main.o已定义: _main未定义: _foo"] B["foo.o已定义: _foo未定义: _NSLog"] C["Foundation已定义: _NSLog"] end A --> D["链接器符号解析"] B --> D C --> D D --> E["可执行文件所有符号已解析"] 符号解析的过程 链接器会遍历所有输入的目标文件和库,执行以下步骤: ...

May 2, 2026

Objective-C与Swift区别

语言设计 Objective-C 是C的超集,在C的基础上添加了Smalltalk风格的消息传递机制 对象间交互不通过调用方法而是发送消息,以实现面向对象编程和超高的动态性 动态性由 Objective-C Runtime 系统支持,可以在运行时修改类结构、方法交换等,详见 Runtime 弱类型语言,核心类型id可以指向任何Objective-C对象 Swift 从很多现代化语言中汲取精华,是一门偏重于安全、性能的现代化语言 Swift 也有自己的运行时系统(Swift Runtime),但设计哲学完全不同,更强调编译期优化 强类型语言,编译器在编译阶段进行严格的类型检查 语法特性 Objective-C 语法冗长,需要显式声明类型 命名通常较长且需要添加前缀避免冲突 需要单独的.h和.m文件声明接口和实现 引用其他文件/模块必须要import(Objective-C中import详解) 协议不支持默认实现 泛型是"轻量级泛型",仅用于编译时类型检查,运行时类型信息会被擦除 Swift 语法简洁,强大的类型推断减少冗余的类型声明 有命名空间(模块)的存在避免命名冲突 不需要单独的头文件声明接口 默认情况下变量必须初始化 引用同模块的其他文件不需要import(Swift中import详解) 协议支持默认实现、支持拓展,支持关联类型以实现泛型协议 值类型也可以遵守协议 泛型在编译时进行类型特化优化,支持完整的泛型元编程 内存管理 Objective-C 大部分都是引用类型,依赖引用计数管理内存 ARC在编译时自动插入retain、release、autorelease调用 64位系统使用优化的isa(Non-pointer isa),通过位域在isa中嵌入引用计数(extra_rc字段),溢出时使用SideTable存储 实例变量(ivar)会被Runtime自动初始化为0/nil Swift 引用类型也是依赖引用计数管理内存 存在大量的值类型,例如结构体、枚举、数组、字典等 非逃逸的值类型的内存存在栈区由系统进行自动管理 纯Swift类的引用计数直接存储在对象头部的RefCount字段中,无需SideTable,访问更高效 编译器在编译时就确保了初始化,不需要运行时统一清零 拓展:值类型和引用类型的区别 拓展:iOS中的内存管理 性能对比 Objective-C 基于消息传递机制本质上是动态派发,运行时查找方法实现,有一定开销 ARC管理引用计数也有开销 引用类型内存存在堆区,内存管理的性能开销会比栈区多很多 Swift 方法派发尽可能使用静态派发(如对final类/方法、私有方法、值类型方法的调用) 编译器可以内联优化,速度接近C 内联优化:比如A方法调用B方法,B方法调用C方法,代码编写调用A方法时编译器通过内联直接调用C方法的实现 值类型内存分配在栈区,性能开销相对较小 虽然值类型是深拷贝,Swift为了优化值类型性能引入写时拷贝(Copy-on-Write)机制,值类型只在写操作发生深拷贝,其他时候都是浅拷贝 安全性 Objective-C 弱类型(动态类型)占主导 核心类型id可以指向任何Objective-C对象 编译器几乎不做类型检查,会有运行时崩溃的风险(如向不响应某消息的对象发送该消息) nil消息发送不会崩溃但可能导致逻辑错误,不容易感知 局部变量不会自动初始化,可能包含栈上的垃圾数据(未定义行为) Swift 可选类型强制处理空值,编译器强制解包检查 强类型和编译时检查阻止了大量常见错误 变量必须初始化后才能使用,编译器在编译阶段强制检查,消除未定义行为 强制初始化使编译器能更精确地追踪变量生命周期,带来更多优化机会 数组越界等操作会在运行时触发明确的崩溃,便于定位问题 编程范式 Objective-C 主要是面向对象编程(OOP) 通过继承实现代码复用 依赖Runtime实现面向切面编程(AOP),如Method Swizzling Swift 拥抱多范式,对函数式编程、面向协议编程(POP)、声明式编程很友好 通过协议扩展提供默认实现,协议组合替代多继承 值类型(struct/enum)也可以遵循协议,不再局限于类 拓展:OOP、POP与AOP ...

May 8, 2026

iOS中import详解

源码版本说明:本文涉及的源码基于 LLVM/Clang 23.0.0 开发版本(llvm-project main分支,commit: 301c0d91b558,2026-01-16)。不同版本的实现细节可能略有差异,但核心机制保持一致。 在Objective-C开发中,头文件引用是日常开发中最基础的操作之一。本文将从Clang编译器的角度,深入讲解iOS中各种import方式的区别、底层查找原理以及最佳实践。 引用方式 语法示例 适用场景 #include #include "Header.h" C/C++传统方式 #import #import "Header.h" Objective-C项目内部引用 #import #import <Framework/Header.h> 系统/第三方Framework @import @import Foundation; Clang Modules方式 PCH Prefix Header文件 预编译公共头文件 #include vs #import #include的工作原理 #include是C/C++中的头文件引用方式,它的工作原理非常简单:预处理器会将目标头文件的内容原封不动地复制粘贴到当前文件中。 这种方式存在一个问题:重复引用。如果多个文件都include了同一个头文件,或者存在循环引用,会导致编译错误。传统的解决方案是使用Include Guard: // Header.h #ifndef HEADER_H #define HEADER_H // 头文件内容 #endif 现代编译器还支持#pragma once指令,功能相同但更简洁: // Header.h #pragma once // 头文件内容 #import的增强 #import是Objective-C对#include的封装和增强。它在#include的基础上增加了一层判重逻辑,自动防止同一个头文件被重复引用。 // BClass.m #import "AClass.h" #import "AClass.h" // 第二次import会被自动忽略 双引号 vs 尖括号 在日常开发中,我们经常会看到两种不同的引用方式: #import "MyClass.h" // 双引号形式 #import <UIKit/UIKit.h> // 尖括号形式 它们的核心区别在于搜索路径的范围不同: ...

June 8, 2026

Objective-C底层原理 - NSObject

本文将深入探讨Objective-C中所有对象的基类NSObject的底层实现原理,涵盖对象本质、isa指针机制、Tagged Pointer、引用计数、内存布局等核心概念。 NSObject 的本质:C 结构体 在Objective-C的底层实现中,NSObject的本质是一个C结构体。在苹果运行时源码中可以看到: // 底层运行时定义 struct objc_object { Class isa; // 指向对象所属类的指针 }; // 类型别名定义 typedef struct objc_object NSObject; 这意味着,当我们通过NSObject *obj = [[NSObject alloc] init];创建一个 NSObject 实例时,实际上在堆上分配了一个struct objc_object结构体,而变量obj只是一个指向该结构体的指针。 // 这行代码在底层实际上做了类似下面的事情: NSObject *obj = [[NSObject alloc] init]; // 等效的伪C代码: // 1. 调用 alloc 类方法,其核心是调用 malloc 在堆上分配一块足够大的内存 // 大小至少是 sizeof(struct objc_object),即一个isa指针的大小 NSObject *obj = (NSObject *)malloc(sizeof(struct objc_object)); // 2. 初始化这块内存,最重要的是设置 isa 指针,让它指向 NSObject 这个类 obj->isa = [NSObject class]; // 实际过程更复杂,但本质如此 // 3. 其他初始化工作 (init) 那么这里提到的isa指针是什么呢? ...

May 2, 2026

SIL(Swift Intermediate Language)

SIL(Swift Intermediate Language,Swift 中间语言)是 Swift 编译器在编译过程中生成的一种中间表示(Intermediate Representation,IR)。它位于 Swift 源码与 LLVM IR 之间,是 Swift 编译器架构中至关重要的一层。 SIL 的设计目标是: 对 Swift 语言的高级语义进行精确建模 在高层抽象的基础上执行强大的优化 保留足够的类型信息,用于诊断和安全检查 作为 Swift 特有优化的载体,弥补 LLVM IR 无法直接表达 Swift 语义的不足 SIL 并不是给开发者日常编写的语言,而是编译器的内部表示。理解它有助于深入理解 Swift 的编译过程、性能优化原理和内存管理机制。 Swift 编译流程概览 在了解 SIL 之前,需要先理解 Swift 的整体编译流程: flowchart TD A[Swift 源代码 .swift] --> B[词法分析 Lexer] B --> C[语法分析 Parser] C --> D[语义分析 Sema] D --> E[SILGen: 生成 Raw SIL] E --> F[SIL 优化 Guaranteed Passes] F --> G[SIL 优化 General Passes] G --> H[IRGen: 生成 LLVM IR] H --> I[LLVM 优化] I --> J[机器码 .o] J --> K[链接器 Linker] K --> L[可执行文件 / 动态库] 各阶段简述 词法分析(Lexer) 编译的第一步。Lexer 逐字符扫描 .swift 源文件,将其切分为一系列Token(词法单元),例如关键字 func、标识符 add、运算符 +、字面量 42、左右括号等。Token 是后续所有分析的最小输入单位。这一步会剥离注释和空白,但保留它们的位置信息以便后续生成精确的诊断信息(错误提示的行号和列号)。 ...

May 2, 2026