IOS异步渲染的原理

打印 上一主题 下一主题

主题 763|帖子 763|积分 2289

UI卡顿

现象简直是用户体验中的一个重要问题,尤其是在移动设备上。用户在使用App时,期望界面可以或许流畅相应,任何的延迟或不顺畅都会影响他们的体验。以下是一些导致UI卡顿的常见原因,以及如何从软件工程的角度进行优化。
1. 掉帧(Frame Drops)

掉帧是指在渲染过程中,某些帧未能按时显示,导致画面不连贯。掉帧的原因大概包括:


  • 主线程壅闭:如果在主线程中执行耗时操作(如网络哀求、复杂计算),会导致UI更新延迟。
  • 复杂的视图层级:过多的视图层级会增长布局和绘制的复杂性,导致渲染时间增长。
  • 不合理的动画:复杂或不优化的动画效果大概导致GPU负担过重,影响帧率。
2. 资源竞争

在多任务环境中,CPU和GPU资源的竞争大概导致性能下降:


  • 高CPU负载:如果CPU正在处理大量任务,大概会影响到UI的渲染。
  • GPU资源争用:多个图形任务同时哀求GPU资源,大概导致渲染延迟。
3. 内存管理

内存的使用不妥也大概导致卡顿:


  • 内存走漏:长时间运行的App如果存在内存走漏,大概导致内存不敷,从而影响性能。
  • 频仍的内存分配和释放:在渲染过程中频仍创建和烧毁对象会增长GC(垃圾回收)的负担,导致卡顿。
4. 网络延迟

在需要从网络获取数据的场景中,网络延迟大概导致UI更新不及时:


  • 异步哀求未处理:如果网络哀求未能及时返回数据,UI大概无法更新,导致用户感知到的延迟。
5. 不合理的绘制计谋

绘制计谋的选择也会影响渲染性能:


  • 过多的重绘:不必要的重绘会增长Render Server的负担,导致帧率下降。
  • 复杂的图形:使用复杂的图形或效果(如阴影、模糊等)会增长GPU的渲染负担。
优化建议

为了淘汰UI卡顿现象,开发者可以接纳以下步调:

  • 异步处理

    • 将耗时操作(如网络哀求、数据处理)放在后台线程中执行,避免壅闭主线程。

  • 优化视图层级

    • 淘汰视图层级的复杂性,使用符合的布局方式(如使用UIView的layer属性进行简朴的图形绘制)。

  • 合理使用动画

    • 使用Core Animation等高效的动画框架,避免在主线程中执行复杂的动画计算。

  • 内存管理

    • 定期检查和优化内存使用,避免内存走漏,使用对象池等技术淘汰频仍的内存分配。

  • 网络哀求优化

    • 使用异步网络哀求,确保UI可以或许及时相应用户操作,使用缓存机制淘汰网络哀求的频率。

  • 绘制优化

    • 使用符合的绘制计谋,避免不必要的重绘,使用setNeedsDisplay和drawRect等方法时要谨慎。

通过以上步调,可以有效提升应用的流畅度,淘汰卡顿现象,从而改善用户体验。
UI渲染流程

在iPhone设备的整体UI渲染流程中,存在一个复杂的管道(pipeline),涉及到App、操作系统(OS)、Render Server和硬件(GPU和显示器)之间的协同工作。以下是对每个阶段的详细分析,包括各自的模块和职责:
1. App阶段



  • 职责:处理用户交互、业务逻辑、数据获取和UI更新。
  • 模块

    • 视图控制器:管理视图的生命周期,处理用户输入和事件。
    • 模型层:负责数据的获取、存储和处理,通常与网络哀求和数据库交互。
    • UI组件:如按钮、标签、图像等,负责展示详细的内容。

  • 潜在问题

    • 主线程壅闭:如果在主线程中执行耗时操作(如网络哀求、复杂计算),会导致UI更新延迟。
    • 不合理的视图更新:频仍的UI更新或不必要的重绘会增长Render Server的负担。

2. Render Server阶段



  • 职责:接收来自App的渲染哀求,处理视图的布局和绘制,并将效果提交给GPU。
  • 模块

    • 布局引擎:计算视图的布局和位置,确保UI元素正确分列。
    • 绘制引擎:将视图内容转换为可渲染的图形,处理图形的合成和优化。
    • 事件处理:处理用户输入事件并更新视图状态。

  • 潜在问题

    • 布局计算复杂:复杂的布局计算大概导致Render Server处理时间过长。
    • 绘制性能:不合理的绘制计谋(如过多的图层或复杂的图形)会影响渲染效率。

