耗电-CPU与后台优化

CPU是App最容易"无意识"耗电的模块,而后台则是最容易"偷偷"耗电的场景。本文聚焦这两大问题域,给出具体的优化手段。 一、CPU耗电的根本原因 根据 耗电-原理 中的分析,CPU耗电由三部分构成: 活跃时间:CPU处于C0/P0的时间。 瞬时功率:和P-State频率相关。 唤醒次数:频繁从C-State唤醒无法进入深度休眠。 对应三条优化方向: 优化方向 策略 减少活跃 任务及时结束,该停就停 降低功率 避免无谓的满核满频,使用合适的QoS 合并唤醒 聚合Timer、聚合任务,让CPU能连续工作后进入深休眠 二、识别CPU耗电热点 从MetricKit入手 cumulativeCPUTime 是线上最直接的CPU指标。对比同版本前后、同机型前后的CPU时长,能快速定位回归。 Time Profiler的使用技巧 在Instruments中抓一份Time Profiler Trace后: 按"Heavy Stack"视图查看耗时函数。 关注后台的采样(通过Xcode的生命周期切换复现)。 查看线程调用频率——单次耗时低但高频调用的函数,同样是耗电大头。 代码层面的CPU异味 // 1. 死循环轮询 while isWaitingForData { if dataReady { break } } // 2. 主线程sleep循环 DispatchQueue.global().async { while true { checkStatus() Thread.sleep(forTimeInterval: 0.1) } } // 3. 密集Timer Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { _ in self.updateUI() } // 4. 无限制的CADisplayLink displayLink = CADisplayLink(target: self, selector: #selector(tick)) displayLink.add(to: .main, forMode: .common) // 页面隐藏后忘记invalidate 这些代码在Time Profiler中都会显示出异常高的调用频率。 ...

May 2, 2026

Objective-C weak 指针作用与实现原理

objc4 runtime zeroing weak SideTable weak_table_t Objective-C weak 指针:作用与实现原理 __weak 的核心价值是“引用对象但不拥有对象”:它不会增加引用计数;当对象开始销毁时,runtime 会把所有指向它的 weak 槽位自动写成 nil,避免野指针。这一页按 objc4 源码中的真实路径解释它如何登记、读取、搬迁和清零。 阅读顺序 weak 解决什么问题 运行时数据模型 核心类型、字段、方法 关键操作流程 带注释源码片段 一遍记住 1. weak 的作用 不拥有对象__weak id x = obj; 不会 retain obj,所以不会延长对象生命周期。 自动归零对象销毁时,所有保存该对象地址的 weak 变量会被 runtime 写成 nil。 读时短暂保活读取 weak 时,runtime 会先尝试 retain,再 autorelease,保证表达式使用期间对象不被并发释放。 拒绝无效目标目标正在 dealloc 或类不支持 weak 时,形成 weak 引用会失败或崩溃,取决于入口函数。 **一句话模型:**strong 指针管“拥有关系”,weak 指针管“可观察关系”。delegate、反向引用、缓存回调对象通常适合 weak,因为它们不应该阻止真实所有者释放对象。 2. 运行时数据模型 weak 表不是全局一把大表直接裸用,而是挂在分片的 SideTable 上。runtime 用对象地址选择某个 SideTable,在该表内维护引用计数辅助信息和 weak 表,并用同一把锁保护它们。 ...

June 1, 2026

CocoaPods 源码导读:从下载到工程集成

