技术探索与实践:从一个 iOS 工程师的视角出发
引言:为什么我要谈技术探索与实践?

我是一名有着五年工作经验的 iOS 工程师,经历过从刚入行的“Hello World”到独立负责复杂模块开发的成长历程。在这条路上,最让我受益匪浅的并不是那些书本上的理论知识,而是不断在项目中遇到问题、解决问题、总结经验的过程。
我相信很多开发者和我有相同的体会——写代码容易,把代码写好很难;用工具不难,用对工具才见真章。所以我想借这篇文章,聊聊我在工作中做过的几次技术探索与实践,分享真实项目中的一些挑战和思考方式。希望能给正在成长中的朋友们一些启发。
一次性能优化项目的背景与挑战

2023年,我参与了一个企业级金融类 App 的重构项目。App 主要面向银行客户,提供账户查询、交易明细查看、消息通知等功能。虽然功能上已经趋于成熟,但用户反馈中最常见的是:“页面打开慢、卡顿严重、切换时会闪退”。
我们团队通过一系列性能分析工具(Instruments、Xcode Organizer 的崩溃报告、MetricKit)发现,主要的问题集中在以下几个方面:
- 列表页面滑动卡顿明显:尤其是当数据量大的时候,FPS 掉到了 20 帧以下。
- 内存占用高且波动大:频繁触发内存警告,甚至出现 OOM 导致 Crash。
- 启动耗时长:冷启动时间超过 5 秒,在低端机型更久。
- 部分 API 调用超时率偏高:网络请求失败后重试逻辑不合理,导致体验下降。
这些看似各自独立的问题,实际上背后都存在共性:架构松散、资源管理粗放、异步处理混乱。我们的目标很明确:在不大幅改动业务的前提下,提升整体性能和稳定性。
解决方案:从底层优化到工程规范落地


一、界面卡顿优化:UITableView / UICollectionView 性能调优
这个问题是最早暴露出来的。我们在首页有一个定制的 UICollectionView,用于展示多种类型的卡片信息。由于每个 cell 都做了图片下载、布局计算等操作,滑动时 FPS 极低。
1. 图片加载优化
- 原来是直接在主线程调用 SDWebImage 加载:
[cell.imageView sd_setImageWithURL:url];
这其实并没有错,但如果我们不做预加载,用户滑动时就容易“跳帧”。
于是我们引入了 SDWebImage 的预加载机制 和 基于 IndexPath 的优先级队列加载策略,并配合 UICollectionViewDataSourcePrefetching 实现更优雅的数据预取。
2. Cell 复用与高度缓存
- 列表中有大量不定高的 cell(图文混排),原来每次
tableView:heightForRowAtIndexPath:都重新计算高度,造成大量 CPU 占用。 - 我们改为将高度缓存到模型中,并在数据刷新时预计算,避免重复绘制。
3. 手势冲突与响应链优化
- 某些页面嵌套了多个 scrollView,手势冲突严重。
- 通过统一使用
UIScrollViewDelegate+ 自定义UIGestureRecognizer的方式,控制层级传递逻辑。
最终,核心列表页面滑动 FPS 提升至接近 60,滚动流畅度显著改善。
二、内存优化:从 Leak Detection 到对象生命周期管理
我们通过 Xcode 的 Debug Memory Graph 工具,发现了几个常见的内存泄露场景:
NotificationCenter中注册但未移除的对象;- 循环引用造成的 UIViewController 不释放;
- 过多持有强引用的大图或自定义 UIView。
针对这些问题,我推动做了几件事:
- 统一使用
NotificationCenter.default.addObserver(forName:object:queue:using:)替代传统的addObserver:self selector:...,并在 deinit 里取消监听; - 使用 Swift 的
[weak self]和 OC 的__weak显式管理闭包循环引用; - 对大图加载使用 autorelease pool 包裹,限制作用域;
- 启用 Instrument -> Allocations 工具跟踪峰值内存,逐步优化。

