崩溃-采集

准确采集崩溃信息是分析和修复问题的前提。本文详细介绍iOS崩溃的捕获方案、堆栈回溯技术以及符号化原理。 崩溃采集架构 flowchart TB subgraph capture["崩溃捕获层"] direction LR mach["Mach Exception Handler"] signal["Unix Signal Handler"] exception["NSException Handler"] end subgraph collect["信息采集层"] direction LR backtrace["堆栈回溯(Backtrace)"] register["寄存器状态"] thread["线程信息"] device["设备/应用信息"] end subgraph storage["本地存储层"] direction LR async_write["异步安全写入"] file_manage["崩溃文件管理"] end subgraph report["上报处理层"] direction LR symbolicate["符号化解析"] aggregate["崩溃聚合"] analyze["数据分析"] end capture --> collect collect --> storage storage --> report Mach异常捕获 Mach是macOS/iOS的内核,提供了底层的异常处理机制。Mach异常是最底层的异常类型,发生在内核态,比Unix信号更早被触发。通过注册Mach异常处理器,可以在信号处理之前捕获到崩溃信息。 核心概念: Mach Port(端口):Mach内核中进程间通信(IPC)的基本单元,类似于文件描述符 Exception Port(异常端口):专门用于接收异常消息的端口 Task:Mach中的任务概念,对应一个进程,包含地址空间和资源 异常端口注册 #include <mach/mach.h> // 异常处理线程函数 static void *exception_handler_thread(void *arg) { mach_port_t exception_port = (mach_port_t)(uintptr_t)arg; while (1) { // 接收异常消息 struct { mach_msg_header_t head; mach_msg_body_t body; // ... 其他字段 } request; mach_msg_return_t result = mach_msg( &request.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(request), exception_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); if (result == MACH_MSG_SUCCESS) { // 处理异常 handle_exception(&request); } } return NULL; } // 注册异常端口 kern_return_t register_exception_handler(void) { kern_return_t kr; mach_port_t exception_port; // 创建异常端口 kr = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port ); if (kr != KERN_SUCCESS) return kr; // 添加发送权限 kr = mach_port_insert_right( mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND ); if (kr != KERN_SUCCESS) return kr; // 设置任务异常端口 kr = task_set_exception_ports( mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE ); if (kr != KERN_SUCCESS) return kr; // 创建异常处理线程 pthread_t thread; pthread_create(&thread, NULL, exception_handler_thread, (void *)(uintptr_t)exception_port); return KERN_SUCCESS; } Mach异常处理流程 flowchart TD A["1. 异常发生"] --> B["2. 内核发送异常消息到异常端口"] B --> C["3. 异常处理线程接收消息"] C --> D["4. 解析异常信息"] D --> D1["exception type"] D --> D2["exception code"] D --> D3["thread state"] D1 & D2 & D3 --> E["5. 采集崩溃信息"] E --> E1["堆栈回溯"] E --> E2["寄存器状态"] E --> E3["其他上下文"] E1 & E2 & E3 --> F["6. 保存崩溃日志"] F --> G["7. 决定后续处理"] G --> G1["转发给原异常处理器"] G --> G2["让进程终止"] Unix信号捕获 Unix信号(Signal)是操作系统向进程发送的异步通知机制。当发生特定事件(如非法内存访问、算术错误等)时,内核会向进程发送相应的信号。iOS中,Mach异常通常会被转换为对应的Unix信号。 ...

June 21, 2026

iOS架构概述

什么是架构 软件架构是指软件系统的高层结构,定义了系统的各个组成部分及其之间的关系。一个好的架构能够帮助我们: 职责分离:将不同的功能模块分开,降低耦合度 可测试性:使代码更容易进行单元测试 可维护性:便于理解和修改代码 可扩展性:方便添加新功能 团队协作:不同开发者可以并行开发不同模块 架构的两个层次 在讨论iOS架构时,需要区分两种不同层次的架构: 类型 关注点 代表模式 页面架构 单个页面内的代码组织 MVC、MVP、MVVM、MVI、TCA、VIPER 工程架构 整个App的模块划分与通信 组件化、插件化、微服务化 两者并不冲突,大型项目通常会同时采用:工程层面使用组件化拆分业务,每个组件内部使用MVVM等页面架构。 页面架构模式 MVC (Model-View-Controller) MVC是Apple官方推荐的架构模式,也是iOS开发中最基础的架构。 flowchart LR Controller <-->|读写| Model Controller -->|更新| View View -.->|用户交互| Controller 特点: Controller作为中介者连接Model和View View和Model不直接通信 在iOS中,ViewController往往承担了过多职责 MVP (Model-View-Presenter) MVP是MVC的演进版本,将业务逻辑从Controller中抽离到Presenter。 flowchart LR Presenter -->|更新| Model Presenter -->|更新| View Model -.->|数据| Presenter View -.->|事件| Presenter View --- ViewController 特点: Presenter持有View的引用(通常是协议) View变得非常"被动",只负责展示 便于单元测试 MVVM (Model-View-ViewModel) MVVM通过数据绑定实现View和ViewModel的同步更新。 flowchart LR ViewModel <-->|读写| Model ViewModel <-->|数据绑定| View 特点: ...

