iOS APM 与 RUM 体系总览

APM(Application Performance Monitoring / Management)是面向线上真实用户的性能稳定性观测与治理体系。对 iOS App 来说,它不是一个单纯的 Crash SDK,也不是一个“把指标画成图”的后台,而是一套贯穿 C 端真实用户体验、端侧采集 SDK、服务端数据平台、B 端研发治理平台 的完整系统。 RUM(Real User Monitoring)是 APM 在真实用户体验侧的核心模型:把用户的一次使用会话拆成 Session / View / Action / Resource / Error / LongTask 等对象,用统一 ID 串联页面、交互、网络、错误和性能现场。没有 RUM 模型,APM 很容易退化成一堆孤立指标;有了 RUM 模型,平台才能回答“哪个真实用户在什么页面做了什么操作,随后发生了什么性能或稳定性问题”。 一、重构后的系列结构 文章 定位 APM(本文) 系列入口:APM/RUM 定义、B/C 端边界、技术分层、建设路线 APM-产品与架构设计 产品视角:C 端体验、B 端用户、角色视图、治理闭环 APM-指标体系 指标口径:稳定性、流畅性、启动、资源、网络、业务、RUM 指标 APM-C端SDK架构 iOS SDK 架构:插件化、远程配置、采样、隐私、低开销、防自崩 APM-数据采集 采集技术:Crash、Watchdog、FOOM、卡顿、启动、网络、MetricKit、业务 Trace APM-数据模型与上报 事件模型、RUM ID、协议、落盘、批量、重试、脱敏、采样 APM-服务端数据架构 接入网关、消息队列、实时计算、存储、符号化、Issue 聚合、配置下发 APM-B端平台设计 Web 控制台:大盘、详情页、用户会话、告警、工单、发布防劣化 APM-业界方案 MetricKit、Sentry、Firebase、Bugly、Matrix、Slardar、Hertz 等方案对比 重构后的主线是: ...

May 7, 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

Texture源码导读

TODO: 待补充

May 2, 2026

启动优化

iOS应用的启动时间是用户体验的关键指标之一。研究表明,应用性能直接影响用户留存:页面加载时间每增加1秒,用户流失率会显著上升。对于iOS应用,需要在20秒内完成启动,否则可能会被watchdog强制终止。 本系列文章从启动流程的各个阶段入手,系统性地介绍iOS启动优化的方法和实践。 启动监控是 APM 的重要子系,线上 P50/P90/P99 的采集、冷/温/热启动判定、以及与业务秒开指标的关联,请参考 APM 系列:指标体系、数据采集。 启动流程概述 App的启动流程分为两个主要阶段: 阶段 时间范围 主要工作 Pre-main 进程创建 → main()函数 加载可执行文件、加载动态库、Rebase/Bind、ObjC Runtime初始化、Swift Runtime元数据注册、+load方法、Initializers main main()函数 → 首帧渲染 UIApplicationMain、AppDelegate回调、首帧渲染 flowchart TD Start([App 冷启动]) --> PreMain subgraph PreMain["Pre-main 阶段"] direction TB A[加载可执行文件创建进程、mmap映射、签名验证] --> B[加载动态库递归加载依赖、包括 Swift Runtime] B --> C[Rebase & Bind指针修正、符号绑定] C --> D[ObjC Runtime 初始化类注册、Category 附加] D --> E[Swift Runtime 元数据注册类型元数据、协议遵循表] E --> F[调用 +load 方法] F --> G[执行 InitializersC++静态构造函数、__attribute__] end PreMain --> MainPhase subgraph MainPhase["main 阶段"] direction TB H[main函数] --> I[UIApplicationMain创建UIApplication、AppDelegate、启动RunLoop] I --> J[AppDelegate回调willFinish、didFinishLaunching] J --> K[首帧渲染创建Window、RootViewController、首屏UI] end MainPhase --> End([启动完成]) 文章导航 本系列包含以下文章,建议按顺序阅读: ...

May 2, 2026

插件化架构详解