本文是系列第三篇,基于 CocoaPods 1.16.2 源码。上一篇我们停在 Analyzer 产出 AnalysisResult,本文继续走完 download_dependencies → validate_targets → generate_pods_project → integrate_user_project → write_lockfiles 这五个阶段。 上一篇:从命令到依赖求解 · 首篇:架构总览 一、全景:从 spec 到可编译的工程 先把本文要走的流程重新放一遍,让你带着地图读代码: flowchart TB A[AnalysisResult] --> B[download_dependencies] B -->|per pod| B1[PodSourceDownloader] B1 -->|命中| C1[Downloader::Cache] B1 -->|未命中| C2[cocoapods-downloaderGit/HTTP/SVN/...] C2 --> C3[rsync → Pods/<name>/] B -->|per pod| B2[PodSourceInstaller] B2 --> B3[PodSourcePreparerprepare_command] B2 --> B4[PodDirCleaner按 spec.source_files 裁剪] B --> D[validate_targetsTargetValidator#validate!] D --> E[generate_pods_project] E --> E1[ProjectCacheAnalyzer增量识别要重生成的 target] E --> E2[SinglePodsProjectGenerator#generate!] E2 --> E21[FileReferencesInstaller] E2 --> E22[PodTargetInstaller] E2 --> E23[AggregateTargetInstaller] E2 --> E24[xcconfig / modulemap / umbrella / info.plist / dummy.m] E --> E3[PodsProjectWriter#write!写盘前触发 Podfile post_install] E --> F[integrate_user_project] F --> F1[create_workspace写 .xcworkspace] F --> F2[deintegrate_removed_targets] F --> F3[TargetIntegrator#integrate!xcconfig / frameworks / scripts] F --> G[write_lockfiles] G --> G1[Podfile.lock] G --> G2[Pods/Manifest.lock] G --> H[perform_post_install_actionsplugin post_install hook] 对应的 Installer#install! 片段: ...

May 2, 2026

Swift中import详解

源码版本说明:本文涉及的源码基于 Swift 编译器 开发版本(swift main分支,commit: e0db5152d4d4,2026-01-19)。不同版本的实现细节可能略有差异,但核心机制保持一致。 在前一篇文章Objective-C中import详解中,我们深入探讨了 Objective-C 中的 import 机制,包括 #include、#import、@import 以及 Clang Modules 的工作原理。本文将继续从 Swift 编译器的角度,讲解 Swift 中 import 的语法、模块查找机制、底层实现原理以及与 Objective-C 的互操作。 从 Clang Module 到 Swift Module 在深入 Swift 的 import 机制之前,让我们先回顾一下上一篇文章中 Clang Module 的核心概念。 Clang Module 核心概念回顾 在 Objective-C 文章中,我们学习了 Clang Module 的几个关键特性: module.modulemap:定义模块结构的配置文件 framework module Foundation { umbrella header "Foundation.h" export * } 预编译模块缓存(.pcm 文件):Clang 将模块编译为二进制格式,缓存以加速后续编译 懒加载机制:只加载实际使用的声明,未使用的部分不会被解析 自动链接:@import 会自动链接对应的库,无需手动配置 模块查找路径:通过 -I、-F 等参数指定搜索路径 Swift 如何继承 Clang Module 的设计 Swift 的模块系统并非从零开始设计,而是站在 Clang Module 的肩膀上,继承了其核心优势,并针对 Swift 的特性进行了扩展: ...

May 2, 2026

编译优化-链接优化

链接是 iOS 编译的最后一个阶段,也是大型工程里经常被忽略的瓶颈。Apple 在 WWDC22 的 “Link fast: Improve build and launch times” 演讲中公开:Xcode 14 新的 ld-prime 链接器比 ld64 快 2 倍。随着 Xcode 17、ThinLTO、Mergeable Libraries 等改进,链接优化已经成为编译加速的重要一环。 链接器的职责 链接器把多个 .o 合并成最终可执行文件或库: flowchart TD A[若干 .o] --> L[链接器] B[静态库 .a] --> L C[动态库 .dylib / framework] --> L L --> D[符号解析] D --> E[dead_strip] E --> F[ObjC runtime fix-up] F --> G[生成 LC_* 加载命令] G --> H[写入 Mach-O] 关键任务: 符号解析:所有 undefined symbol 必须在某个 .o / .a / .dylib 里找到 重定位:把符号引用的偏移写入正确位置 Dead Strip:去掉未被使用的代码/数据 ObjC 元数据修复:把分散的类、分类信息合并成 ObjC runtime 能识别的结构 生成 Mach-O:写 Load Commands、__LINKEDIT 等段 链接器对比 ld64(经典) ld64 是 Apple 长期使用的经典链接器,源代码在 apple-oss-distributions/ld64。单线程为主,在大工程上明显偏慢。 ...

May 2, 2026

耗电-原理

本文从硬件功耗模型、iOS系统的能效调度机制、以及各个硬件模块的功耗特性三个维度,系统性地讲解iOS App耗电的底层原理。 功耗的基本概念 功率、能量与电量 功率(Power):单位时间内消耗的能量,单位为瓦特(W)。 能量(Energy):功率在时间上的积分,单位为焦耳(J)或毫瓦时(mWh)。 电量(Battery Capacity):电池能存储的总能量,单位为毫安时(mAh)。 \[ Energy = \int P(t)\,dt \]对于一段时间内近似恒定的功率,可以简化为 \( E = P \times t \)。这也是所有耗电优化的起点——要么降功率(P),要么降时间(t)。 电池电压与电流 iPhone使用锂电池,标称电压约为3.7~3.8V。操作系统看到的电流则随着硬件负载实时变化。可以近似认为: \[ P(t) = V \times I(t) \]在App层面,我们无法直接测量V和I,但可以通过 UIDevice.current.batteryLevel 观察电量百分比,通过 IOKit 私有API读取更精细的电池状态(后面检测篇会详细讲)。 硬件功耗特性 不同硬件模块的功耗曲线差异非常大。理解每个模块的特性,才能找到最有效的优化点。 CPU:P-State与C-State 现代CPU(包括Apple Silicon)通过 动态电压频率调整(DVFS) 来平衡性能与功耗。 P-State(Performance State) P-State描述CPU在工作时的电压/频率档位。以Apple A系列芯片为例,大核和小核都有多个P-State: flowchart LR subgraph P-State P0["P0最高频率功耗最大"] P1["P1中高频率"] P2["P2中低频率"] P3["P3低频率功耗小"] end P0 -.->|负载下降| P1 P1 -.-> P2 P2 -.-> P3 P3 -.->|负载上升| P0 CPU的功耗与频率大致呈 立方关系(电压随频率升高,功耗 ≈ V² × f ≈ f³)。这意味着: ...

May 2, 2026

objc4 Autorelease 一页讲透

objc4 runtime guide Autorelease 的作用和实现原理 一句话:autorelease 是“延迟一次 release”的机制。对象先被登记到当前线程的自动释放池中, 等池子 pop 时再统一发送 release,从而让返回值和临时对象可以跨过当前语句或当前函数继续存活一小段时间。 核心文件:runtime/NSObject.mm 数据结构:AutoreleasePoolPage 线程隔离:TLS hot page 边界标记:POOL_BOUNDARY 先建立正确心智模型 autorelease pool 不是一堆对象的容器类。 在 objc4 里,它更像每个线程私有的一条栈。栈里放两类指针: 普通对象指针,以及表示池边界的 POOL_BOUNDARY。每次 push 池,就压入一个边界并返回这个边界地址作为 token。 每次 autorelease 对象,就把对象指针压到 hot page 的 next 位置。pop 时拿 token 找到边界, 把边界之上的对象按后进先出的顺序逐个 objc_release。 它解决什么问题 **返回 +0 对象:**被调用方可以返回一个“当前调用者可用,但不归调用者拥有”的对象。 **批量释放临时对象:**循环、事件回调、线程入口可以用池边界控制临时对象峰值。 兼容手动引用计数:retain 表示拿所有权,release 表示交回所有权,autorelease 表示稍后自动交回。 **配合 ARC 优化:**运行时和编译器会尝试消除返回值中的多余 retain/autorelease。 一张图看懂栈变化 假设外层池中 autorelease 了 A,又进入内层池 autorelease 了 B、C。内层 pop 只释放 B、C,不影响外层 A。 push 外层 ...

June 1, 2026

编译优化

大型 iOS 工程的编译耗时往往是研发效能最突出的瓶颈之一。以抖音、今日头条、美团为代表的一线团队,工程代码量通常在数百万到千万行级,本地全量编译耗时 10 分钟以上、CI 上半小时起步已是常态。编译等待不仅直接压缩研发时间,还会打断心流、降低人均产出。 本系列文章从 iOS 编译系统的原理出发,结合社区最新实践(Xcode 16/17 Explicit Modules、rules_xcodeproj、seer-optimize、cocoapods-hmap-prebuilt、Rugby 等),系统性介绍编译优化的各类手段与背后的原理。 编译流程概述 理解iOS编译原理是编译优化的前提。一次典型的 iOS 编译流程可以划分为以下阶段: flowchart TD A[Parse Podfile / 生成工程] --> B[Pod Install / 依赖解析] B --> C[Xcode Build System 调度] C --> D[Dependency Scan / 模块依赖扫描] D --> E[Swift/Clang 前端编译] E --> F[生成 .o / .swiftmodule / .pcm] F --> G[链接 ld64 / ld-prime / lld] G --> H[签名 / 打包 / 资源处理] H --> I[产出 .app / .ipa] 阶段 主要工作 常见瓶颈 依赖管理 Pod Install、SPM 解析 Source 更新慢、Specification 解析重复、沙盒拷贝 工程生成 生成 Pods.xcodeproj、xcconfig、hmap Target 数量多、pbxproj 膨胀 构建调度 Build System 解析任务图、并行调度 依赖粒度过粗、并行度不足 模块扫描 Clang/Swift 依赖扫描 隐式模块重复编译 源码编译 Swift/Clang 前端、类型检查、SIL/IR 生成 类型推导爆炸、WMO 关闭、PCH 失效 链接 符号解析、LTO、dead-strip 链接参数过长、ThinLTO 串行 收尾 签名、资源拷贝、dSYM 串行脚本阻塞 优化思路全景 编译优化的核心思路只有三条:减少需要做的工作、让必须做的工作更快、让做过的工作可复用。所有社区实践都可以归入这三类。 ...

May 27, 2026

CocoaPods 源码导读:从命令到依赖求解

本文是系列第二篇,基于 CocoaPods 1.16.2 源码。我们从 bin/pod 进场,走完 CLAide 的命令分发、Podfile 的 DSL 求值、Analyzer 的七步分析、Resolver + Molinillo 的回溯求解,最后交付 AggregateTarget/PodTarget 给下一篇讲的下载与集成。 上一篇:架构总览 · 下一篇:从下载到工程集成 运行示例贯穿全文:pod install --repo-update 一、bin/pod:16 行的入口 CocoaPods 的可执行文件非常薄,核心逻辑全在 require 'cocoapods' 之后的 Pod::Command.run: # CocoaPods/bin/pod : 24 if $PROGRAM_NAME == __FILE__ && !ENV['COCOAPODS_NO_BUNDLER'] ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__) require 'rubygems' require 'bundler/setup' $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) elsif ENV['COCOAPODS_NO_BUNDLER'] require 'rubygems' gem 'cocoapods' end STDOUT.sync = true if ENV['CP_STDOUT_SYNC'] == 'TRUE' require 'cocoapods' if profile_filename = ENV['COCOAPODS_PROFILE'] # 开 ruby-prof 做性能剖析 # ... reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io) else Pod::Command.run(ARGV) end 这里有两个工程化细节值得留意: ...

May 2, 2026

Swift二进制兼容性

Swift二进制兼容性是指Swift编译后的二进制代码能够跨版本、跨模块正确链接和运行的能力。它由三个核心机制组成:ABI稳定(Swift 5.0)、模块稳定性(Swift 5.1)和Library Evolution。这三者共同使得Swift二进制框架的分发成为可能。 graph TD A[Swift二进制兼容性] --> B[ABI稳定Swift 5.0] A --> C[模块稳定性Swift 5.1] A --> D[Library Evolution] B --> B1[统一运行时固定调用约定/内存布局] C --> C1[.swiftinterface跨编译器版本导入模块] D --> D1[运行时布局查询库可独立于客户端更新] B1 --> E[二进制框架分发] C1 --> E D1 --> E ABI稳定 什么是ABI ABI(Application Binary Interface,应用程序二进制接口)定义了二进制层面的接口规范,包括: 数据类型的大小和对齐方式:如Int在64位系统上是8字节 函数调用约定:参数如何传递、返回值如何获取 名称修饰(Name Mangling)规则:函数和类型在二进制中的命名方式 内存布局:对象在内存中的结构 异常处理机制:错误如何在二进制层面传播 运行时元数据格式:类型信息的存储方式 ABI vs API 特性 API(源码接口) ABI(二进制接口) 层面 源代码层面 编译后的二进制层面 兼容性检查 编译时 链接时/运行时 关注点 函数签名、类型定义 内存布局、调用约定 变化影响 需要重新编译 需要重新链接或替换二进制 一个简单的例子: // API层面:函数签名 func calculate(value: Int) -> Int // ABI层面关注的是: // - Int在内存中占多少字节 // - value参数通过哪个寄存器传递 // - 返回值通过哪个寄存器返回 // - 函数在二进制中的符号名是什么 ABI不稳定时期的问题 在Swift 5.0之前,Swift的ABI是不稳定的。这意味着: ...

May 2, 2026