June 21, 2026

Swift 面试:编译流程、Runtime 与元数据源码解析

Swift 面试:编译流程、Runtime 与元数据源码解析 Swift 面试里问编译流程和 Runtime,通常不是想听一串名词,而是想看你能不能把 源码 -> AST -> SIL -> 优化 -> IRGen -> LLVM -> Runtime Metadata 串起来。 这篇文章按面试题展开,把答案落到 Swift 编译器源码里的 SILGen、SILOptimizer、IRGen、Metadata、Value Witness Table、Protocol Witness Table 和 HeapObject。 面试高频问题 Swift 源码从 .swift 到机器码经历哪些阶段? AST、SIL、LLVM IR 分别负责什么? 为什么 Swift 需要 SIL,而不是直接生成 LLVM IR? SILGen 做什么?SILOptimizer 做什么? IRGen 为什么要生成 Metadata 和 Witness Table? Swift Runtime Metadata 保存了哪些信息? Value Witness Table 是什么? Protocol Witness Table 和 class vtable 有什么区别? class、struct、enum 在 Runtime 表示上有什么差异? HeapObject 和 Metadata 有什么关系? 30 秒回答版 Swift 编译流程可以简化为: ...

June 16, 2026

Swift 面试:并发、async/await 与 Actor 源码解析

Swift 面试:并发、async/await 与 Actor 源码解析 Swift Concurrency 面试经常从 async/await 问起,但真正的重点是:Task 是结构化并发的执行单元,await 是显式暂停点,Actor 通过隔离和串行执行器保护状态,Sendable 则约束跨并发边界传递的数据。 这篇文章按面试题展开,把结论落到 Swift 标准库和编译器源码里的 Actor、GlobalActor、MainActor、AsyncLet、TaskGroup、Sendable 和 ActorIsolation。 面试高频问题 async/await 和 GCD 的关系是什么? await 到底表示线程阻塞还是任务暂停? Task、async let、TaskGroup 有什么区别? Actor 如何保证数据隔离? MainActor 是什么,为什么 UI 更新要回到 MainActor? GlobalActor 和普通 Actor 有什么区别? Sendable 解决什么问题? Task.detached 为什么要慎用? Actor 之间调用为什么需要 await? Swift Concurrency 是编译器机制还是运行时机制? 30 秒回答版 Swift Concurrency 是编译器、标准库和运行时共同完成的并发模型。 async/await 不是对 GCD 的简单语法糖。await 表示当前异步任务可能在这里暂停,把执行权交还给调度器;等异步结果可用时,再从 continuation 恢复。它不应该理解成“阻塞当前线程等待”。 Actor 是一种受隔离保护的引用类型。Actor 内部可变状态只能在 actor 隔离域内访问;跨 actor 访问必须异步,编译器会插入隔离检查。运行时通过 executor 保证同一 actor 的隔离状态不会被多个任务同时进入。 ...

June 16, 2026

Swift 面试:值语义、COW 与集合源码解析

Swift 面试:值语义、COW 与集合源码解析 Swift 的 Array、String、Dictionary 都表现为值类型,但它们不可能每次赋值都完整复制底层存储。面试官问值语义和 COW,真正想看的是你能不能说清楚:值语义是语言语义,COW 是性能实现;集合表面是 struct,底层经常共享引用存储;写入前靠唯一性检查决定是否复制。 这篇文章从面试题出发,把结论落到 Swift 标准库源码里的 Array buffer、String guts、Dictionary storage 和 isKnownUniquelyReferenced。 面试高频问题 Swift 的 struct 为什么常说是值语义? Array 赋值时一定会复制底层元素吗? Copy-on-Write 的触发条件是什么? isKnownUniquelyReferenced 检查的到底是什么? Array、ContiguousArray、ArrayBuffer、Storage 是什么关系? String 为什么不能用整数下标随机访问? String.Index 为什么不是一个简单的 Int? Dictionary 也是 COW 吗? ArraySlice 为什么可能持有原数组存储? 值类型里包含 class 引用,还算值语义吗? 30 秒回答版 Swift 的值语义是说:从语言使用者角度看,赋值、传参、修改不会意外影响另一个值。但这不等于底层每次都立即复制。 标准库集合通常用 Copy-on-Write 实现:多个值可以共享同一份堆上 buffer;只有当某个值要写入时,才检查底层 buffer 是否唯一引用。如果唯一,就原地修改;如果不唯一,就复制一份新 buffer 再修改。 所以: 值语义:用户看到的是独立值 COW:实现上先共享,写入前再判断是否复制 ARC:维护底层 buffer 的引用计数 isUnique:让 COW 判断能否原地修改 面试可以这样回答: Swift 的 Array 是 struct,但它内部持有引用语义的 storage。赋值时通常只复制结构体里的引用;真正写入时通过唯一性检查决定是否复制底层 storage。这让 Array 同时具备值语义和接近引用共享的性能。 ...

