深入理解技术探索与实践:一个老iOSer的性能优化实战手记

字段又改名了
2025-12-15 09:23
阅读 756

凌晨两点,MacBook风扇呼呼作响,Xcode 15 的 build log 正在疯狂刷屏。我揉了揉发酸的眼睛,心里默默吐槽:“这破需求,产品经理居然说‘就改个按钮颜色,明天上线’”。做了6年 iOS 开发,从 Objective-C 转战 Swift 再到 SwiftUI 小试牛刀,我早就习惯了这种深夜写代码的节奏——白天被会议和 CR(Code Review)撕碎的时间,只有晚上才能找回 coding 的心流。

最近在准备跳槽,一边刷 LeetCode,一边复盘自己这几年做的项目。突然意识到,真正让我在技术面试中脱颖而出的,不是背了多少八股文,而是在真实业务场景里死磕出来的性能优化经验。今天就想掏心窝子聊聊,我是怎么在一次次线上事故和 deadline 压力下,把“技术探索”变成“落地实践”的。


事情得从去年双11说起

当时我在一家电商公司,负责主 App 的商品详情页。那是个流量黑洞,每天几千万 PV,任何一点卡顿都会被放大成用户投诉。双11前两周,测试同学提了个 bug:“iOS 16 用户滑动详情页时,FPS 掉到 30 以下,甚至偶发卡死”。

我一开始没当回事,心想:“不就是图片加载慢点嘛,加个缓存不就完了?”结果真机一跑,好家伙, Instruments 里的 Time Profiler 直接给我上了一课——主线程被大量 layoutSubviews 和 image decoding 占满,尤其是快速滑动时,CPU 瞬间飙到 90%+。

那一刻我真的想砸电脑。但转念一想,这不正是跳槽简历上能写“主导核心页面性能优化,提升 FPS 40%”的好素材吗?干就完了!


技术探索:别只盯着“快”,要看“稳”

很多人一提到性能优化,就想到“异步”、“缓存”、“懒加载”。但实际项目中,过早优化是万恶之源,而盲目堆技术更会带来维护灾难

我先做了三件事:

  1. 量化问题:用 XCTest 写了一套 UI Performance Test,记录冷启动、滑动、图片加载等关键路径的 FPS、内存、CPU 数据。
  2. 归因分析:通过 Instruments 的 Core Animation & Time Profiler,定位到两个核心瓶颈:
    • 自定义 UITableViewCell 里用了 Auto Layout + 多层嵌套,导致频繁 layoutSubviews
    • 图片解码在主线程进行,大图直接阻塞 UI
  3. 技术选型权衡
    • SDWebImage 还是 Kingfisher?最终选了后者,因为它的解码策略更灵活(支持后台解码 + 预解码)
    • 重构布局:放弃 Auto Layout,改用手动 frame 计算(虽然丑,但快)

这里有个血泪教训:别为了“现代化”而现代化。团队里有个新来的同事非要上 SwiftUI,结果发现和现有 UIKit 混合架构冲突,调试成本翻倍。最后还是老老实实用熟悉的工具解决问题。


实战:从理论到代码的鸿沟

1. 图片解码优化

// 以前的写法(罪恶之源!)
imageView.sd_setImage(with: url)

// 优化后:后台解码 + 预解码
let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
    .append(another: RoundCornerImageProcessor(radius: 8))

KingfisherManager.shared
    .retrieveImage(
        with: url,
        options: [
            .processor(processor),
            .backgroundDecode,      // 关键!后台解码
            .preloadAllAnimationData
        ]
    ) { result in
        guard let image = try? result.get().image else { return }
        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }

别小看 .backgroundDecode 这一行,它让图片解码从主线程挪到了后台队列,滑动流畅度立竿见影。

2. 减少 Layout 计算

我把原来的 Auto Layout Cell 全部重写为手动计算 frame,并缓存 cell 高度:

// 在 ViewModel 里预计算高度
class ProductDetailViewModel {
    private(set) var cachedHeight: CGFloat?

    func calculateCellHeight(for width: CGFloat) -> CGFloat {
        if let height = cachedHeight { return height }
        
        // 模拟 UILabel 的 sizeThatFits
        let titleHeight = title.boundingRect(
            with: CGSize(width: width - 32, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [.font: UIFont.systemFont(ofSize: 16)],
            context: nil
        ).height
        
        cachedHeight = 120 + titleHeight // 固定padding + 动态title
        return cachedHeight!
    }
}

这样 tableView(_:heightForRowAt:) 直接返回缓存值,避免每次滚动都触发布局计算。

3. 内存抖动治理

用 Xcode 的 Memory Graph Debugger 发现,每次滑动都会创建大量临时 NSAttributedString 对象。于是引入对象池(Object Pool)复用:

final class AttributedStringPool {
    static let shared = AttributedStringPool()
    private var pool: [NSMutableAttributedString] = []
    
    func acquire() -> NSMutableAttributedString {
        if !pool.isEmpty {
            return pool.removeLast()
        }
        return NSMutableAttributedString()
    }
    
    func release(_ attrStr: NSMutableAttributedString) {
        attrStr.mutableString.setString("")
        attrStr.removeAttribute(.foregroundColor, range: NSRange(location: 0, length: attrStr.length))
        pool.append(attrStr)
    }
}

虽然有点“复古”,但在高频创建/销毁场景下,GC 压力明显降低。


效果实测:数据不说谎

优化前后对比(iPhone 13, iOS 16.5):

指标 优化前 优化后 提升
平均 FPS 42 58 +38%
内存峰值 180 MB 145 MB -19%
主线程阻塞 >16ms 次数/分钟 27 5 -81%

最爽的是双11当天,运维群里一片“系统稳定”,而我们 iOS 组终于不用半夜爬起来查 Crashlytics 了。


开发心得:技术探索的本质是“解决问题”

回头想想,这次优化过程其实没啥高深算法,全是些“脏活累活”。但正是这些细节,构成了工程师的核心竞争力。

最近面试了几家公司,聊到性能优化,我不再只是背“离屏渲染”、“CAShapeLayer”这些名词,而是能讲清楚:

  • 为什么选这个方案(比如 Kingfisher 的解码策略比 SDWebImage 更可控)
  • 踩过什么坑(比如后台解码后图片方向错乱,需要额外处理 EXIF)
  • 如何验证效果(用 XCTest + CI 自动化性能回归)

求职时,面试官要的不是一个技术百科全书,而是一个能闭环解决问题的人。


写在最后

做 iOS 开发六年,Swift 从 1.0 到 5.9,ARC 让我们告别了 MRC 的噩梦,但性能优化永远在路上。有时候我会自嘲:“是不是年纪大了,越来越喜欢抠这些细节?”但转念一想,对用户体验的敬畏,才是工程师的初心

如果你也在准备跳槽,别只刷题。回头看看自己做过的需求,有没有可以深挖的技术点?哪怕是一个 tableView 的滚动优化,只要讲清楚背景、决策、结果,都比空谈“精通性能优化”强一百倍。

好了,天快亮了,我得去回个邮件——产品经理又在钉钉上@我:“那个按钮颜色,能不能再暖一点?”。唉,打工人,打工魂,今晚继续肝。

(完)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