3. GPU阶段



  • 职责:将Render Server提交的内容进行现实的图形渲染。
  • 模块

    • 图形API:如Metal或OpenGL,负责与GPU进行交互,提交渲染命令。
    • 着色器:处理图形的渲染效果,包括顶点着色器和片段着色器。
    • 资源管理:管理纹理、缓冲区等GPU资源,确保高效使用。

  • 潜在问题

    • 渲染任务复杂:复杂的渲染任务大概导致GPU无法及时完成渲染。
    • 资源竞争:多个任务同时哀求GPU资源大概导致延迟。

4. 显示阶段



  • 职责:将GPU渲染的内容显示到屏幕上。
  • 模块

    • 显示控制器:负责将渲染效果传输到屏幕,确保内容按时显示。

  • 潜在问题

    • 显示延迟:如果显示控制器无法及时获取到GPU的渲染效果,大概会导致屏幕显示延迟。

整体流程总结


  • 用户交互:用户在App中进行操作,触发事件。
  • App处理:App接收事件,处理业务逻辑,并更新UI状态。
  • Render Server更新:Render Server接收App的更新哀求,进行布局和绘制。
  • GPU渲染:Render Server将绘制效果提交给GPU进行渲染。
  • 显示内容:GPU完成渲染后,将效果传输到显示控制器,终极显示在屏幕上。
优化建议

为了淘汰卡顿和提升用户体验,开发者可以接纳以下步调:


  • 异步处理:将耗时操作放在后台线程中执行,避免壅闭主线程。
  • 淘汰重绘:优化UI更新逻辑,避免不必要的重绘和布局计算。
  • 简化渲染:使用符合的图形API和资源管理计谋,淘汰GPU的渲染负担。
  • 性能监测:使用工具监测各个阶段的性能,找出瓶颈并进行针对性优化。
通过对每个阶段的优化,可以有效提升应用的流畅度和用户体验,确保在60Hz的刷新率下,内容可以或许及时更新,避免卡顿现象。
简而言之

一般的iPhone设备屏幕刷新率为60Hz,也就是每秒钟刷新60次,而每次刷新的时间,屏幕会去读取Render server提交的内容,终极呈现在用户眼前(每次读取对应上图On the display的一格)。既然屏幕读取的如此频仍,自然Render server也需要不断地更新,一旦某一次Render server没有按时(1/60秒内)更新,那么屏幕读取到的就是上一次展示的内容。App负责处理业务相干的逻辑并提交给Render server:好比展示什么图片什么文字,怎么展示等等。Render server处理完了之后再提交给GPU进行渲染。屏幕硬件我们无法控制先抛开不谈,App阶段和Render server任何一个阶段的处理超时,都会导致这一批内容赶不上下一趟上屏车,从而造成卡顿。
CoreAnimation的渲染流程可以用下图来概括:


图的来源 Advanced Graphics and Animations for iOS Apps
我们看到在应用步伐(Application)和渲染服务器(Render Server)中都有 Core Animation ,但是渲染工作并不是在应用步伐里(尽管它有 Core Animation)完成的。它只是将视图层级(view hierarchy)打包(encode)提交给渲染服务器(一个单独的进程,也有 Core Animation), 视图层级才会被渲染。(“The view hierarchy is then rendered with Core Animation with OpenGL or metal, that’s the GPU.”) 大抵流程如下:
Handle Events: 它代表 touch, 即统统要更新视图层级的事变;
Commit Transaction: 编码打包视图层级,发送给渲染服务器;
Decode: 渲染服务器第一件事就是解码这些视图层级;
Draw Calls: 渲染服务器必须等待下一次重新同步,以便等待缓冲区从 它们实现渲染的显示器 返回,然后终极开始为 GPU 绘制,这里就是 OpenGL or metal 。
Render: 一旦视图资源可用, GPU 就开始它的渲染工作,希望在下个重新同步完成,因为要交换缓冲区给用户。
Display: 显示给用户看。
在上述环境下,这些不同的步骤统共超过三帧。在末了一个步骤 display 后,是可以平行操作的,在 Draw call 的时间可以处理下一个 handler event 和 Commit Transaction 。如下图所示

Core Animation Pipeline的超时问题是导致用户感知到卡顿的重要因素。为了更好地理解这个过程,我们可以将其分为两个主要阶段:App阶段和Render阶段。以下是对这两个阶段的详细分析,以及大概导致超时的原因和优化建议。

1. App阶段

