最新iOS性能优化 - 分析&应用,腾讯面试题目java

最后

针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

一. 性能指标

APP 的性能指标主要是包括 CPU、GPU、内存、电池耗电、网络加载几个大的方面,网络加载在下文会提及,电池耗电主要是由于 CPU、GPU、网络等因素决定,所以不作为基础的指标。

1. CPU占有率

iOS APP 为单进程的应用,不涉及到跨进程通讯(不包括 Extention)。

1.1 线程使用

线程的使用及通讯会带来 CPU 的开销,大量的线程启用自然时候使得 CPU 使用率上升,不同线程之间的通讯需要添加锁来确保线程安全,又加大了线程的使用周期。

使用线程时需要注意:

  • 不要在并发队列中使用过多的线程锁操作,如果必要则需要降低加锁代码的执行时耗,尽量精简化,也可以直接采用串行队列来实现同步。
  • 不同场景使用不同的 GCD 队列,例如  执行多个独立的 I/O 操作 | 数据编解码(全局并发队列:任务执行不分先后)、单线程数据库操作(自定义串行队列:同步执行任务)、实现文件的多读单写 | 多个并行任务的组合操作(自定义并发队列:执行的任务存在依赖关系)。
1.2 执行方法耗时

常见较为耗时的场景如下。

  • 对象创建:对象的创建会分配内存、调整属性,个别类的对象创建则更为耗时,如NSDateFormatter、NSCalendar,频繁生产临时变量可以改成单例调用。
  • 布局计算:视图布局的计算会由于不同逻辑的运行时耗而带来不同程度的 CPU 开销。
  • 图像绘制:图像的绘制通常是指用那些以 CG 开头的方法把图像绘制到画布中,然后从画布创建图片并显示这样一个过程。
  • 图片解码:图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。
  • 图片过大:超过 GPU 的最大纹理尺寸时,图片需要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗(iOS-图片裁剪/缩略性能探究)。
/* 避免频繁创建NSDateFormatter实例 */
+ (NSDateFormatter *)dateFormatter {
    static NSDateFormatter *formatter;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        formatter = [[NSDateFormatter alloc] init];
        formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    });
    return formatter;
}
1.3 I/O操作

I/O 操作是指文件的读取、写入、更新。磁盘 I/O 的执行速度要远低于 CPU 和内存的速度。文件的读写主要性能开销是 I/O,同时也会有小占比的 CPU 与内存的消耗。

在 APP 运行过程中,由于 I/O 操作速度较慢,方法的调用时耗自然也就更大,通常会使用多线程来进行文件的读写操作,防止主线程的堵塞。文件大小与文件数量关系着线程资源的开销,最终决定 CPU 的性能开销。

1.4 CPU使用分析

Xcode自带的 CPU 检测工具:

第三方开源的 CPU 检测组件:

  • 滴滴的 DoraemonKit,一款面向泛前端产品研发全生命周期的效率平台。

2. GPU渲染-FPS

FPS :Frames Per Second 的简称缩写,意思是每秒传输帧数,可以理解为我们常说的“刷新率”(单位为Hz)。FPS 是测量用于保存、显示动态视频的信息数量。每秒钟帧数愈多,所显示的画面就会愈流畅,FPS 值越低就越卡顿,所以这个值在一定程度上可以衡量应用在图像绘制渲染处理时的性能。iOS 系统中正常的屏幕刷新率为 60Hz(60次每秒)。页面渲染优化相关内容会在下文根据具体场景列举说明。

Xcode 自带的 FPS 检测工具:

第三方开源的FPS检测组件:

  • 滴滴的 DoraemonKit,一款面向泛前端产品研发全生命周期的效率平台。

3. 内存

这里讲的内存主要是内存缓存,不对内存管理做过多的叙述,有兴趣可以看一下我之前写的文章-iOS内存管理

每一台 iPhone 机子都拥有固定的物理内存空间,也就是我们常说的运行内存 2个G、4个G 这种硬件配置。系统的运行会有一部分的内存开销,其他的则由运行的 APP 共同分配。