什么是插件化 在iOS开发中,插件化通常指的是编译期插件化,即通过壳工程(Shell Project)按需装配不同的业务组件,生成不同功能的App。这与Android的运行时插件化(动态加载代码)有本质区别。 iOS插件化 vs Android插件化 特性 Android插件化 iOS插件化 实现方式 运行时动态加载APK/DEX 编译期按需集成组件 代码热更新 支持 不支持(App Store限制) 主要目的 绕过包大小限制、热修复 多App复用、编译提效 技术难度 高(Hook系统API) 中(工程配置) iOS的"插件化"主要体现在: 壳工程设计:一套代码支撑多个App 配置驱动集成:通过配置文件决定集成哪些组件 二进制化提效:稳定组件编译为二进制,减少编译时间 flowchart TB subgraph 组件库["组件仓库"] A[用户组件] B[商城组件] C[直播组件] D[社区组件] E[基础组件] end subgraph 壳工程["壳工程 (Shell Project)"] Config["配置文件"] Shell["壳工程代码"] end subgraph Apps["生成的App"] App1["App A(用户+商城+基础)"] App2["App B(用户+直播+基础)"] App3["App C(全量组件)"] end A & B & C & D & E --> Config Config -->|"按需装配"| App1 Config -->|"按需装配"| App2 Config -->|"按需装配"| App3 两种插件化实现方式 根据组件间通信方案的不同,iOS插件化有两种主要实现方式,它们的核心区别在于是否强制要求编译期隔离: ...

May 2, 2026

objc4:isa_t、nonpointer isa、引用计数与 SideTable

isa_t、nonpointer isa、引用计数与 SideTable 本文基于 objc4 仓库中的 runtime 源码和相关测试,解释 Objective-C 对象头里的 isa_t 如何同时承载类信息、引用计数、弱引用/关联对象标记,以及这些信息何时转移到 SideTable。 作用 实现原理与位布局 核心结构和关键字段方法 关键流程 测试如何约束行为 1. 作用 传统 Objective-C 对象的第一个机器字是 isa,直接保存类指针。objc4 在支持 SUPPORT_NONPOINTER_ISA 的平台上把这个机器字扩展为 isa_t: 低位和高位保存状态,中间区域保存类信息。这样一次对象头访问就能拿到类、内联引用计数、 是否有弱引用、是否有关联对象、是否可能有 C++ 析构等信息。 为什么要 nonpointer isa 对象绝大多数时候引用计数很小,把计数放进 isa 可让 retain/release 走原子 CAS 快路径,避免进入全局散列表和锁。 为什么仍需要 SideTable isa 空间有限。引用计数溢出、raw isa 对象、弱引用表,以及 deallocating 状态的兼容存储,都需要 SideTable。 raw isa 的存在原因 类对象始终使用 raw pointer isa;某些类通过 instancesRequireRawIsa() 或 运行时配置禁用 nonpointer isa;测试 rawisa.m 还覆盖了 __DATA,__objc_rawisa 段禁用 nonpointer isa 的场景。 ...

June 1, 2026

编译优化-观测

“无法度量就无法优化”。在开始任何编译优化动作之前,必须先建立一套可重复、可对比的观测手段,否则改动的真实收益无从谈起。本文介绍 iOS 编译耗时观测的主要工具链和原理。 观测目标分层 不同层次的观测工具回答不同的问题: flowchart TD A[编译耗时观测] --> B[整体耗时] A --> C[阶段耗时] A --> D[任务级耗时] A --> E[函数/表达式级] B --> B1[xcodebuild 总耗时] C --> C1[Build Timing Summary] C --> C2[Pod Install 阶段计时] D --> D1[Build Timeline] D --> D2[XCLogParser] E --> E1[-debug-time-compilation] E --> E2[-warn-long-expression] 观测层次 典型问题 工具 整体 一次构建花了多久? time xcodebuild、MetricKit 阶段 哪个阶段最慢? -showBuildTimingSummary、Build Timing Summary 任务 哪个文件/目标最慢? Xcode Build Timeline、XCLogParser 函数级 哪个函数/表达式让前端卡住? -debug-time-function-bodies、-warn-long-expression-type-checking 整体耗时 xcodebuild 命令行计时 最简单也最稳定的方式是直接给 xcodebuild 加 time: ...

