前言

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性能优化系列篇之“列表流畅度优化”

iOS 项目优化的更多相关文章

  1. ios项目里扒出来的json文件

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000 } p.p2 { margin: 0.0px 0. ...

  2. iOS项目的本地化处理(多国语言)

    项目的本地化就是:iOS系统在不同语言环境下自动切换语言,从而实现一个app发布到全世界各个国家的AppStore上. 我们不仅仅需要在iOS项目中做本地化处理,在上架iOS APP的时候,也需要做对 ...

  3. 【腾讯Bugly干货分享】微信读书iOS性能优化

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...

  4. iOS性能优化:Instruments使用实战

    iOS性能优化:Instruments使用实战   最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...

  5. IOS 性能优化的建议和技巧

    IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...

  6. IOS 项目问题总结

    把自己项目中遇到的问题总结一下,供大家参考,希望大家多多提出意见!! 在Xcode 6.2中遇到Your build settings specify a provisioning profile w ...

  7. 深入浅出聊Unity3D项目优化:从Draw Calls到GC

    前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...

  8. Unity3D项目优化(转)

    前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...

  9. 检查iOS项目中是否使用了IDFA

    (1)什么是IDFA 关于IDFA,在提交应用到App Store时,iTunes Connect有如下说明:   这里说到检查项目中是否包含IDFA,那如何来对iOS项目(包括第三方SDK)检查是否 ...

随机推荐

  1. 每日一点:git 与 github 区别

    絮絮叨叨在前:以前的公司,都用svn 进行代码管理.最近我那程序猿先生真的受不了我,强迫我使用tortoiseGit. 一开始对于 git 和 github 傻傻分不清,干脆自己整理资料,总结一下. ...

  2. redis 出现(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details

    如果在ubuntu安装的redis含端口使用,但是某些时候常常出现 (error) MISCONF Redis is configured to save RDB snapshots, but is ...

  3. React-redux: React.js 和 Redux 架构的结合

    通过Redux 架构理解我们了解到 Redux 架构的 store.action.reducers 这些基本概念和工作流程.我们也知道了 Redux 这种架构模式可以和其他的前端库组合使用,而 Rea ...

  4. MATLAB神经网络(7) RBF网络的回归——非线性函数回归的实现

    7.1 案例背景 7.1.1 RBF神经网络概述 径向基函数是多维空间插值的传统技术,RBF神经网络属于前向神经网络类型,网络的结构与多层前向网络类似,是一种三层的前向网络.第一层为输入层,由信号源结 ...

  5. kafka实现无消息丢失与精确一次语义(exactly once)处理

    在很多的流处理框架的介绍中,都会说kafka是一个可靠的数据源,并且推荐使用Kafka当作数据源来进行使用.这是因为与其他消息引擎系统相比,kafka提供了可靠的数据保存及备份机制.并且通过消费者位移 ...

  6. [日志分析]Graylog2采集Nginx日志 主动方式

    这次聊一下Graylog如何主动采集Nginx日志,分成两部分: 介绍一下 Graylog Collector Sidecar 是什么 如何配置 Graylog Collector Sidecar 采 ...

  7. Python - 函数形参之必填参数、缺省参数、可变参数、关键字参数的详细使用

    Python函数形参 必传参数:平时最常用的,必传确定数量的参数 缺省参数:在调用函数时可以传也可以不传,如果不传将使用默认值 可变参数:可变长度参数 关键字参数:长度可变,但是需要以kv对形式传参 ...

  8. Android微信逆向--实现发朋友圈动态

    0x0 前言 最近一直在研究Windows逆向的东西,想着快要把Android给遗忘了.所以就想利用工作之余来研究Android相关的技术,来保持对Android热情.调用微信代码来发送朋友圈动态一直 ...

  9. 「踩坑记」Android API 判断权限申请结果的闪退问题

    这几天尝试着用Android Studio写一个小工具的时候遇到了一个动态权限申请的问题.权限的申请使用的语句为: ActivityCompat.requestPermissions(this, ne ...

  10. Asp.Net Core 3.1 获取不到Post、Put请求的内容 System.NotSupportedException Specified method is not supported

    # 问题 是这样的,我.net core 2.1的项目,读取.获取Post请求内容的一段代码,大概这样: [HttpPost] public async Task<IActionResult& ...