RyukieDev
  • 关于我-AboutMe
  • 我的作品-MyApps
    • 「梦见」账本
      • 「梦见」账本(Umemi):极致的记账体验
      • 隐私协议:Privacy Policy
      • 服务协议:Terms of use
      • 外观预览:Preview
        • 赛博朋克-Cyberpunk
        • 樱-Sakura
        • 初恋-FirstLove
        • 永生-Eternal
        • 菲尼克斯-Phoenix
        • 报丧女妖-Banshee
        • 九霄-NYXL
        • Dream
        • 猕猴桃-Kiwi
        • 蜂蜜-Hachimitsu
        • DC
    • Elic-扫雷无尽天梯
    • 隐私访问记录
      • 03.如何分析iOS15隐私访问记录
      • PrivacyPolicy
      • FrameWorks
    • 醒词
      • PrivacyPolicy
      • TermsOfUse
    • 一色
      • PrivacyPolicy
    • 醒诗
      • PrivacyPolicy
    • 醒词键盘
      • PrivacyPolicy
    • 中文数字键盘
      • PrivacyPolicy
  • 独立开发
    • 产运
      • 01.没钱推广?这忘了这100美元
      • 02.在个人站点配置app-ads
      • 03.应用图标圆角
      • 04.iOS独立开发者注册公司到App备案上架.md
    • iCloud
      • 01.基于iCloud构建用户体系
      • 02.基于iCloud构建游戏内排行榜
  • Swift
    • 01.纯Swift路由方案探究
    • 02.使用Carthage替代CocoaPods
    • 03.逃逸闭包和非逃逸闭包
    • 04.向下向上取整
    • 05.Copy-on-write
    • 06.OC老项目Swift混编的一些坑
    • 07.OC项目中加入Swift混编
    • 08.Optional实质
    • 09.R-Swift-安全的资源组织方案forSwift
    • 10.Struct与Class
    • 11.Swift5新特性
    • 12.Swift性能分析
    • 13.SwiftPackage使用
    • 14.String与Substring
    • 15.Array,Set,Dictionary
    • 16.For-in跳跃遍历
    • 17.Switch元祖
    • 18.循环的标签控制
    • 19.Protocol与静态派发、动态派发
    • 20.Swift位移枚举
    • 21.Swift轻量级网络封装:SwiftyServiceProtocol(适用于混编或纯Swift项目)
    • 22.open与public
    • 23.Swift项目编译速度优化
    • 24.[译]编写高性能Swift代码-Writing High-Performance Swift Code(2022.8.25版)
    • 25.Swift编译流程
    • 26.Swift方法调度
  • SwiftUI
    • 01.Form
    • 02.Navigation
    • 03.ViewBuilder参数10个限制
    • 04.UIKit混编时Dismiss掉HostController
    • 05.如何在SwiftUI中使用ImagePicker?
    • 06.从some看Swift不透明类型
    • 07.TabView使用
    • 08.openURL
    • 09.Search
    • 10.SwifUI中使用WKWebView
  • DeepLearning
    • 基础知识
      • 01.感知机与神经网络
      • 02.线性可分
    • TensorFlow
      • 01.Anaconda
      • 02.JupyterNotebook
      • 03.安装TensorFlow
  • iOS
    • 底层
      • 01.alloc与字节对齐
      • 02.结构体内存对齐
      • 03.对象本质探究与isa
      • 04.ISA与Class
      • 05.深入探索Class的结构
      • 06.WWDC20-runtime优化
      • 07.深入探究属性
      • 08.isKindOfClass的底层实现
      • 09.slowpath和fastpath
      • 10.Class-cache_t
      • 11.源码解读objc_msgSend
      • 12.类的实现与初始化源码解读
      • 13.动态决议与消息转发
      • 14.iOS应用启动(一):dyld与main函数
      • 15.iOS应用启动(二):环境配置与runtime初始化
      • 16.iOS应用启动(三):镜像文件的读取和加载
      • 17.iOS应用启动(四):分类的加载
      • 18.关联对象源码解析
      • 19.MethodSwizzing方法交换的坑
      • 20.详解KVC
      • 21.KVO几个被忽视的细节
      • 22.KVO底层原理
      • 23.多线程原理与atomic
      • 24.任务与队列的几个面试题
      • 25.dispatch_once单例实现原理
      • 26.栅栏函数
      • 27.信号量
      • 28.锁|性能分析
      • 29.锁|@synchronized
      • 30.锁|递归锁
      • 31.锁|NSConditionLock
      • 32.关于Block你所该知道的一切
    • 内存管理
      • 01.从一个面试题看TaggedPointer
      • 02.Retain&Release
      • 03.SideTable和weak底层实现
      • 04.Timer优化
      • 05.自动释放池与Runloop
      • 06.dealloc
    • 编译器
      • 01.LLVM
    • 杂项
      • 01.堆栈的深度问题
      • 02.使用TTF字体
      • 03.为什么选VIPER
      • 04.项目路由方案
      • 05.隐藏导航栏下面的线
      • 06.源代码到IPA
      • 07.iOS重签名调研
      • 08.load与-initialize
      • 09.NSTimer与GCD
      • 10.NSURLConnection-和-NSURLSession
      • 11.Storyboard中UnwindSegue的使用
      • 12.UI调试-UIDebuggingInformationOverlay
      • 13.UIWebView和WKWebView
      • 14.UIWebView自适应高度
      • 15.weak实现原理
    • Runloop
      • 01.RunLoop
      • 02.autoreleasepool
    • Runtime
      • 01.基本操作
      • 02.实现NSCoding的自动归档和自动解档
      • 03.消息机制
      • 04.重写description打印对象信息
      • 05.MethodSwizzling的问题
    • 优化
      • 01.Apple官方资源瘦身方案ODR(一):初见
      • 02.Apple官方资源瘦身方案ODR(二):践行|换肤系统改造
      • 03.二进制重排实践
      • 04.iOS截屏防护方案
      • 05.提高编译速度
      • 06.图片格式-WebP
      • 07.App启动速度优化
      • 08.IDL自动化埋点
      • 09.渲染原理及优化
      • 10.「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动
      • 11.离屏渲染
      • 12.一键搞定iOS16横竖屏切换
    • 多线程
      • 01.合适的线程数量
      • 02.死锁
      • 03.为什么用dispatch-once实现单例
      • 04.iOS多线程方案
      • 05.iOS多线程技术对比
    • Database
      • 01.数据库主键和外键
      • 02.FMDB-死锁问题
      • 03.FMDB与WCDB
      • 04.SQLite数据库修复
    • 架构
      • 01.组件化
  • 逆向
    • 01.寄存器
    • 03.iOS应用签名原理
    • 04.利用Xcode进行重签名与调试
    • 05.dylib注入
    • 06.MachO文件
    • 07.dyld
    • 08.Hook
    • 09.深入理解动态库与静态库
    • 10.通过符号表找到符号
    • 11.fishhook原理
    • 12.去符号与恢复符号
    • 13.反HOOK防护(一):基于Fishhook
    • 14.反HOOK防护(二):Monkey
    • 15.Inlinehook:Dobby
    • 16.LLDB
    • 17.虚拟内存
    • 18.Chisel工具
    • 19.DS.LLDB工具
    • 20.Cycript工具
    • 21.Cycrupt用法
    • 22.Logos
    • 23.应用砸壳
    • 24.实战人人视频破解
    • 25.解密被加密的数据库文件
  • Flutter
    • 01.初见Flutter
    • 02.Layout
    • 03.状态管理
    • 04.BottomNavigationBar
    • 05.MaterialApp
    • 06.android资源配置
    • 07.Positioned与Container嵌套无法充满容器
    • 08.Cell点击跳转
    • 09.代码规范
    • 10.通过联系人Cell看断言
    • 11.有状态Widget初始化重写&链式调用&排序
    • 12.索引条:手势及clamp函数
    • 13.ListView滑动到指定位置
    • 14.悬浮菜单列表
    • 15.Mock数据小技巧
    • 16.第三方库导入与网络数据异步请求与展示
    • 17.请求数据保留
    • 18.异步编程之Future
    • 19.Future&Microtask
    • 20.Dart异步编程:Isolates和事件循环
    • 21.Widget的生命周期
    • 22.Widget树&Render树&Element树
    • 23.Key
    • 24.调用原生相册
    • 25.iOS原生嵌入FlutterModule
  • 网络
    • 01 网络分层的优点
    • 02 网络理解
    • 03 iOS-网络安全之HTTPS
    • 04 POST和GET
    • 05 SSL-TLS四次握手
  • 直播技术
    • 01 直播技术相关
    • Socket-Little-Endian-Big-Endian
  • 知识点梳理
    • 01 面试算法题记录01
    • 02 面试题记录-C语言
    • 08 一套iOS底层试卷
    • 03 知识点梳理:iOS底层
    • 04 知识点梳理:网络
    • 05 知识点梳理:多线程
    • 06 知识点梳理:计算机基础
    • 07 知识点梳理:算法数据结构
    • 09 知识点梳理:HTML和浏览器
    • 10 知识点梳理:JavaSctipt
  • Framework
    • 01 CodeReading-01-YYModel
    • 02 RYImagePicker-iOS图片视频选择器
    • 03 RYImagesScroller-iOS高度自定义的图片轮播器
    • 04 RYPhotosBrowser
  • Issue
    • 01 使用KVC设置UIAlertAction按钮颜色的Crash
    • 02 iOS-常见崩溃分析
    • 03 UICollectionView的一些问题
  • OpenGL ES
    • 01.顶点着色器与片元着色器
  • 数据结构与算法
    • 剑指Offer-Swift
      • 03.找出数组中重复的数字
      • 04.二维数组中的查找
      • 05.替换空格
      • 06.从尾到头打印链表
      • 07.重建二叉树
      • 12.矩阵中的路径(回溯法)
      • 13.机器人的运动范围
      • 14.I.剪绳子
      • 14.II.剪绳子
      • 15.二进制中1的个数(含一个拓展问题)
      • 16.数值的整数次方
      • 18.删除链表的节点
      • 21.调整数组顺序使奇数位于偶数前面
      • 22.链表中倒数第k个节点
      • 24.反转链表
      • 25.合并两个排序的链表
      • 26.树的子结构
      • 27.二叉树的镜像
      • 28.对称的二叉树
      • 29.顺时针打印矩阵
      • 30.包含min函数的栈(容易被误导的一题)
      • 31.栈的压入、弹出序列
      • 32.I.从上到下打印二叉树
      • 32.II.从上到下打印二叉树II
      • 32.III.从上到下打印二叉树III
      • 32.从上到下花式打印二叉树
      • 33.二叉搜索树的后序遍历序列
      • 34.二叉树中和为某一值的路径
      • 35.复杂链表的复制(无Swift用例)
      • 36.二叉搜索树与双向链表
      • 37.序列化二叉树
      • 39.数组中出现次数超过一半的数字
      • 40.最小的k个数
      • 41.数据流中的中位数
      • 42.连续子数组的最大和
      • 43.1~n整数中1出现的次数
      • 44.数字序列中某一位的数字
      • 45.把数组排成最小的数
      • 46.把数字翻译成字符串
      • 47.礼物的最大价值
      • 48.最长不含重复字符的子字符串
      • 50.第一个只出现一次的字符
      • 52.两个链表的第一个公共节点
      • 53-I.在排序数组中查找数字
      • 53-II.0~n-1中缺失的数字
      • 54.二叉搜索树的第k大节点
      • 55-I.二叉树的深度
      • 55-II.平衡二叉树
      • 56-I.数组中数字出现的次数
      • 56-II.数组中数字出现的次数II
      • 57.和为s的两个数字
      • 58-I.翻转单词顺序
      • 58-II.左旋转字符串
      • 59-I.滑动窗口的最大值
      • 59-II.队列的最大值
      • 60.n个骰子的点数
      • 61.扑克牌中的顺子
      • 62.圆圈中最后剩下的数字
      • 63.股票的最大利润
      • 64.求1+2+…+n
      • 65.不用加减乘除做加法
      • 66.构建乘积数组
      • 67.把字符串转换成整数
      • 68-I.二叉搜索树的最近公共祖先
      • 68-II.二叉树的最近公共祖先
    • 技巧
      • 01.前缀和
      • 02.同余性质
      • 03.快速幂
      • 04.快速排序
      • 05.深度优先&广度优先
      • 06.冒泡排序
      • 07.摩尔投票
      • 08.优先队列
    • 数据结构
