关于ios:如何优化iOS系统上的图文评论UI界面

42次阅读

共计 3533 个字符,预计需要花费 9 分钟才能阅读完成。

在咱们的社交 APP 上,⽤户的动静由精美的照⽚、视频和⽂字组成。对于每张照⽚和视频,咱们都会展现出残缺的题目和五个最新评论。

因为⽤户喜爱使⽤题目来讲述照⽚背地的故事,因而它们通常很⻓、很简单,并且可能蕴含超链接和表情符号。

渲染如此简单的⽂本带来了⼀些问题,它在滚动时造成性能降落。

即便在 iPhone 12 这样的新设施上,简单题目的初始⽂本绘制须要⻓达 50 毫秒,⽽⽂本展现须要⻓达 30 毫秒,渲染速度很慢。

⽂本问题还是简略问题,有时咱们须要加载更加简单的图⽚甚⾄视频。

所有这些步骤都发⽣在 UI 线程上,导致 app 在⽤户滚动时丢帧。

当主线程必须解决太多操作时,最常⻅的结果是呈现丢帧景象,当咱们不能保障 60 fps(每 16.67 毫秒⼀ 帧)时就会发⽣这种景象。

基础知识

在开始之前,最好先理解本⽂的基本概念。

主线程不应该⽤于沉重的操作,⽽次要⽤于:
1、承受⽤户输⼊ / 交互;
2、显示后果并更新 UI。

精准地辨认并调试丢帧问题

有时咱们很容易发现掉帧问题,因为掉帧最常⻅的表现形式是⽆响应 / 卡顿。

咱们能够使⽤ 友盟 + U-APM查看在 iPhone12 这样的新款设施上是否会发⽣卡顿。

显⽽易⻅在 iPhone12 上也发⽣了卡顿,由此推断咱们的代码存在优化空间,⽽并⾮⽤户的设施 配置问题。

接下来,咱们须要更精确的⽅法来跟踪卡顿问题。

咱们尝试了使⽤ CADisplayLinkTimeProfiler

使⽤ CADisplayLink 类:

class DroppingFramesHelper: NSObject {
private var firstTime: TimeInterval = 0.0
private var lastTime: TimeInterval = 0.0
func activate() {
let link = CADisplayLink(target: self, selector: #selector(updat
link.add(to: .main, forMode: .commonModes)
}
@objc private func update(link: CADisplayLink) {
if lastTime == 0 {
firstTime = link.timestamp
lastTime = link.timestamp
}
let currentTime = link.timestamp
let elapsedTime = floor((currentTime - lastTime) * 10_000)/10
let totalElapsedTime = currentTime - firstTime
if elapsedTime > 16.7 {print("[DFH] Frame was dropped with elpased time of \(elapse}
lastTime = link.timestamp
}
}

而后,在 AppDelegate 的⽅法中拜访它的⼀个实例:

didFinishLaunchingWithOptions:DroppingFramesHelper().activate()

当初,如果测试程序呈现丢帧的状况,咱们能够在管制台上监控它们:

采取措施

当初通过控制台和 友盟 + U-APM晓得了掉帧的状况存在,咱们能做些什么呢?能够采取⼀些下⾯这些措施:

(1)缩小视图和通明视图的数量
(2)最⼩化“间断调⽤函数”中的负载
(3)解码 JPEG 图像
(4)离屏渲染

咱们将会⼀⼀进⾏探讨。

1、缩小视图和通明视图的数量

为了提⾼应⽤程序的性能,⾸先要做的事是:

• 缩小视图的数量。
• 升高透明度。

解决的⽅法很简略:

label.layer.opacity = 1.0
label.backgroundColor = .white

为了更加容易地察看到重叠的透明度,咱们能够使⽤ ⼀个⾮常⽅便的⼯具:调试 -> 视图调试 – > 渲染 -> 颜⾊混合层。

这个⼯具让咱们能够轻松地发现重叠的视图,如下图所示:

在咱们不须要时,这⾥使⽤标签将背景颜⾊设置为不清晰。

2、最⼩化“间断调⽤函数”中的负载

显⽽易⻅,像 cellForItemAt indexPathscrollViewDidScroll 这样被间断调⽤的函数必须运算得⾮常快。

所以咱们尽可能使⽤最“单纯”的视图 / 单元格,并使⽤⾮常笨重疾速的运算⽅法。(例如,不波及布局束缚、对象调配的配置)

3、解码 JPEG 图像

当咱们解决丢帧问题时,常⻅的“可优化点”是图像解码。

通常,这个操作是在主线程上是由 imageViews 实现的,但在图像⾮常⼤的时候会导致咱们的应⽤程序变慢。

为了缓解这个问题,⼀种解决⽅案是将解码⼯作移⾄后盾队列。这样,操作不会像
UIImageView 采⽤的失常解码那样⾼效,但 mainThread 将是闲暇的。

在后盾解码图像:

extension UIImage {class func decodedImage(_ image: UIImage) -> UIImage? {guard let newImage = image.cgImage else { return nil}
// To optimize this, you can some cache control.
let colorspace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil,
width: newImage.width,
height: newImage.height,
bitsPerComponent: 8,
bytesPerRow: newImage.width * 4,
space: colorspace,
bitmapInfo: CGImageAlphaInfo.noneSkipFir
context?.draw(newImage, in: CGRect(x: 0, y: 0, width: newImage.w
let drawnImage = context?.makeImage()
if let drawnImage = drawnImage {return UIImage(cgImage: drawnImage)
}
return nil
}
}

能够增加⼀些进⼀步的缓存管制以提⾼效率:

import UIKit
class AsyncImageView: UIView {
private var _image: UIImage?
var image: UIImage? {
get {return _image}
set {
_image = newValue
layer.contents = nil
guard let image = newValue else {return}
DispatchQueue.global(qos: .userInitiated).async {DispatchQueue.main.sync {}
let decodedImage = UIImage.decodedImage(image)
DispatchQueue.main.async {self.layer.contents = decodedImage?.cgImage}
}
}
}

能够使⽤“AsyncImageView”, 在后盾线程⽽不是主线程中解码图像。

debug 的后期咱们尝试不使⽤ sync,然而程序发⽣了解体⾏为。为了找出起因,咱们使⽤了 友盟 + U-APM 的异样检测进⾏测试。

能够从图中看出,代码导致了 OOM 内存异样报警,这是因为内存正告是在主线程上解决的,⽽咱们正在后盾解决图像,所以如果咱们使⽤太多内存,就会出现意外⾏为并带来极⼤⻛险(例如图中发⽣的解体)

4、离屏渲染

当咱们解决 UI 元素的特定属性时,可能会遇到⼀些离屏渲染问题,因为咱们须要在出现它们之 前筹备渲染这些元素。这意味着⼤量使⽤ CPU 和 GPU。

如何发现了这个问题?

咱们使⽤了⼯具:Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow。

和前⽂第⼆点的例⼦类似,使⽤此⼯具,咱们能够发现以⻩⾊或红⾊突出显示的元素。

以下代码内容:

imageView.layer.cornerRadius = avatarImageHeight / 2.0

咱们使⽤ UIBezierPath 代替,它能够简略地解决特定的离屏渲染问题:

简⽽⾔之,以下是通过调试得出的⼏点教训:

1、防止 CornerRadius 属性;
2、防止使⽤ ShouldRasterize;
3、使⽤ .rounded() 值,因为更容易计算。
4、Shadows 也会导致离屏渲染

其余倡议

读者还能够尝试⼀下以下的优化倡议:

1、⽂本测量(boudingRectWithSize),然而 debug 过程可能⾮常沉重。除⾮⾮常须要,否则请尽量避免使⽤它们。
2、查看构造布局,尤其是使⽤⾃动布局并且必须⽀持旧设施时。
3、尝试将⼯作放⼊后盾队列,但请留神内存正告。

正文完
 0