和安卓不同的是,IOS 系统并没有限制固定的内存分配规则,所以运行一个 APP,有时候可以达到几百甚至超过 1GB 的内存使用,不过这样无限制的消耗内存会导致内存警告,最终导致进程被杀掉。

内存的使用场景:

  • 临时/局部,临时申请的内存空间,使用完即释放,如二级页面的数据源缓存。
  • 静态/全局,静态内存,static、const、extern 声明的常量与对象(单例对象、全局数组)。

内存的缓存策略:MemoryCache

  • 常规缓存,NSDictionary、NSArray、NSSet、NSPointerArray / NSMapTable / NSHashTable(支持弱引用)。
  • 缓存+淘汰策略,LRU、LFU、NSCache(LFU 优先于 LRU)。

Xcode自带的内存检测工具:

第三方开源的内存监控组件:

  • Faceboo k的 FBMemoryProfiler,分析 iOS 内存使用和检测循环引用,仅检测 OC。
  • 腾讯的 OOMDetector,OOM 监控、大内存分配监控、内存泄漏检测,支持监控 C++ 对象和 malloc 内存块以及 VM 内存。

二. 场景应用

1. 启动

iOS 冷启动流程分为 Pre-main 与 main,也就是 main 函数入口的之前与之后的两部分。网上这方面的资料也很多,这里就大概过一下,相关的博文推荐:抖音-iOS启动优化之原理篇抖音-iOS启动优化之实战篇抖音-基于二进制文件重排的解决方案

1.1 Pre-main

1)具体流程

  • Dyld:动态链接器,在系统内核做好程序准备工作之后,交由 dyld 负责余下的工作。
  • Load Dylibs:加载动态库,iOS 的动态库包含 dylib 与动态 framework,静态库包含 .a 与静态 framework。
  • Rebase:将镜像读入内存,修正镜像内部的指针,并以 Page 为单位进行签名验证,保证不会被篡改,性能消耗主要在 I/O。
  • Bind:查询符号表,设置指向镜像外部的指针,性能消耗主要在 CPU 计算。
  • Objc:读取所有类,将类对象其注册到这个全局表中;读取所有分类,把分类加载到类对象中; 检查 selector 的唯一性。
  • initalizers:dyld 开始运行程序的初始化函数,调用每个 Objc 类和分类的 +load 方法,调用C/C++ 中的构造器函数,和创建非基本类型的C++静态全局变量。

2)优化策略

  • 合并自定义的动态库,删除冗余的 dylib 与动态 framework。
  • 将动态库转为静态库(Mach-O Type:Static Library)。
  • 使用二进制重排,减少 Page 载入的缺页中断问题。
  • 减少 ObjC 类、方法(selector)、类别(category)的数量。
  • 减少 ObjC 的 +load 方法,类/协议的绑定通过启动项自注册的方式实现。
  • 减少C的 constructor 函数、C++ 静态对象。
1.2 Main

1)具体流程

2)优化策略

  • SDK 注册较为耗时的可以使用异步并发加载,部分二级页才用到的 SDK 可以采用懒加载的形式。
  • 防止启动时有过多的串行接口操作,尽量精简。
  • 避免启动后出现过多的耗性能操作,例如频繁读写 I/O,数据解码等耗时方法的调用。

2. 页面

2.1 原生页面-渲染原理

1)View的渲染

View 的展示是由 Layer 实现,View 主要处理 Touch 响应链相关的事件。UIView 提供了绘图 API-drawRect,可以在该方法中获取图形上下文,并实现图形的绘制,调用 setNeedsDisplay 刷新绘制。

当 View / Layer 的 frame 与图层结构发生改变,或者是手动调 setNeedsLayout / setNeedsDisplay方法时,View / Layer 被标记为待处理状态,系统会监听 mainRunLoop 的 BeforeWaiting / Exit 状态,在监听回调中遍历所有待处理 View / Layer,实现 UI 的刷新。View 添加 subView 也是在 mainRunloop 的回调中实现 UI 绘制,所以 View 的 layoutSubviews / drawRect 方法只有在不同 mainRunloop 的回调节点才会多次触发。