Powered by GitBook
On this page
  • 一、 职责与分工
  • 二、 双缓冲机制
  • 三、 图片加载的过程
  • 四、 为什么需要解压缩
  • 五、 界面优化

Was this helpful?

  1. iOS
  2. 优化

09.渲染原理及优化

Previous08.IDL自动化埋点Next10.「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动

Last updated 3 years ago

Was this helpful?

将图形渲染到屏幕是CPU和GPU协同完成的一个过程.

一、 职责与分工

  • CPU

    • 计算视图Frame

    • 图片解码

    • 将需要绘制的纹理图片通过数据总线传递给GPU

  • GPU

    • 纹理混合

    • 顶点变换与计算

    • 像素点的填充计算

    • 渲染到缓冲区

  • 时钟信号

    • 垂直同步信号V-Sync

    • 水平同步信号H-Sync

  • iOS设备双缓冲机制

    • 显示系统通常会引入两个帧缓冲区

二、 双缓冲机制

  • 有前帧缓存、后帧缓存,即GPU会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取

  • 当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)

  • 当你视频控制器已经读完一帧,准备读下一帧的时候

    • GPU会等待显示器的VSync信号发出后,前帧缓存和后帧缓存会瞬间切换

    • 后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存

2.1 卡顿产生的原因

如果在一个VSync时间内CPU或GPU没有将处理完成的数据提交到帧缓冲区,那一帧就会被丢弃

