这周公众号发布的以下文章:
本期知识小集的主要内容包括:
- 一些 UI 性能优化的 tips
- 你的项目中还用热修复吗?
- Assets的几个方便用法
- 两种 App 启动连续闪退检测策略
一些 UI 性能优化的 tips
作者:
-
圆角效果:圆角效果的优化老生常谈,产生性能问题的根源在于原生圆角效果带来的离屏渲染开销。通常我们推荐直接使用圆角的素材,或者提前在子线程将图片进行圆角裁剪,这两者原理相同。除此之外,还有一种思路是在需要圆角的视图最上层添加一个中空的圆角遮罩层,以此来做出圆角效果。这个遮罩层和被盖在下面的视图在显示时会由 GPU 进行图层混合,而图层混合的开销远小于离屏渲染。值得一提的是,由于圆角效果通常在一屏中频繁出现,所以这个遮罩的图片素材可以只加载一次,并且应用于每一个圆角视图,避免重复加载。
-
阴影效果:值得注意的是系统原生的阴影实现要求 layer 的
masksToBounds
值为 YES,所以原生的阴影效果和圆角是不兼容的。高效的阴影实现是为阴影指定 shadowPath,如果你还没用的话,不妨试一下。 -
适时替换轻量控件:
@ibireme
在他的性能优化文章中提出在合适的时候用 CALayer 替换 UIView,这确实有效,不过盲目替换往往会造成代码维护的困难。这里举两个适合的场景:绘制线条时,完全可以替换。以及静态展示图片时,将图片对象赋值给 layer 的 content 属性,也完全可以达到效果。 -
图片解码:图片解码的知识不再赘述,值得一提的是,对于不同的图片格式,不同的解码算法,或者使用系统解码方法时的不同参数设置,都会影响解码性能,如果有这方面瓶颈的,不妨做多种尝试。
再说一个经典的例子:为了实现一个简单的画板需求,有人会在 UIView 上频繁调用 drawRect
方法进行新笔划的绘制,殊不知有一个天生的专用图层对象 CAShapeLayer
是很适合做这件事的。CAShapeLayer 不需要像普通 CALayer 一样创建寄宿图,不会造成巨量内存的使用,并且它使用了硬件加速。
UI 性能优化时,我们常常需要实时监测帧率。这里讲一下 @ibireme
的帧率监测工具 YYFPSLabel
的实现原理:使用 CADisplayLink,在每帧的回调事件中,计数器 c 加一,并且累计时间间隔 t 也进行更新。当时间间隔够 1 秒后,使用 c/t 计算出过去 1 秒的帧率,而后计数器清零,时间戳更新为当前时间戳,再重复之前步骤。因此 YYFPSLabel 的帧率更新周期在 1 秒左右。
你的项目中还用热修复吗?
作者:
前两天知识小集群里有人讨论关于热修复的问题,对此我非常感兴趣,今天作为一个小集和大家探讨一下。虽然目前苹果严禁带有热修复功能的 APP 上线,一旦发现,将增加审核时间(大约是一周的时间)。苹果主要考虑到了安全问题,避免给自己找事,所以干脆禁用了 JSPatch。但是 JSPatch 使用的 API 并没有违反苹果的规定,他也就没有一个十足的理由拒绝你的 APP 上线。这样就导致还有很多公司在悄悄地用 JSPatch。不过原理基本都是对 JSPatch 进行混淆后使用,当然如果你有能力自己实现一个 JSPatch 也可以。
被拒苹果的拒绝理由大概是这样的:
目前我了解到市面上主要通过以下几种方式进行混淆(如果对这个话题感兴趣,后续我们会在【知识小集】公众号 进一步探讨):
方式一:使用官方提供的混淆方式
目前使用官方提供的 JSPatch 服务任然可以过审,据说也是通过静态混淆-宏定义 这中方式。
方式二:Bugly(静态混淆-宏定义)
Bugly 提供了热修复功能,它提供了一种对 JSPatch 混淆的方式。在 BuglyHotfixConfuse_pch.h
文件中把需要混淆的类名方法名替换掉。有兴趣的读者可以在 查看详细代码。
方式三:自己混淆
自己混淆当然是最保守的,苹果很难察觉。某天网上爆出一个 ZipArchive
安全漏洞,而这个漏洞的一个条件就是使用了类似 JSPatch 这种可以动态执行脚本的功能,而被爆出的 APP 经查确实使用混淆后 JSPatch,而他们采用的混淆方式也就是自己混淆。所以自己混淆 JSPatch 这条路是通的。自己混淆主要是理解 JSPatch 的原理,换一种方式来实现。
Assets的几个方便用法
作者:
Assets 想必大家都使用过,今天聊几个 Assets 比较方便的用法。
- 在工程中,某个通用的颜色,我们可能会用宏或者全局变量来表示,这样可以方便大家的使用,但有一个弊端,在
storyboard
或者xib
布局的时候,设置颜色依旧要去设置具体的RGB值;而Assets给我们提供了一个很方便的功能,可以创建New Color Set
,就弥补了刚才方案的缺陷(如下图),并且代码中使用也很方便。
- 在需要拉伸图片的时候,通常会使用 UIImage 的 API 的
-[UIImage resizableImageWithCapInsets:resizingMode:]
这个方法;而 Assets 为我们提供了 Slicing 的功能(如下图),在 Assets 中直接设置后,在 storyboard 和 xib 中就可以直接显示拉伸后的图片,在代码中使用也及其方便,直接用-[UIImage imageNamed:]
方法即可。
- 如果是 Universal 的工程,同一个 UIImageView,在 iPhone 中显示图片 A,在 iPad 中显示图片 B,Assets 可以很方便的通过
Devices
设置,会让代码看着很清爽,不会存在判断机型再去设置图片的恶心代码。在设置横竖屏的时候也可以充分利用Width Class
和Height Class
两个参数(如下图)。
我觉得这 3 个用法在工作中还是很实用的,当然 Assets 还有其他很好用的功能,欢迎大家一起交流。
两种 App 启动连续闪退检测策略
作者:
当我们要做 App 日志上报时,需要考虑到一种行为:App 在启动时就崩溃闪退了,而且当遇到连续启动闪退(也就是每次打开 App 必崩)时,那几乎是灾难,但更可怕是,如果没有有效的监测手段,我们可能对已发生的这种线上严重问题毫不知情。
WeRead 团队博客的[《iOS 启动连续闪退保护方案》] (http://wereadteam.github.io/2016/05/23/GYBootingProtection/) 和 MrPeak 老师的 分别介绍了两种简易的如何检测连续闪退的策略,在这里跟大家分享一下。
- 计时器方法
1)App 本地缓存维护一个计数变量,用于表示连续闪退的次数;
2)在启动入口方法 application:didFinishLaunchingWithOptions:
里判断 App 之前是否发生过连续闪退,如果有,则启动保护流程,自我修复,日志上报等,否则正常启动。判断的逻辑如下:
3)先取出缓存中的启动闪退计数 crashCount,然后把 crashCount 加 1 并保存;
4)接着使用 dispatch_after
方法在 5s 后清零计数,如果 App 活不过 5 秒计数就不会被清零,下次启动就可以读取到;
5)如果发现计数变量 > maxCount,表明 App 连续 maxCount 次连续闪退,启动保护流程,重置计数。
具体的代码如下图所示:
这种计数器方法逻辑简单,与原有的代码耦合小。但存在误报可能(用户在启动 App 后又立即 kill 掉,会被误认为是 crash),不过可以通过设置时间阈值或者在 applicationWillTerminate:
里标记 App 是被手动 kill 来减少误报。
- 时间数组比对
我们可以在本地保存一个 App 每次启动时间、闪退时间、手动关闭时间的时间数组,然后在 App 启动时根据分析各个时间戳判断是否存在连续闪退(当闪退时间减去启动时间小于阈值 5 秒时,则认为是启动闪退),具体如下:
1)App 每次启动时,记录当前时间 launchTs,写入时间数组;
2)App 每次启动时,通过 crash 采集库,获取上次 crash report 的时间戳 crashTs,写入时间数组;
3)App 在接收到 UIApplicationWillTerminateNotification
通知时,记录当前时间戳 terminateTs,写入时间数组。注意,之所以要记录 terminateTs,是为了排除一种特殊情况,即用户启动 App 之后立即手动 kill app。
如果我们正确记录了上面三个时间戳,那么我们可以得到一个与 App crash 行为相关的时间线,如下图:
根据各种时间线的行为特征,我们只需要加上时间间隔判断,就能得知是否为连续两次闪退了。注意,如果两个 crashTs 之间如果存在 terminateTs,则不能被认为是连续闪退。
以上,介绍了两种检测 App 是否存在启动连续闪退的策略。
此外,对于连续闪退的保护方案以及连续闪退如何上报日志,请详细阅读开头提到的两篇博文。
关注我们
知识小集是一个团队公众号,主要定位在移动开发领域,分享移动开发技术,包括 iOS、Android、小程序、移动前端、React Native、weex 等。每周都会有 原创 文章分享,我们的文章都会在公众号首发。欢迎关注查看更多内容。
欢迎关注我们的公众号:iOS-Tips,也欢迎加入我们的群组讨论问题。可以公众号留言 ios、flutter、web、pwa、小程序 等关键词获取入群方式。