iOS 项目优化

前言

iOS性能优化系列篇之“优化总体原则”

  • 不要提前过度优化
  • 要找到性能瓶颈
  • 要在不同性能指标间权衡
  • 要理解优化任务的底层运行机制
  • 要有技术保障体系

一、启动速度优化

1.1 学习文章

1.2 操作步骤

  1. 查看启动时间

    配置 Xcode 环境变量在日志中打印启动时间:

    • 打开工程 -> Edit Scheme -> Run -> Environment Variables

    根据需要添加 DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS 环境变量。value 设置为 1(表示 YES),开启这个功能。


    Total pre-main time: 617.58 milliseconds (100.0%)
             dylib loading time: 472.75 milliseconds (76.5%)
    rebase/binding time: 27.01 milliseconds (4.3%)
    ObjC setup time: 28.90 milliseconds (4.6%)
    initializer time: 88.76 milliseconds (14.3%)
    slowest intializers :
    libSystem.B.dylib : 8.81 milliseconds (1.4%)
    libMainThreadChecker.dylib : 14.42 milliseconds (2.3%)
    AFNetworking : 18.43 milliseconds (2.9%)
    Realm : 20.98 milliseconds (3.3%)
    CYKJBasic : 12.96 milliseconds (2.0%)

    包括执行以下步骤的所有时间:

    1. 解析镜像
    2. 映射镜像
    3. Rebase 镜像
    4. Bind 镜像
    5. 镜像初始化
    6. 调用 main()方法
    7. 调用 UIApplicationMain() 方法
    8. 调用 applicationWillFinishLaunching 回调
  2. 优化 main() 函数之后的执行时间

    • 使用代码绘制 UI,减少或者不用 xib 和 storyboard
    • 延迟初始化和加载不必要的 UIViewController 和 View。
    • 使用后台线程处理耗时的任务
    • 能延迟初始化的尽量延迟初始化
  3. 优化 main() 函数之前的执行时间

    Session 所要传达的内容:

    • 使用 DYLD_PRINT_STATISTICS 测试启动加载时间
    • 减少自定义的动态库集成
    • 精简原有的 Objective-C 类和代码
    • 移除静态的初始化操作
    • 使用更多的 Swift 代码

    优化:

    • loading dylib

      减少动态库的数量。尽量保证将 App 现有的非系统级的动态库个数保证在 6 个以内。采取的方式:1、合并动态库;2、使用静态库。

    • rebase/binding

      Rebase 和 Binding 都是为了解决指针引用的问题。对于 Objective-C 开发来说,主要的时间消耗在 Class/Method 的符号加载上,所以常见的优化方案是:

      • 减少 __DATA 段中的指针数量。
      • 合并 Category 和功能类似的类,减少唯一 Selector 的个数。
      • 删除无用的方法和类。
      • 多用 Swift Structs,因为 Swfit Structs 是静态分发的。
      • 减少 c++ 虚函数
    • ObjC setup time

      尽量减少类的数量,可以达到减少这一部分的时间。

    • Initializers

      • 用 initialize 替代 load。(注意:要根据实际情况替换)
      • 减少使用 c/c++ 的 __atribute__((constructor))。推荐使用 dispatch_once()、pthread_once()、std:once()等方法;
      • 推荐使用 swift
      • 不要在初始化中调用 dlopen() 方法,因为加载过程是单线程,无锁,如果调用 dlopen 则会变成多线程,会开启锁的消耗,同时有可能死锁
      • 不要在初始化中创建线程

1.3 实际处理

实际处理时因为要考虑团队的原因,所以只采用了一下三个步骤:

  1. 删除不需要的三方库,因为工程中用 use_frameworks! pod 进来的库是动态库。
  2. 将大部分 +load 方法改为 +initialize 方法,保留部分必要的 +load 方法;
  3. 动态库 -> 静态库,技术上实现了,讨论后最终弃用。
  4. 删除无用的方法和类,减少 rebase/binding 时间。。

1.4 处理效果

慈云 app 启动时间有 700 ~ 850ms,降为 600 ~ 700ms。