在App阶段,主要涉及到数据的预备和处理。这个阶段的超时通常是由于以下几个原因:


  • 数据加载

    • IO操作:从硬盘或网络加载数据(如图片、视频等)是一个耗时的过程。如果数据还在硬盘上,读取速率大概会受到磁盘速率的影响;如果数据在云端,网络延迟和带宽限制也会影响加载时间。
    • 优化建议:使用异步加载和缓存机制(如NSCache、NSURLCache等)来淘汰IO操作的影响。可以考虑使用占位图或低分辨率图像,等高分辨率图像加载完成后再替换。

  • 数据处理

    • 复杂的计算:在预备数据时,如果需要进行复杂的计算(如图像处理、数据解析等),大概会壅闭主线程。
    • 优化建议:将耗时的计算任务放在后台线程中执行,使用GCD或NSOperationQueue来管理并发任务。

  • UI更新

    • 频仍的UI更新:如果在短时间内频仍更新UI,大概会导致主线程负担过重。
    • 优化建议:归并多次UI更新,尽量淘汰重绘和布局的次数。

2. Render阶段

在Render阶段,主要涉及到将预备好的数据提交给Render Server和GPU进行渲染。这个阶段的超时大概由以下原因引起:


  • 复杂的渲染任务

    • 高复杂度的图形:如果要渲染的图形(如纹理、光影效果等)过于复杂,GPU大概无法在1/60秒内完成渲染。
    • 优化建议:简化图形的复杂度,使用符合的纹理压缩格式,淘汰多边形数量,避免过多的实时光照和阴影效果。

  • GPU资源竞争

    • 多个渲染任务:如果同时有多个渲染任务哀求GPU资源,大概导致渲染延迟。
    • 优化建议:合理安排渲染任务的优先级,避免在同一帧内执行过多的渲染操作。

  • 提交和同步

    • 提交延迟:如果在提交渲染命令时发生延迟,大概会导致渲染帧的丢失。
    • 优化建议:使用符合的渲染队列和同步机制,确保渲染命令可以或许及时提交。

总结

为了淘汰用户感知到的卡顿,开发者需要在App阶段和Render阶段都进行优化。以下是一些综合性的优化建议:

  • 异步处理:确保所有耗时操作(如网络哀求、IO操作、复杂计算)都在后台线程中执行,避免壅闭主线程。
  • 数据预加载:在用户需要数据之前,提前加载和缓存数据,淘汰实时加载的延迟。
  • 简化渲染:优化图形的复杂度,使用符合的纹理和效果,确保GPU可以或许在规定时间内完成渲染。
  • 性能监测:使用工具(如Instruments、Xcode的性能分析工具)监测应用的性能,找出瓶颈并进行针对性优化。
通过以上步调,可以有效提升应用的流畅度,淘汰卡顿现象,从而改善用户体验。
App阶段的耗时任务

常见问题

你提到的主线程卡顿问题确实是iOS开发中一个非常重要的主题。主线程负责处理UI更新和用户交互,因此任何壅闭主线程的操作都会直接影响用户体验。以下是一些常见的导致主线程卡顿的原因,以及相应的解决方案,特别是针对UI相干的更新逻辑。
常见导致主线程卡顿的原因


  • 同步IO操作

    • 读取文件或数据库时,如果使用同步方法,会导致主线程等待IO操作完成,从而造成卡顿。

  • 同步网络哀求

    • 使用同步网络哀求(如URLSession的dataTask)会壅闭主线程,直到哀求完成。

  • 复杂计算

    • 在主线程中执行复杂的计算(如图像处理、数据解析等)会占用大量CPU资源,导致UI无法及时更新。

  • 锁住主线程

    • 使用不妥的锁(如NSLock、dispatch_semaphore等)大概导致主线程被锁住,影响UI相应。

  • 频仍的UI更新

    • 在短时间内频仍调用UI更新方法(如setNeedsDisplay、layoutIfNeeded等)会增长主线程的负担。

解决方案

为了避免主线程卡顿,开发者可以接纳以下步调:

  • 异步处理IO和网络哀求

    • 使用异步方法进行文件读取和网络哀求。比方,使用URLSession的异步dataTask方法,确保网络哀求在后台线程中执行。
    • 对于文件读取,可以使用DispatchQueue.global().async来在后台线程中读取文件。
    1. DispatchQueue.global().async {
    2.     // 执行IO操作
    3.     let data = try? Data(contentsOf: fileURL)
    4.     DispatchQueue.main.async {
    5.         // 更新UI
    6.     }
    7. }
    复制代码

  • 将复杂计算移至子线程

    • 将复杂的计算任务放在后台线程中执行,使用GCD或NSOperationQueue来管理并发任务。
    1. DispatchQueue.global().async {
    2.     // 执行复杂计算
    3.     let result = performComplexCalculation()
    4.     DispatchQueue.main.async {
    5.         // 更新UI
    6.     }
    7. }
    复制代码

  • 使用符合的锁机制

    • 避免在主线程中使用锁,尽量使用无锁编程或使用DispatchQueue的串行队列来处理共享资源。

  • 淘汰频仍的UI更新

    • 归并多次UI更新,尽量淘汰重绘和布局的次数。可以使用CATransaction来批量处理动画和UI更新。
    1. CATransaction.begin()
    2. CATransaction.setDisableActions(true)
    3. // 执行多个UI更新
    4. CATransaction.commit()
    复制代码

  • 使用异步绘制

    • 对于复杂的图形绘制,可以考虑使用Core Graphics或Core Animation的异步绘制功能,避免在主线程中进行复杂的绘制操作。

  • 性能监测和优化

    • 使用Xcode的 Instruments 工具监测应用的性能,找出瓶颈并进行针对性优化。特别关注“Time Profiler”和“Main Thread Checker”工具,资助辨认主线程的壅闭原因。