其中还有一个小插曲:某天晚上我加班查内存泄漏,发现某 UI 类持续增长。反复测试才发现是某个第三方库内部持有了 delegate(OC 中没有 weak),最后不得不 fork 一份源码自己修复……
三、启动耗时分析与优化
为了定位冷启动瓶颈,我们使用 Xcode 的 Time Profiler 工具追踪了整个启动流程,结果如下:
| 耗时阶段 | 占比 |
|---|---|
| dyld 加载 | 1.2s |
| UIKit 初始化 | 0.8s |
| 第一屏渲染 | 1.5s |
| 第三方 SDK 初始化 | 1.0s |
优化手段包括:
- 延迟初始化某些非关键服务(比如日志、统计SDK);
- 将部分同步初始化转为异步执行;
- 精简首屏加载的内容,先展示骨架屏;
- 使用编译器指令
-Wl,-no_order_inits禁止自动符号排序,手动指定顺序(这个有点黑科技,但在大型项目中确实有效); - 启用 App Thinning 和 Bitcode 减少二进制体积。
最终冷启动时间从 4.8s 缩短到 2.9s,效果立竿见影。
四、网络层架构升级与稳定性建设
原有的网络请求封装非常基础,都是简单的 AFNetworking 封装一层,然后统一加 header、token 等操作。但由于缺乏统一入口和错误重试机制,导致:
- 请求并发控制混乱;
- 错误处理冗余且分散;
- 用户切换网络环境时经常报错却无法恢复;
- 本地缓存机制缺失,影响弱网环境下的用户体验。
我们决定进行一次彻底的网络层重构:
技术选型
- 选择了 Alamofire + Combine(Swift 项目)+ RxSwift(OC 混编) 的组合方式;
- 新增统一请求调度器(RequestScheduler),支持任务优先级、并发数控制;
- 对错误码进行集中解析和分类:鉴权失败、网络中断、接口异常等;
- 实现离线缓存策略(NSURLCache + Disk 缓存,带过期判断);
- 增加重试机制:根据类型自动重试 1~3 次;
- 所有请求纳入监控系统,接入 APM(如 Sentry)进行埋点上报。
这套体系上线之后,网络层面的崩溃率下降了 37%,API 超时率也明显降低。
效果总结:看得见的提升

经过为期两个月的持续优化,我们完成了以下成果:
- FPS 从平均 30 帧左右提升至稳定 55 以上
- 冷启动时间缩短约 40%
- 内存占用峰值从 480MB 降到 320MB 以内
- Crash 率下降超过 40%
- 用户主动投诉明显减少
最关键的是,这次性能优化不是一次性攻坚,而是形成了可持续的优化机制和文档沉淀,后续新功能接入也有标准可依。
我的经验与建议:技术探索要有“边界感”
作为一个经历过多次大型重构和技术升级的老兵,我也想结合自己的经历,分享几点关于技术探索与实践的心得:
1. 技术选型要“务实”,而非“炫技”
- 不要为了追求新技术而引入复杂的框架,尤其是在项目进度紧张时。
- 更重要的是选择适合你当前团队水平、维护成本可控的技术方案。
- 比如我们当时考虑是否使用 SwiftUI 替换现有 UIKit 页面,但评估下来过渡成本太大,最终还是以渐进改造为主。
2. 性能优化要坚持“分而治之”的思路
- 不要试图一次解决所有问题。找准关键路径(冷启动、核心页面)重点突破。
- 使用 Instrument、Xcode Performance Tool、MetricKit 等官方工具辅助分析。
3. 文档和自动化是质量保障的核心
- 任何优化做完都要写成文档沉淀下来;
- 对于性能相关指标,最好接入 CI/CD 流水线,设置阈值监控;
- 我们的项目后来加入了每 PR 自动运行性能快照对比脚本,一旦有严重退化就会报警。
4. 学会接受“妥协”而不是“完美主义”
- 在实际项目中,往往需要在可维护性和极致性能之间找到平衡。
- 比如为了追求更低的内存占用,完全采用轻量级组件,可能会增加维护成本和风险。
- “合理即可”,是我这几年越来越深的感受。
5. 多向团队外的成员学习,不要自我封闭
- 我之前一直专注客户端,但通过和后台、运维、产品经理的沟通,了解到更多全局视角。
- 比如通过和后端协同优化返回结构,节省了大量解析时间和流量,这也是一个意外收获。
结语:技术探索,是一场漫长的修行
回顾这几年的 iOS 开发旅程,我觉得技术探索从来不是一个孤立的动作。它是对产品体验的追求、是对架构设计的理解、更是对自己代码风格和工程质量的一种坚持。
在这个快速变化的时代,我们每天面对新的框架、新的语言、新的理念。但真正重要的,不是你用了多少新技术,而是你有没有把这些技术真正落地、用出价值。
愿我们都保持一颗热爱技术、敬畏代码的心,在这条路上越走越远。
如果你也在技术实践中遇到了瓶颈,或者正处在要不要尝试某些技术的犹豫期,欢迎留言交流。我们一起踩坑,一起成长。

评论 0