上面提到 View 的本质是 Layer,Layer 则包含 contents,这个 contents 指向的是一块缓存又名Baking Store。Objective-c 提供了 Core Animation 的渲染内核,底层是由 OpenGL 实现 GPU 渲染,流程大致如下:

  1. 初始化用于绘制的上下文 EAGLContext;
  2. 创建帧缓冲区和渲染缓冲区,设置画布的宽高;
  3. 添加附件,比如颜色附件或者深度附件;
  4. 切换到帧缓冲区,在帧缓冲中进行绘制;
  5. 切换到屏幕缓冲区,读取帧缓冲中的信息;
  6. 绘制到屏幕上,在容器 dealloc 时删除缓冲区。

所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。

2)GPU的离屏渲染

  • 当前屏幕渲染,指的是 GPU 的渲染操作在当前用于显示的屏幕缓冲区进行。
  • 离屏渲染,指的是 GPU 在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作。

离屏渲染主要开销包括创建新的缓冲区、屏幕缓冲区到离屏缓冲区的来回切换。

iOS 中主要是由于 Layer 的某些属性设置导致的离屏渲染,常见的有遮罩(mask)、透明(opaque)、阴影(shadow)、光栅化(rasterize)、圆角(cornerRadius),离屏渲染会让 APP 的交互变得不流畅(如:比较复杂的图文混排 List),需要避免频繁触发离屏渲染,相关博文推荐:iOS离屏渲染场景及优化方案

2.2 原生页面-复杂布局

原生页面的复杂布局一般有两种常见的场景:

  • 微博、空间、朋友圈这些样式多样化的列表页,呈现的特点是 Cell 的复用程度较低,单元图层元素也较为复杂。
  • 股票K线图,图像编辑、动态图表等图形绘制页面,主要特点是在一个固定的画布中,根据数据源与对应的场景需求,快速地绘制并展示图形。

2)低复用列表

  • 离屏渲染:mask/opaque/shadow/rasterize/cornerRadius,这些属性都会引起离屏渲染,低刷新频率并不会出现卡帧的现象,主要出现在列表页快速滑动的时候。
  • 视图过度绘制:Cell 中的非交互图层使用 Layer 代替;减少图层嵌套,精简图层的数量;使用异步渲染,开启子线程把复杂的图层元素绘制在 ImageContext,获取 Image 后切回主线程展示。
  • 数据加载:通过懒加载 / 预加载,具体场景具体应用;使用异步线程实现数据的获取 / 加工(I/O操作、数据换算)。
  • 图片预解码:把图片解码从主线程切换到子线程,使用异步线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。
/* 使用异步线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片 */
- (void)drawImage:(UIImage *)image {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        CGImageRef imageRef = image.CGImage;
        size_t width = image.size.width;
        size_t height = image.size.height;
        size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
        size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
        CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
        uint32_t bitmapInfo = CGImageGetBitmapInfo(imageRef);

        CGContextRef contextRef = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo);
        CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef tImageRef = CGBitmapContextCreateImage(contextRef);
        CGContextRelease(contextRef);

        dispatch_async(dispatch_get_main_queue(), ^{
            self.layer.contents = (__bridge id)tImageRef;
            CGImageRelease(tImageRef);
        });
    });
}

3)频繁画布重绘

  • 统一事件源触发:定时器与 Touch 事件源的统一入口,避免事件源过于频繁的触发图层重绘。
  • 减少局部刷新:以整体数据源的变化为刷新频次,减少局部刷新的频率(与统一事件源类似)。
  • 减少图层嵌套:图形绘制的场景减少 Layer 以及图层嵌套,使用效率更优的 CGGraphis-API。
  • 使用异步绘制:开启子线程把复杂的图层元素绘制在 ImageContext,获取 Image 后切回主线程展示。
/* 异步绘制,在需要频繁重绘的视图上效果最好(比如绘图应用、TableViewCell之类)*/


#### 总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

> (跨平台开发(**Flutter**)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

> ![](https://img-blog.csdnimg.cn/img_convert/d8f4665241356e88daff0954ad104df0.webp?x-oss-process=image/format,png)




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

> (跨平台开发(**Flutter**)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

> [外链图片转存中...(img-aKubv9WD-1715372774229)]




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值