总结

通过将耗时的操作移至子线程、优化UI更新逻辑以及合理使用锁机制,开发者可以有效淘汰主线程的负担,从而提升应用的流畅度和用户体验。虽然解决95%的UI卡顿问题大概已经充足,但在现实开发中,持续关注性能和用户体验仍然黑白常重要的。
UI对象的创建与烧毁

UI对象的创建与烧毁确实是iOS开发中一个重要的性能考量,尤其是在涉及到列表滚动等高频率的UI更新场景时。以下是对这个问题的深入分析,以及一些优化建议。
UI对象创建与烧毁的开销

在iOS中,创建和烧毁UI对象(如UIView、UIButton等)会涉及到肯定的CPU开销,尤其是在以下环境下:

  • 快速滚动的列表:在UITableView或UICollectionView中,快速滚动时会频仍创建和烧毁单元格(cell),这会导致性能下降。
  • 复杂的视图条理:如果视图条理过于复杂,创建和烧毁这些视图对象的开销会更大。
优化方向

为了淘汰UI对象的创建与烧毁带来的性能开销,可以考虑以下几个方向:
1. 使用复用机制



  • UITableView和UICollectionView的复用:Apple提供的这两个控件内置了复用机制,允许开发者重用已经创建的单元格,避免频仍创建和烧毁对象。确保在cellForRowAt或cellForItemAt方法中正确使用dequeueReusableCell(withIdentifier:for方法。
2. 使用轻量级对象



  • 使用CALayer替代UIView:在不需要相应用户交互的环境下,可以使用CALayer来替代UIView。CALayer比UIView更轻量,且在渲染时开销更小。
3. 对象池



  • 实现对象池:对于频仍创建和烧毁的对象,可以实现一个对象池来管理这些对象的复用。对象池可以预先创建肯定命量的对象,并在需要时从池中获取,而不是每次都创建新的对象。
    1. class ObjectPool<T> {
    2.     private var availableObjects: [T] = []
    3.     private var inUseObjects: [T] = []
    4.     func acquire() -> T {
    5.         if let object = availableObjects.popLast() {
    6.             inUseObjects.append(object)
    7.             return object
    8.         }
    9.         let newObject = T() // 假设T是可初始化的
    10.         inUseObjects.append(newObject)
    11.         return newObject
    12.     }
    13.     func release(_ object: T) {
    14.         if let index = inUseObjects.firstIndex(where: { $0 === object }) {
    15.             inUseObjects.remove(at: index)
    16.             availableObjects.append(object)
    17.         }
    18.     }
    19. }
    复制代码
4. 主线程优化



  • 在主线程不繁忙时进行操作:虽然UI对象的创建和烧毁必须在主线程中进行,但可以通过将这些操作安排在主线程不繁忙的时段来优化。比方,可以使用DispatchQueue.main.asyncAfter来延迟执行某些UI更新操作,确保在主线程空闲时进行。
    1. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    2.     // 执行UI对象的创建或销毁
    3. }
    复制代码
5. 淘汰视图条理



  • 简化视图条理:尽量淘汰视图的嵌套层级,使用更简朴的视图布局。复杂的视图条理会增长创建和烧毁对象的开销。
6. 使用合成视图



  • 合成视图:在某些环境下,可以将多个视图合成一个视图,淘汰视图的数量。比方,使用UIImageView来显示图像,而不是使用多个UIView来组合显示。
总结

通过使用复用机制、轻量级对象、对象池、主线程优化和简化视图条理等方法,可以有效淘汰UI对象的创建与烧毁带来的性能开销。这些优化步调不但能提升应用的流畅度,还能改善用户体验。在现实开发中,持续关注性能并进行针对性优化黑白常重要的。
UI对象的操作和修改

