手记

iOS-性能优化的那些事

前言
本人在这家公司已经三年多了,这款三年多我一直在做的APP也烂熟于心,APP也0到1到目前的500万的用户量;对于APP的功能来说也是比较全面的,用到的技术知识点也比较多吧,APP的优化也是一直在做的事情,而且APP性能的优化也不是一朝一夕的事情,在此离别之际,我将详细说明讲解一下我在三年里对APP性能优化方面做过的一些事,大家仁者见仁智者见智,也欢迎大家进群提供宝贵的意见和建议!

基础优化
使用ARC,现在的iOS开发大家用的都是ARC,几乎没有人再去使用MRC了,使用ARC的好处就是不用再时时刻刻注意要释放创建的对象了;避免使用xib或者storyboard。

这里说一下xib和sb的缺点吧,如下:

1.占用API包比较大;
2.导致APP启动时间比较耗时,因为在APP启动main()以前需要加载他们;
3.加载速度比较慢;
4.后期的版本更新迭代维护时间成本比较高;
5.多人开发容易引起冲突。

列表图片优化
列表不论在哪一个APP中是使用最为广泛的一款控件了,在我的APP中也不例外,我们的APP只要功能列表类似于微信的朋友圈,图片有0~9张的形式还可以是视频文件;
先说一说图片吧,如果一个列表都是9张图片,列表在加载和滑动的时候会耗用过多的内存;我在这里是把图片这一块单独抽出来做一个图片资源的封装,然后根据创建的图片容器(UIImageView)的大小加载缩略图,我们公司的APP存储用的是七牛云存储,所以在获取图片资源的时候,只需要设置对应的字段就可以拿到缩略图了,点击的查看大图的时候才查看原图;

再说说视频,视频的处理逻辑和图片差不多,这里在CELL上我们使用一个UIImageView来替代视频播放器,可以去出视频的第一帧作为封面,点击图片的时候才调用我们封装好的视频播放组件。

复用机制
说道复用,我们可能常用的有CELL的复用,其实我们也可以自己写我们的服用,就比如上图中的列表,我们一般采用的思路就是在主控制器上添加一个UIScrollView,再根据有多少个小标题类型创建多少个子控制器,接着把子控制器添加到主控制器的childViewControllers中,最后把子控制器的视图添加到UIScrollView上,有没有更节省内存空间的做法呢???
当然有的,我们可以只创建5个控制器然后丢到我们的可重用数组中,根据每次滑动去加载不同的缓存数据;
还比如我在直播间创建刷礼物的视图动画的时候,因为最多只能显示三条,那么我只会创建三个视图,当有大于三条的礼物信息过来的时候也只会创建三个视图,然后丢进我的可重用数组里面,每个视图动画完成以后才重新复制Model,如下图左下角的礼物动画最多三个,最少没有:

就如上面所说,在项目中我们可以在很多地方创建属于我们自己的重用机制!

离屏渲染
在开发中,我们常用有圆角处理、阴影、遮罩等等;先说说圆角优化吧,我们一般设置圆角的方式如下:

view.layer.cornerRadius = 10;
view.layer.masksToBounds = YES;
这样处理的渲染机制是GPU在当前屏幕缓冲区外新开辟一个渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一定数量,会触发缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上——掉帧。

优化方案:
a.可以做一个透明的png图片盖在上面;
b.使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角;
c.使用CAShapeLayer和UIBezierPath设置圆角;
d.也可以将图片的处理放在服务端(比如:七牛云储存就可以设置图片的圆角);
e.尽量把view设置成不透明的。

说明:离屏渲染还可以做很多东西,具体的可以自行查找!

懒加载
这里的懒加载主要说的是懒加载思想,可以懒加载的类型有很多,当然好处有很多,最主要的是可以节省内存资源;

重大开销对象
我们在APP使用过程中会用到很多重大开销对象(比如NSDateFormatter和 NSCalendar),如我们在列表需要计算用户年龄的时候会经常用到NSDateFormatter,还有一些时间的格式化输出,或者在网络请求的时候需要传一些时间戳,所以我们可以把NSDateFormatter放在单利里面,这样就不用经常创建了,其他的也是同样的道理。