May 5, 2026

fishhook 源码导读

fishhook 由 Facebook 于 2013 年开源,用于在运行时动态重绑定(rebind)Mach-O 中被 dyld 绑定的 C 符号,是 iOS/macOS 逆向与底层埋点(malloc 追踪、双 close 检测、网络 socket 拦截、NSLog 重定向等)领域的"瑞士军刀"。 一、fishhook 是什么 一句话定义:fishhook 通过修改 Mach-O 镜像中 __DATA(_CONST) 段里 __la_symbol_ptr / __nl_symbol_ptr 两个 Section 中的函数指针,把对外部 C 符号(如 open、close、NSLog)的调用重定向到自定义的替换函数,同时保留原函数指针供回调使用。 它提供的能力类似 macOS 上 DYLD_INTERPOSE 宏(编译期 interpose),但: 运行时生效:不需要重新编译依赖库,随时可以在 App 启动后对系统库的 C 函数下钩子; 支持动态加载的 image:注册后对后续 dlopen 加载的 image 同样生效; 对业务零侵入:不改 App 本身的代码段,也不破坏符号表,只是重写 GOT/stub 里的一个指针。 但它也有非常明确的能力边界: 能力 是否支持 Hook 通过 dyld 动态绑定的外部 C 函数(libSystem、CoreFoundation 里的 C API、objc_msgSend 等) 是 Hook Objective-C 方法(-[NSString length]) 否(要用 Method Swizzling) Hook 当前 image 内部的 C 函数(静态链接、static、内联) 否(它不走 __la_symbol_ptr,直接是段内相对寻址) Hook Swift 方法(除非显式 @_cdecl) 否 Hook 已经解析并内联到寄存器里的符号(如 LTO 后消失的符号) 否 记住这条原则:fishhook 的作用域 = 被 dyld 绑定为"间接符号"(Indirect Symbol)的 C 符号。 ...

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

组件化架构详解

什么是组件化 组件化是一种工程架构思想,将一个大型App拆分为多个独立的业务组件(Module),每个组件可以独立开发、独立测试、独立编译,组件之间通过中间层进行解耦通信。 组件化解决的问题 问题 未组件化 组件化后 编译速度 全量编译,耗时长 只编译修改的组件,支持二进制化 团队协作 代码冲突频繁 独立仓库,减少冲突 代码复用 复制粘贴,难以维护 组件复用,统一维护 耦合度 类之间直接依赖,牵一发动全身 通过中间层通信,解耦 测试 难以单元测试 组件可独立测试 组件化与页面架构的关系 组件化是工程架构,关注的是App整体的模块划分和通信方式;而MVC、MVVM、TCA等是页面架构,关注的是单个页面内的代码组织。 两者并不冲突,在大型项目中通常会同时采用: 工程层面:使用组件化拆分业务模块 组件内部:每个组件使用MVVM/TCA等页面架构 flowchart TB subgraph App["App壳工程"] subgraph Module1["首页组件"] M1V["View"] M1VM["ViewModel"] M1M["Model"] end subgraph Module2["商城组件"] M2V["View"] M2VM["ViewModel"] M2M["Model"] end Router["路由/中间件"] end Module1 <--> Router Module2 <--> Router 组件化的分层架构 典型的组件化架构采用三层结构: flowchart TB subgraph 业务层["业务层 - Business Layer"] A[首页组件] B[商城组件] C[用户组件] D[消息组件] end subgraph 中间层["中间层 - Mediator Layer"] E[路由Router] F[服务Service] end subgraph 基础层["基础层 - Foundation Layer"] G[网络库] H[图片库] I[数据库] J[工具库] end A & B & C & D --> E & F E & F --> G & H & I & J 业务层(Business Layer) 业务层包含各个业务组件,每个组件是一个独立的业务单元: ...

May 2, 2026