UI对象的操作和修改是iOS开发中需要特别关注的性能问题。对视图层级的频仍操作会导致系统进行遍历和重绘,从而影相应用的性能。以下是一些关于如何优化这些操作的建议:
视图层级操作的开销


  • 遍历视图树

    • 调用addSubview、removeFromSuperview、bringSubviewToFront等方法时,UIKit会遍历视图树以更新视图的层级关系。这种遍历操作在视图层级较深或视图数量较多时会产生显著的性能开销。

  • 布局和重绘

    • 改变UIView的frame或bounds属性会触发布局过程,UIKit会遍历视图树以重新计算每个视图的位置和巨细。
    • 改变视图的透明度(alpha)大概会导致重绘,尤其是在视图层级较复杂时,重绘的开销会更大。

优化建议

为了淘汰这些操作带来的性能开销,可以考虑以下优化计谋:
1. 淘汰视图层级



  • 简化视图层级:尽量淘汰视图的嵌套层级,使用更简朴的视图布局。每增长一层视图,都会增长遍历的复杂度。
  • 归并视图:如果多个视图可以归并为一个视图,尽量归并。比方,使用一个UIImageView来显示图像,而不是使用多个UIView来组合显示。
2. 预先确定视图层级



  • 在创建时确定层级:在创建视图时,尽量确定好视图的层级关系,避免在后续操作中频仍调用bringSubviewToFront等方法。
  • 使用insertSubview(_:aboveSubview或insertSubview(_:belowSubview:如果需要在特定位置插入视图,使用这些方法可以避免不必要的遍历。
3. 批量更新视图



  • 批量更新:如果需要对多个视图进行操作,尽量将这些操作归并在一起,淘汰多次遍历。比方,可以在一个方法中添加多个子视图,而不是逐个添加。
4. 使用setNeedsLayout和layoutIfNeeded



  • 延迟布局:在需要更新多个视图的布局时,可以先调用setNeedsLayout,然后在得当的时机(如在主线程空闲时)调用layoutIfNeeded,以淘汰不必要的布局计算。
5. 使用CATransaction进行动画



  • 使用CATransaction:在进行视图的动画时,可以使用CATransaction来批量处理视图的更新,避免多次重绘。
    1. CATransaction.begin()
    2. CATransaction.setAnimationDuration(0.3)
    3. // 执行多个视图的更新
    4. view.alpha = 0.5
    5. view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    6. CATransaction.commit()
    复制代码
6. 避免不必要的通知和回调



  • 淘汰通知和回调:在进行视图操作时,尽量避免触发不必要的通知和回调。如果某些操作不需要更新视图的状态,可以考虑使用标志位来控制。
总结

通过淘汰视图层级、预先确定层级、批量更新视图、延迟布局、使用CATransaction以及避免不必要的通知和回调等方法,可以有效降低UI对象操作和修改带来的性能开销。这些优化步调不但能提升应用的流畅度,还能改善用户体验。在现实开发中,持续关注性能并进行针对性优化黑白常重要的。
UI布局计算

UI布局计算确实是iOS开发中的一个重要性能考量,尤其是在使用Auto Layout时。Auto Layout的灵活性和强大功能使得它成为现代iOS开发的尺度,但它也带来了性能开销。以下是一些关于如何优化UI布局计算的建议:
Auto Layout的性能开销


  • 布局计算

    • Auto Layout在每次布局时都会进行束缚解析和布局计算,这大概会导致性能下降,尤其是在视图层级复杂或束缚数量较多的环境下。

  • 频仍的布局更新

    • 在动态更新UI时,频仍调用setNeedsLayout和layoutIfNeeded会导致多次布局计算,增长CPU负担。

优化建议

为了淘汰UI布局计算的开销,可以考虑以下优化计谋:
1. 淘汰布局计算的频率



  • 批量更新:在需要更新多个视图的布局时,尽量将这些操作归并在一起,淘汰多次调用setNeedsLayout和layoutIfNeeded。可以在所有更新完成后再进行一次布局。
  • 使用UIView的performBatchUpdates:在UITableView或UICollectionView中,使用performBatchUpdates方法可以在一次更新中进行多个插入、删除和移动操作,从而淘汰布局计算的次数。
2. 预计算布局



  • 提前计算布局:在对象初始化时,可以将一些布局计算放到子线程中进行,提前计算好布局所需的数值,然后在主线程中一次性设置好视图的frame或束缚。这可以淘汰在主线程中进行的重复布局计算。
    1. DispatchQueue.global(qos: .userInitiated).async {
    2.     // 进行复杂的布局计算
    3.     let calculatedFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
    4.    
    5.     DispatchQueue.main.async {
    6.         // 在主线程中设置计算好的frame
    7.         myView.frame = calculatedFrame
    8.     }
    9. }
    复制代码
3. 使用固定尺寸和束缚



  • 使用固定尺寸:如果大概,尽量使用固定的宽度和高度,避免使用动态计算的束缚。固定尺寸的视图在布局时开销更小。
  • 简化束缚:尽量淘汰束缚的数量,避免复杂的束缚关系。使用简朴的束缚可以提高布局性能。
4. 使用layoutSubviews优化



  • 重写layoutSubviews:如果你重写了layoutSubviews,确保只在必要时调用super.layoutSubviews(),而且避免在该方法中进行复杂的计算。可以使用标志位来控制何时需要更新布局。
5. 使用UIView的layoutIfNeeded和setNeedsLayout



  • 合理使用layoutIfNeeded和setNeedsLayout:在需要更新布局时,使用setNeedsLayout标志视图需要重新布局,而不是立即调用layoutIfNeeded。这样可以将布局计算推迟到下一个运行循环中,淘汰不必要的计算。
6. 使用UIView的layoutMargins和preservesSuperviewLayoutMargins



  • 使用布局边距:利用layoutMargins和preservesSuperviewLayoutMargins可以简化布局计算,淘汰手动设置边距的复杂性。
总结

通过淘汰布局计算的频率、预计算布局、使用固定尺寸和束缚、优化layoutSubviews、合理使用layoutIfNeeded和setNeedsLayout以及利用布局边距等方法,可以有效降低UI布局计算的性能开销。这些优化步调不但能提升应用的流畅度,还能改善用户体验。在现实开发中,持续关注性能并进行针对性优化黑白常重要的。
文字计算和排版

文字计算和排版确实是iOS开发中一个重要的性能考量,尤其是在处理大量文本时。UIKit中的一些文本计算方法(如sizeToFit和sizeThatFits)通常在主线程中执行,大概会导致性能瓶颈。以下是一些优化文字计算和排版的建议:
文字计算的性能开销


  • 主线程壅闭

    • UIKit中的文本计算方法通常在主线程中执行,大概会导致UI卡顿,尤其是在处理大量文本或复杂排版时。

  • 复杂的排版需求

    • 不同字体的行高、间距、宽度等计算涉及多个因素,大概会导致性能开销增长。

优化建议

为了提高文字计算的性能,可以考虑以下计谋:
1. 使用boundingRectWithSize



  • 在子线程中计算文本尺寸:使用boundingRect(withptions:attributes:context方法可以在子线程中计算文本的尺寸,从而避免在主线程中进行耗时的计算。
    1. DispatchQueue.global(qos: .userInitiated).async {
    2.     let text = "需要计算的文本"
    3.     let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 16)]
    4.     let maxSize = CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude)
    5.    
    6.     let boundingRect = text.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil)
    7.    
    8.     DispatchQueue.main.async {
    9.         // 在主线程中使用计算结果
    10.         myLabel.frame.size = boundingRect.size
    11.     }
    12. }
    复制代码