按照60fps刷新率,每隔16ms就会又一次VSync信号

2.2 卡顿优化-CPU

  • 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView

  • 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改

  • 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性

  • Autolayout会比直接设置frame消耗更多的CPU资源

  • 图片的size最好刚好跟UIImageView的size保持一致

  • 控制一下线程的最大并发数量

  • 尽量把耗时的操作放到子线程

2.3 卡顿优化-GPU

  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示

  • 尽量减少视图数量和层次

  • 减少透明的视图(alpha<1),不透明的就设置opaque为YES

  • 尽量避免出现离屏渲染

2.4 卡顿检测

  • YYFPSLabel

    • 基于 DisplayLink

    • 每次++,少了就掉帧了

  • Runloop

    • 在 主线程 的 Runloop 中创建一个 Observer

    • 监听事务

    • 在 子线程 开启计时

    • 两次事务的间隔超过一定的时间就标识有事务比较耗时

  • 微信的 Matrix

    • 也是基于 Runloop 的,更全面

  • 滴滴 Doraemon

    • 主线程发不断发信号

三、 图片加载的过程

3.1 imageNamed

使用后会有缓存,适合重复使用的场景

When searching the asset catalog, this method prefers an asset containing a symbol image over an asset with the same name containing a bitmap image. Because symbol images are supported only in iOS 13 and later, you may include both types of assets in the same asset catalog. The system automatically falls back to the bitmap image on earlier versions of iOS. You cannot use this method to load system symbol images; use the init(systemName:) method instead.