如果采用静态方式 pod,可以降为 450 ~ 550ms。

二、接口请求优化

主要集中处理一级 tab 的切换体验,频繁的调用接口,频繁的刷新界面显然是影响用户体验的。优化的思路有以下几点:

  1. 使用 loading 框 + 默认灰色矩形视图;


  2. 使用本地缓存

  3. 每隔 15s(或者 10s) 以上才请求一次,防止频繁触发请求

    @property (nonatomic, assign) CFTimeInterval lastTi;
    - (void)viewWillAppear:(BOOL)animated
    {
    [super viewWillAppear:animated];
    CFTimeInterval nowTi = CACurrentMediaTime();
    // 10 秒内不请求
    if (nowTi - self.lastTi > 10) {
    self.reqData.pageNo = 1;
    [self.service requestPatientConsultList:self.reqData];
    self.lastTi = nowTi;
    }
    }
    - (void)makeRequestAction
    {
    // 接口请求
    }

    CACurrentMediaTime() 在退到后台、手动修改设备时间后没有影响。

  4. 对数据进行判断,数据没有更新不需要刷新界面。

    @property (nonatomic, copy) NSString * primaryCompareMD5;
    // 初始值 @""
    self.primaryCompareMD5 = @"";
    SELF_WEEK;
    // 异步执行
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized (self) {
    // 这里的 data 是要判断的接口数据
    NSString * string = [data componentsJoinedByString:@""];
    if (!string) {
    string = @"";
    }
    NSString * md5 = [CYDXUtil doMd5:string];
    SELF_STRONG;
    // 数据未发生改变,直接返回
    if ([strongSelf.primaryCompareMD5 isEqualToString:md5]) {
    return;
    }
    strongSelf.primaryCompareMD5 = md5;
    // 主线程刷新界面
    dispatch_sync(dispatch_get_main_queue(), ^{
    [strongSelf.tableView reloadData];
    });
    }
    });

这里的策略还是存在点问题:

  1. 不能处理好未读标识。看实际情况,处理从子界面返回时的逻辑。

三、界面滑动优化

3.1 监测界面卡顿工具

  1. 使用系统的 Instrument - CoreAnimation
  2. 使用系统的 Instrument - Time Profiler
  3. YYFPSLabel

在未滑动时,Instrument - CoreAnimation 显示的是 0 FPS。

YYFPSLabel 显示的是 60FPS。

卡顿问题修改后,Instrument - CoreAnimation 需要在设备上运行一遍,然后重新用工具检测;YYFPSLabel 可以直接加入工程,可视化,更加的友好,方便。

Time Profiler 可以查看哪些方法占用时间多,优化那些可以优化的。

3.2 优化处理

顶部菜单栏

Instrument之Core Animation工具

  1. 图层混合

    Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Blended Layers

    正常的为绿色;出现图层混合时为红色。

    • 确保控件的 opaque 属性设置为 true,确保 backgroundColor 和父视图颜色一致且不透明;
    • 如无特殊需要,不要设置低于 1 的 alpha 值;
    • 确保 UIImage 没有 alpha 通道;
  2. 图片缩放

    Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Misaligned Images

    如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。

    这个看情况是否优化,是否需要 UI 配合。

    • 确保图片大小和frame一致,不要在滑动时缩放图片;
    • 确保图片颜色格式被 GPU 支持,避免劳烦 CPU 转换;
  3. 离屏渲染

    Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow

    触发离屏渲染的地方标记为黄色。

    下面的情况或操作会引发离屏渲染:

    • 为图层设置遮罩(layer.mask)
    • 同时设置 layer.masksToBounds 和 corneRadius 属性设置为 true
    • 将图层的 layer.allowsGroupOpacity 属性设置为 YES 和 layer.opacity < 1.0
    • 为图层设置阴影(layer.shadow*)
    • 为图层设置 layer.shouldRasterize = true
    • 文本(任何种类,包括 UILabel、CATextLayer、CoreText 等)
    • 使用 CGContext 在 drawRect: 方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现

    解决:

    • 使用 CoreGraphics 绘制圆角,不使用 mask 或 masksToBounds + corneRadius;
    • 设置阴影是使用 shadowPath;
    • UILabel 如果不是透明的,设置 opaque = YES,Clip To bounds = YES,backgroundColor;
    • 设置图片圆角可以让 UI 切图,也可以使用 CoreGraphics 绘制图片的方式。在 iOS 9.0 以上,图片设置圆角不会触发离屏渲染。

    iOS 阴影,圆角,避免离屏渲染
    iOS - 圆角的设计策略 - 及绘画出没有离屏渲染的圆角

  4. UITableViewCell 优化

    • Cell 复用;
    • 提前计算并缓存 Cell 的高度;
    • 减少 subviews 的个数和层级;
    • 少用 subviews 的透明图层;
    • 如果不是透明视图,背景色不要使用 clearColor;
    • 注意离屏渲染问题;
    • 图片提前在子线程异步解码,SDWebImage 已经实现;
    • 异步绘制(自定义 Cell 绘制)VVeboTableViewDemoYYAsyncLayerAsyncDisplayKit
    • 滑动时,按需加载。注意:这个会导致滑动时出现大量空白,不友好。
    • 尽量显示“大小刚好合适的”图片资源
    • 避免同步的从网络、文件获取数据