2. 使用Core Text进行排版



  • Core Text的优势:对于复杂的文本排版需求,Core Text提供了更高效的文本处理和绘制本领。它允许更细粒度的控制,包括字形、行间距、字间距等。
  • 使用Core Text进行文本绘制:如果你的应用需要处理大量文本或复杂的排版,可以考虑使用Core Text来进行文本的排版和绘制。Core Text可以在子线程中进行计算,而且性能更优。
    1. import CoreText
    2. func drawTextUsingCoreText(text: String, in context: CGContext) {
    3.     let attributedString = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 16)])
    4.     let line = CTLineCreateWithAttributedString(attributedString)
    5.     context.textPosition = CGPoint(x: 0, y: 0)
    6.     CTLineDraw(line, context)
    7. }
    复制代码
3. 缓存计算效果



  • 缓存文本尺寸:对于重复使用的文本,计算一次后可以将效果缓存起来,避免重复计算。可以使用字典或其他数据布局来存储文本和其对应的尺寸。
    1. var textSizeCache: [String: CGSize] = [:]
    2. func cachedTextSize(for text: String, font: UIFont, maxSize: CGSize) -> CGSize {
    3.     if let cachedSize = textSizeCache[text] {
    4.         return cachedSize
    5.     } else {
    6.         let attributes: [NSAttributedString.Key: Any] = [.font: font]
    7.         let boundingRect = text.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil)
    8.         textSizeCache[text] = boundingRect.size
    9.         return boundingRect.size
    10.     }
    11. }
    复制代码