June 16, 2026

Swift 面试:泛型、协议与派发源码解析

Swift 面试:泛型、协议与派发源码解析 Swift 面试里,泛型和协议经常不是单独问语法,而是连着问:Swift 中函数派发机制有哪几种?泛型是编译期还是运行期机制?协议调用为什么可能慢?some 和 any 为什么性能不同?Protocol Witness Table 到底是什么? 这篇文章按面试题展开,把答案落到 Swift 编译器里的 Generic Signature、Generic Specializer、SIL 调用指令、Protocol Witness Table、class vtable 和 Devirtualize 优化。 面试高频问题 Swift 中函数派发机制有哪几种? 直接派发、class vtable 派发、witness table 派发、Objective-C 消息派发分别适用于什么场景? function_ref、class_method、witness_method、objc_method 在 SIL 里分别代表什么? 如何用 swiftc -emit-silgen / swiftc -emit-sil -O 观察派发指令? dynamic / @objc dynamic 会怎样影响派发? 如何用代码对比泛型、some、any 的派发和性能差异? Swift 泛型是运行时泛型还是编译期泛型? Generic Signature 是什么?它保存哪些信息? 泛型特化为什么能提升性能? Protocol Witness Table 是什么? 协议方法调用什么时候是静态派发,什么时候是动态派发? some Protocol 和 any Protocol 的本质区别是什么? final、private、具体类型为什么有利于优化? 协议扩展里的方法一定是动态派发吗? class_method 和 witness_method 有什么区别? 面试里如何解释“Swift 既强调协议,又强调性能”? 30 秒回答版 Swift 函数派发面试不要只答“静态派发和动态派发”。更完整的分类是:直接派发、class vtable 派发、protocol witness table 派发、Objective-C 消息派发,再补充优化器可能把动态调用去虚拟化成直接调用。 ...

June 16, 2026

Swift 面试:ARC 与内存管理源码解析

Swift 面试:ARC 与内存管理源码解析 Swift 面试里问 ARC,通常不是想听一句“自动引用计数”。真正要答清楚的是:对象头里有什么、strong / weak / unowned 到底差在哪里、编译器什么时候插入或消除 retain/release、为什么闭包会循环引用。 这篇文章按面试题展开。先给可以直接回答的版本,再把结论落到 Swift 源码里的对象布局、引用计数状态机和 SIL ARC 优化。 面试高频问题 Swift 的 ARC 和 Objective-C 的 ARC 有什么区别? Swift 对象的引用计数存在哪里? strong、weak、unowned 的底层差异是什么? 为什么 weak 在对象释放后会自动变成 nil? 为什么 unowned 访问已释放对象会崩溃? 闭包为什么容易造成循环引用? 编译器会如何优化 retain/release? 值类型是否完全不参与 ARC? ARC 和 Copy-on-Write 有什么关系? 30 秒回答版 Swift ARC 是编译器和运行时共同完成的内存管理机制。 编译器在 SIL 阶段根据所有权语义插入 retain_value、release_value、strong_retain、strong_release 等引用计数指令,并通过 ARC 优化尽量消除冗余 retain/release。运行时负责真正维护对象的引用计数。 对原生 Swift 堆对象来说,对象头包含两部分:metadata 和 refCounts。源码里 HeapObject 的注释明确说 metadata 总是指向一个有效的 metadata 对象,refCounts 是 Swift heap object header 的非 Objective-C 成员。 ...

June 16, 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

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

runtime/maptable.h 作用与实现原理

objc4 runtime NXMapTable 开放寻址哈希表 runtime/maptable.h runtime/maptable.h 作用与实现原理 runtime/maptable.h 定义了 Objective-C runtime 里旧 NeXT 风格的 NXMapTable:一个通用的 key - value 指针映射表。 它不拥有业务对象,只负责把指针或整数形式的 key 映射到指针或整数形式的 value, 并通过回调把“怎么 hash、怎么比较、释放时怎么处理”交给调用方决定。 存储模型连续桶数组,每个桶是 {key, value}。 冲突处理开放寻址 + 线性探测。 扩容阈值元素数超过桶数的 75% 后翻倍重哈希。 runtime 用途类名表、协议表、future class 表、少量 meta 到 non-meta 映射。 阅读路径 它解决什么问题 和 hashtable2 的区别 核心数据结构 Prototype 回调机制 查找、插入、删除、扩容流程 带注释核心代码 runtime 中的实际使用 必须记住的约束 1. 它解决什么问题 runtime 需要维护很多“名字到结构体指针”“元类到类”“未来会出现的类名到占位 Class”的映射。 这些映射发生在启动、加载镜像、注册类、查找协议等低层路径上,不能依赖 Objective-C 容器对象。 NXMapTable 就是一个 C 接口的轻量哈希表。 它是什么 一个 void * key 到 void * value 的映射表。key 和 value 可以是指针,也可以把整数强转成指针使用。 ...

June 1, 2026