This method checks the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method creates the image from an available asset catalog or loads it from disk. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used.

In iOS 9 and later, this method is thread safe.

3.2 imageWithContentsOfFile

不会创建缓存,适合于低频使用的资源图

This method does not cache the image object.

3.3 具体过程

  1. 将UIImage赋值给UIImageView

  2. 一个隐式的CATransaction捕捉到了UIImageView图层树的变化

  3. 主线程下个RunLoop到来时,CoreAnimation提交了这个Transaction

    • 这个过程可能会对图片进行Copy操作,而受图片是否字节对齐等因素的影响,这个Copy操作可能包含一下部分或全部操作:

    • 分配内存缓冲区,用于管理文件IO和解压缩操作

    • 将图片数据从磁盘读取到内存

    • 将图片数据解码成未压缩的位图格式,这是个非常耗时的CPU操作

    • CPU计算好图片Frame,对图片解压后,交给GPU进行渲染

    • CoreAnimation的CALayer用位图数据渲染UIImageView

  4. 渲染流程

    • GPU获取图片坐标

      • 顶点计算

    • 将图片光栅化

      • 获取图片对应屏幕上的像素点

      • 计算每个像素点最终显示的颜色值

    • 从帧缓存区中渲染到屏幕上

四、 为什么需要解压缩