4. 淘汰不必要的重绘



  • 避免频仍更新:在需要更新文本时,尽量淘汰不必要的重绘。可以使用标志位来控制何时需要更新UI,避免在每次文本变化时都进行布局和绘制。
5. 使用NSAttributedString



  • 使用富文本:如果需要处理不同样式的文本,可以使用NSAttributedString来管理文本的样式和属性。这样可以在一次计算中处理多个样式,淘汰计算次数。
总结

通过使用boundingRectWithSize在子线程中计算文本尺寸、利用Core Text进行复杂排版、缓存计算效果、淘汰不必要的重绘以及使用NSAttributedString来管理文本样式,可以有效提高文字计算的性能。这些优化步调不但能提升应用的流畅度,还能改善用户体验。在现实开发中,持续关注性能并进行针对性优化黑白常重要的。
Render阶段的耗时任务

图片的解码和绘制
iOS平台上,一张图片从硬盘读取到内存中的UIImage对象时,现实上内存中的还是Data Buffer,而终极屏幕显示现实需要的是解码后的Image Buffer天生的Frame Buffer(或者叫bitmap位图)。即使这个UIImage对象被设置到了UIImageView上,在这个UIImageView被提交到Render server之前,也是不会自动去解码的。系统的解码只会发生在Render server阶段,这实在是一个比力耗时的操作,且由于UIKit的筹划这些都会发生在主线程,在滑动场景下大量的解码任务堆积很容易造成主线程拥塞。另外比力坑的是没有现成的API判断一个图片是否被解码,因此很多开源库都会在使用前自动触发绘制来达到解码的目的,好比SDWebImage、YYImage等。
离屏渲染

大多是环境下,GPU在渲染时会在当前屏幕区域开发一个Frame Buffer,并在这块缓存上进行所需的渲染。但是有些时间,好比我们同时设置了cornerRadius和clipToBounds时,GPU的绘制任务无法在一个Frame内完成,就需要在屏幕外另外开发一个Frame Buffer进行绘制,并在全部完成后将两个Buffer的内容进行合成,这被称作“离屏渲染”。离屏渲染对于性能的损耗非常大,主要在于GPU的上下文切换所需的开销很大,需要清空当前的管线和栅栏。
离屏渲染(Offscreen Rendering)是iOS开发中一个重要的性能考量,尤其是在涉及复杂视图和图形效果时。离屏渲染的主要问题在于它会导致性能损耗,主要是由于GPU上下文切换的开销。以下是关于离屏渲染的详细解释以及如何优化它的建议。
离屏渲染的概念


  • Frame Buffer

    • 在正常环境下,GPU会在当前屏幕区域开发一个Frame Buffer进行渲染,这样可以直接将渲染效果显示在屏幕上。

  • 离屏渲染的触发

    • 当视图需要进行复杂的绘制操作,好比同时设置cornerRadius和clipToBounds时,GPU无法在一个Frame Buffer内完成所有绘制任务。这时,系统会在屏幕外开发一个新的Frame Buffer进行绘制,完成后再将效果合成到主屏幕上。

  • 性能损耗

    • 离屏渲染会导致GPU上下文切换,清空当前的渲染管线和栅栏,这些操作都需要斲丧额外的时间和资源,大概导致帧率下降和UI卡顿。

离屏渲染的常见场景



  • 圆角视图:使用cornerRadius属性时,尤其是与clipToBounds结合使用。
  • 阴影效果:设置阴影时,通常会导致离屏渲染。
  • 复杂的图形效果:如渐变、模糊等效果。
优化离屏渲染的建议

为了淘汰离屏渲染的影响,可以考虑以下优化计谋:
1. 避免不必要的离屏渲染



  • 简化视图条理:尽量淘汰视图的条理布局,避免复杂的嵌套,淘汰需要离屏渲染的环境。
  • 使用mask代替clipToBounds:如果只需要裁剪部门区域,可以考虑使用mask,而不是clipToBounds,以淘汰离屏渲染的需求。
2. 使用CAShapeLayer



  • 使用CAShapeLayer:对于需要圆角或复杂形状的视图,可以使用CAShapeLayer来绘制路径,而不是使用cornerRadius。CAShapeLayer可以在GPU上直接绘制,避免离屏渲染。
    1. let shapeLayer = CAShapeLayer()
    2. shapeLayer.path = UIBezierPath(roundedRect: myView.bounds, cornerRadius: 10).cgPath
    3. myView.layer.mask = shapeLayer
    复制代码
3. 降低阴影的复杂性



  • 使用阴影路径:为阴影设置一个明确的路径,避免系统自动计算阴影路径,这样可以淘汰离屏渲染的开销。
    1. myView.layer.shadowPath = UIBezierPath(rect: myView.bounds).cgPath
    复制代码