还有就是,像年龄生日什么的最好不要由前端APP来计算,最好放在服务器上,由服务端计算好然后再传给我们。

内存警告
如果程序在运行过程中发生了内存警告(didReceiveMemoryWarning),我们需要快速应急处理一下,不要用不了多久APP就会被杀死掉,所在在收到内存警告主要思路就是想着去清理栈上的东西,以后我们可以做以下几点:

a.清理不需要的已经创建的对象,不论是试图还是工具类;

b.释放单利里面不需要的对象,比如上文提高过得重大开销对象;

c.如果有正在下载的任务,取消或者暂停全部任务;

d.如果使用了SDWebImage,可以清理一下缓存clearMemory;

主线程流畅
不要阻塞主线程,要保证主线程的流程性;我们不要把一些重大开销对象放到主线程里面,我们可以创建子线程去处理这些事情;比如多个网络请求结束一起刷新UI、多个动画效果、数据的读写操作等等;

数据缓存
做缓存这个事情,一方面减小内存的小号,还有很重要的一方面就是优化用户体验;可以做缓存的东西很多,我们可以对数据接口进行缓存,还可以对WebView进行缓存,还有图片缓存、高度缓存等等;

在这里不得不说一下,数据存储了;我们在做缓存的时候选择正确合适的存储方式也很难重要,当然大家也可以根据项目的实际需求来选择存储,我们公司的APP本地存储我选择的是SQLite,管理类是我基于FMDB的又一次封装.

网络API优化
这个优化不单单需是前端小伙伴的事,还需要后端开发人员的配合,避免一些不必要数据的返回,更要避免在APP端处理或者计算太多东西,比如年龄、星座的计算;即使后端的小伙伴把一些不必要的数据返回了,我们在创建数据模型Model的时候可以选择不去接收!

PS:这里我需要吐槽一下,以前公司有个java后端,返回的数据全部是一个表的实体类,管你有用没用,直接一股脑的全给你,自己去计算和自己去查找,那真叫一个心累啊,所以一个优秀的API开发工程师也是一个非常重要的原因!

自动释放池
自动释放池(autoreleasepool)在MRC的时代真是用的非常多,但是现在的项目都是ARC,自动释放池用的也就比较少了,因为有系统帮我们监管,但是如果我们一个页面创建了太多的类或者对象,如果等页面销毁的时候由系统统一释放难免会出现一个峰值影响整体性能,这时候我们就可以考虑使用autoreleasepool了,避免峰值的出现!?

还比如我们在遍历一些大数组或者字典的时候,可以使用自动释放池来降低内存峰值,比如:

NSArray *bigArray = @[] //这是一个很大很大的数组
NSMutableArray *newArray = [[NSMutableArray alloc] init];
for (NSStirng *item in bigArray) {
@autoreleasepool {
ZFJModel *model = [[ZFJModel alloc] init];
model.item = item;
[newArray addObject: model];
}
}
APP启动优化
在AppDelegate里面我们会写一些很多东西,你写的东西越多越影响APP的冷启动时间就会越长,因此我们需要对AppDelegate进行减负,具体如何减负大家可以根据各自项目的实际情况,第一可以删除一些不是必须的东西;第二可以把一些东西写在别的地方(如RootViewController),我在APP的RootViewController写的东西比如IM聊天的配置初始化登录,一些未处理的任务,还有一些版本的坚持和缓存的清理等等!

Instruments
Instruments 是什么我这里就不作过多的介绍了,这里我主要说我用Instruments干什么;我平时用Instruments主要干两件事,一件事是检查内存泄漏,还有一件事是检查耗时函数;对于内存泄漏我们对应修改补漏就行了,对于耗时函数要么就换一种写法,要么就创建一个子线程来处理!

?这里要说一个我的习惯,我的所有的控制器都继承于BaseViewController,然后我再BaseViewController里面写了以下的一部分代码:

  • (void)dealloc{
    NSLog(@"=== %@ dealloced! ===", NSStringFromClass([self class]));
    }
    如果我的页面销毁不走dealloc的打印,那么页面肯定有没有被释放的对象,要么是发生循环引用了,要么是需要手动释放,具体打印以下对象的引用计数!
0人推荐
随时随地看视频
慕课网APP