四、内存占用

主要检查工程中的内存泄露、循环引用的问题。

  1. instrument - Allocations 动态分析
  2. Analyze 静态分析
  3. 腾讯 MLeaksFinder
  4. 脸书 FBMemoryProfiler

说明:

  1. MLeaksFinder 效果比较好,能查找工程中出现循环引用的的场景,在慈云找到十几处循环引用。
  2. 官方的 instrument 工具效果一般,在慈云 app 只检测出了 3 处循环引用,2 处内存泄露;
  3. Analyze 静态分析可检测出“创建了但未被使用的”变量,需要结合代码逻辑,小心处理。
  4. 脸书的 FBMemoryProfiler 工具不大好用,主要是记录内存的开辟与销毁。

如果要更多的处理内存占用问题,需要分析工程中的代码逻辑,通过使用不同的存储方式、容器类、加载数据方式等,达到减少内存使用的目的。

五、缩小 ipa 包大小

爱奇艺移动应用优化之路:如何让崩溃率小于千分之二

安装包大小的优化,主要包含两大块:资源大小的优化和二进制大小的优化。

5.1 资源大小的优化

资源大小的优化主要包括以下几个方面:

  1. 资源压缩
  2. 未使用、重复资源的删除
  3. 资源上云

具体步骤:

  1. 资源压缩

    Xcode 的编译选项中,提供了 Compress PNG FilesRemove Text MetaData From PNG Files

    但是由于 PNG 是无损压缩,经过 Xcode 压缩后的图片资源,依然很大。

    使用 pngquant 对大多数的 32 位图进行了处理,将其转为 8 位图,并且使用 Zopfli 进行了压缩,这样整体的 PNG 图片资源大概被压缩了 70% 左右。这里要注意,由于一些渐变背景的颜色覆盖范围较大,转为 8 位图颜色丢失较大,表现效果会差很多,所以这些图片要谨慎处理。

  2. 未使用、重复资源的删除

  3. 资源上云

    资源上云可以有效减少包内资源,唯一要注意的是这些资源由于是 lazy load,所以比较适合层级较深的页面使用。

5.2 其他处理

  1. 配置编译选项 Generate Debug Symbols 设置为 NO;

  2. 舍弃架构,如:armv7,根据实际选择。

  3. 编译的版本必须是 release 版本,

  4. 查找内部使用到的第三方库,一方面可以进行删减代码,用不到的类,直接删除,还有第三方库中的图片资源统统删除掉,如果能够自己手写实现的,那费功夫自己写吧

六、引用库升级及替换

持续更新的第三方库也会对性能做出优化,在替换前,需先确定不会给项目引入问题。

七、更多优化

iOS 性能调试
iOS视图成像理论及性能优化
iOS性能优化系列篇之“列表流畅度优化”

posted @ 2020-02-26 13:41  和风细羽  阅读(1253)  评论(0编辑  收藏  举报