4. 使用位图缓存



  • 位图缓存:对于复杂的视图,可以考虑将其渲染到位图中,然后在需要时直接使用这个位图,避免重复的离屏渲染。
    1. UIGraphicsBeginImageContextWithOptions(myView.bounds.size, false, 0)
    2. myView.layer.render(in: UIGraphicsGetCurrentContext()!)
    3. let image = UIGraphicsGetImageFromCurrentImageContext()
    4. UIGraphicsEndImageContext()
    复制代码
5. 监控和分析性能



  • 使用Instruments:使用Xcode的Instruments工具监控应用的性能,特别是GPU的使用环境,找出离屏渲染的瓶颈并进行优化。
  • Profiling:在开发过程中,定期进行性能分析,确保没有不必要的离屏渲染。
总结

离屏渲染虽然在某些环境下是不可避免的,但通过合理的筹划和优化,可以显著淘汰其对性能的影响。避免不必要的离屏渲染、使用CAShapeLayer、降低阴影复杂性、使用位图缓存以及监控性能都是有效的优化计谋。通过这些方法,可以提高应用的流畅度和用户体验。
复杂纹理的混合

iOS的设备上,如果需要渲染的纹理超过了4096x4096,就会超出GPU的处理范围,会同时影响GPU和CPU的性能。此外,如果需要渲染很多层重叠的layer,且这些layer都有不同的颜色和透明度,或是设置了group opacity时,GPU需要对这种环境打包处理同时陪同着额外的开销。我们能做的就是尽量淘汰视图层级的复杂度,以及淘汰不必要的透明度设置。
在iOS开发中,复杂纹理的混合和多层重叠的视图处理确实会对GPU和CPU的性能产生显著影响。以下是关于复杂纹理混合的详细解释以及优化建议。
复杂纹理混合的挑衅


  • 纹理巨细限制

    • iOS设备的GPU通常对纹理的巨细有限制,常见的最大纹理尺寸为4096x4096像素。如果需要渲染的纹理超过这个尺寸,大概会导致性能下降或渲染失败。

  • 多层重叠的视图

    • 当多个视图(layer)重叠时,尤其是当这些视图具有不同的颜色和透明度时,GPU需要进行复杂的混合计算。这种环境下,GPU会对每个像素进行多次计算,增长了渲染的开销。

  • 组透明度(Group Opacity)

    • 如果设置了组透明度,GPU需要在渲染时考虑整个组的透明度,这会导致额外的计算和性能损失。

优化复杂纹理混合的建议

为了提高渲染性能,可以考虑以下优化计谋:
1. 淘汰纹理尺寸



  • 优化纹理巨细:确保使用的纹理尺寸在GPU的处理范围内,尽量将纹理尺寸控制在4096x4096以内。可以通过压缩纹理或使用更小的分辨率来实现。
  • 使用纹理图集:将多个小纹理归并为一个大纹理图集,淘汰纹理切换的次数,从而提高渲染性能。
2. 简化视图层级



  • 淘汰视图层级:尽量淘汰视图的层级布局,避免不必要的嵌套。每增长一层视图,GPU在渲染时需要处理的复杂度就会增长。
  • 归并视图:如果大概,将多个视图归并为一个视图,淘汰重叠的层数。比方,可以使用UIView的drawRect:方法在一个视图中绘制多个元素,而不是使用多个重叠的视图。
3. 淘汰透明度设置



  • 避免不必要的透明度:尽量淘汰视图的透明度设置,尤其是在重叠的视图中。透明度会导致GPU进行额外的混合计算,影响性能。
  • 使用不透明视图:如果视图不需要透明,可以将其设置为不透明,这样可以淘汰GPU的混合计算。
4. 使用合成层(Composition Layers)



  • 使用CALayer:对于需要复杂混合的视图,可以考虑使用CALayer进行合成。CALayer可以在GPU上进行更高效的渲染。
  • 使用CATransformLayer:如果需要进行3D变换,可以使用CATransformLayer,它可以避免不必要的重绘和混合。
5. 监控和分析性能



  • 使用Instruments:使用Xcode的Instruments工具监控应用的性能,特别是GPU的使用环境,找出性能瓶颈并进行优化。
  • Profiling:定期进行性能分析,确保没有不必要的复杂纹理混合和视图重叠。
总结

复杂纹理的混合和多层重叠的视图处剖析对iOS应用的性能产生显著影响。通过淘汰纹理尺寸、简化视图层级、淘汰透明度设置、使用合成层以及监控性能,可以有效提高渲染性能,改善用户体验。在现实开发中,持续关注性能并进行针对性优化黑白常重要的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表