4.1 位图 JPG PNG

  • 位图就是一个像素数组,包含了每个像素点的信息。

  • JPG和PNG都是压缩的位图

    • JPG有损压缩

    • PNG无损压缩,支持alpha通道

4.2 获取原始图片数据

UIImage *image = [UIImage imageNamed:@"text.png"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));

五、 界面优化

5.1 预排版

提前进行相关计算,例如在 Cell 复用之前就通过数据模型对相关 Layout 信息进行计算。否则每次复用都进行计算就很浪费性能了。

5.2 预解码

系统默认会在主线程对图片进行解压,但该操作是个非常耗时的操作。所以现在就出现了对图片进行异步解压的方案。核心方法是CGBitmapContextCreate。

SDWebImage可以在sd_decompressedImageWithImage处断点调试过程

public struct SwiftyImageLoader {
    public static let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
    
    public static func imageRefContainsAlpha(imageRef: CGImage) -> Bool {
        switch imageRef.alphaInfo {
        case .none, .noneSkipFirst, .noneSkipLast:
            return false
        default:
            return true
        }
    }
}

extension UIImageView {
    func asyncSet(newImage: UIImage?) {
        guard let image = newImage else {
            self.image = nil
            return
        }
        
        DispatchQueue.global().async { [weak self] in
            guard let imageRef = image.cgImage else {
                self?.setImageInMainQueue(image: image)
                return
            }

    //        let bytesPerPixel: Int = 4
            let bitsPerComponent: Int = 8
            
            // device color space
            let colorspaceRef = SwiftyImageLoader.colorSpace
            let hasAlpha = SwiftyImageLoader.imageRefContainsAlpha(imageRef: imageRef)
            
            // iOS display alpha info (BRGA8888/BGRX8888)
            // 位移枚举可以这样写
            let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: hasAlpha
                                                            ? CGImageAlphaInfo.premultipliedFirst.rawValue
                                                            : CGImageAlphaInfo.noneSkipFirst.rawValue)
                .union(.byteOrder32Little)
            
            let width = imageRef.width
            let height = imageRef.height
            
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
            
            guard
                let context: CGContext = CGContext.init(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: 0, space: colorspaceRef, bitmapInfo: bitmapInfo.rawValue)
            else {
                self?.setImageInMainQueue(image: image)
                return
            }
            
            // Draw the image into the context and retrieve the new bitmap image without alpha
    //        CGContextDrawImage
            context.draw(imageRef, in: CGRect(origin: .zero, size: CGSize(width: width, height: height)))
            
            guard
                let imageRefWithoutAlpha = context.makeImage()
            else {
                self?.setImageInMainQueue(image: image)
                return
            }
            let imageWithoutAlpha = UIImage(cgImage: imageRefWithoutAlpha, scale: image.scale, orientation: image.imageOrientation)
            self?.setImageInMainQueue(image: imageWithoutAlpha)
            //  CGContextRelease(context) Swift 会自动去管理了,不用手动去释放了
        }
    }
    
    private func setImageInMainQueue(image: UIImage) {
        DispatchQueue.main.async { [weak self] in
            self?.image = image
        }
    }
}

5.3 按需加载

一般我们卡顿主要是因为列表有大量的图片,我们可以选择在滚动的时候采用展示展位图,滚动停止的时候再展示相关图片的方式来提高性能。

当然这样对于用户的浏览体验来说会有一些影响。

5.4 异步渲染

UIView 和 CALayer 的关系

Graver

Graver 好像是借鉴了 YYAsyncLayer 有争议,后来取消开源了。

将坐标交给

计算

下面是我在我的个人项目 中的账户封面设置中用做的优化:

顶点着色器
片元着色器
梦见账本
美团开源Graver框架:用“雕刻”诠释iOS端UI界面的高效渲染
从 Graver 源码再来看异步渲染
界面优化-界面渲染流程
界面优化-双缓存原理
VSync与丢帧
UIView