精选文章

31. 性能精通

2018-11-05 · 性能优化

31. 性能精通

性能优化的目标是“稳定、可预期”,不是临时修补。核心流程是:指标 → 工具 → 定位 → 优化 → 验证。

一、先量化再优化

1. 帧预算概念

  • 60 FPS:每帧约 16.7ms
  • 120 Hz 设备:每帧约 8.3ms

超过这个预算,就会出现掉帧或卡顿。

2. 自己实现一个 FPS 监控

final class FPSMonitor {
    private var link: CADisplayLink?
    private var lastTime: CFTimeInterval = 0
    private var count: Int = 0

    func start(_ handler: @escaping (Int) -> Void) {
        stop()
        link = CADisplayLink(target: self, selector: #selector(step))
        link?.add(to: .main, forMode: .common)
        _handler = handler
    }

    func stop() {
        link?.invalidate()
        link = nil
        lastTime = 0
        count = 0
    }

    private var _handler: ((Int) -> Void)?

    @objc private func step(_ link: CADisplayLink) {
        if lastTime == 0 {
            lastTime = link.timestamp
            return
        }
        count += 1
        let delta = link.timestamp - lastTime
        if delta >= 1 {
            let fps = Int(round(Double(count) / delta))
            _handler?(fps)
            count = 0
            lastTime = link.timestamp
        }
    }
}

二、Instruments 的正确打开方式

1. Time Profiler

定位 CPU 热点函数,找到“最耗时”的逻辑。

2. Core Animation

查看掉帧区域和渲染瓶颈,尤其适合排查复杂列表和动画。

3. Allocations / Leaks

观察内存曲线是否持续上升,定位泄漏对象。

4. Main Thread Checker

找出不应该在主线程执行的任务。

三、用 Signpost 标记关键流程

import os.signpost

let log = OSLog(subsystem: "com.jj.app", category: .pointsOfInterest)
let signpostID = OSSignpostID(log: log)

func renderFeed() {
    os_signpost(.begin, log: log, name: "FeedRender", signpostID: signpostID)
    // 渲染逻辑
    os_signpost(.end, log: log, name: "FeedRender", signpostID: signpostID)
}

这样可以在 Instruments 中清晰看到“关键业务流程耗时”。

四、卡顿排查流程(实战版)

  1. 复现场景(如列表滑动、进入详情)
  2. 打开 Time Profiler + Core Animation
  3. 找到主线程耗时函数
  4. 判断是“计算重”还是“IO/解码重”
  5. 调整代码并对比优化前后

五、性能问题典型场景与解决

1. 图片解码导致掉帧

问题UIImage(data:) 在主线程解码成本高。

解决:后台线程解码 + 缩略图。

import ImageIO

func downsample(data: Data, to size: CGSize, scale: CGFloat) -> UIImage? {
    let options = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let source = CGImageSourceCreateWithData(data as CFData, options) else { return nil }

    let maxDimension = max(size.width, size.height) * scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimension
    ] as CFDictionary

    guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
    return UIImage(cgImage: image)
}

2. JSON 解析阻塞主线程

问题:解析大 JSON 在主线程执行。

解决:解析放到后台线程。

DispatchQueue.global().async {
    let result = try? JSONDecoder().decode([Post].self, from: data)
    DispatchQueue.main.async {
        self.items = result ?? []
        self.tableView.reloadData()
    }
}

3. 列表布局过重

问题cellForRowAt 做了大量计算。

解决:预计算高度、缓存布局结果。

final class HeightCache {
    private var cache: [Int: CGFloat] = [:]

    func height(for id: Int) -> CGFloat? { cache[id] }
    func setHeight(_ height: CGFloat, for id: Int) { cache[id] = height }
}

六、内存与泄漏排查

1. 闭包循环引用

class PostVC: UIViewController {
    var onFinish: (() -> Void)?

    func setup() {
        onFinish = { [weak self] in
            self?.dismiss(animated: true)
        }
    }

    deinit { print("PostVC deinit") }
}

2. 通知未移除

class ObserverVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(onMsg), name: .init("msg"), object: nil)
    }

    @objc private func onMsg() {}

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

七、启动性能优化要点

  • 启动时只做“必须任务”
  • 非关键模块延迟初始化
  • 同步 IO 改为异步读取
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 必要初始化
    DispatchQueue.global().async {
        // 延迟加载统计、日志等
    }
    return true
}

八、验证与回归

优化后必须验证:

  • FPS 是否提升
  • CPU 曲线是否下降
  • 内存是否稳定
  • 核心业务是否被影响
JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