SwiftUI学习笔记04 – 如何调试SwiftUI?

1,536次阅读
没有评论

SwiftUI学习笔记04 – 如何调试SwiftUI?
SwiftUI底层默认走Core Animation渲染,它也可以直接用Metal,效率非常高。结构简单的App一般不会遇到性能问题,但SwiftUI的写法和刷新机制毕竟跟我们熟悉的UIKit/AppKit不同,过去的写法容易造成没有必要的View Redrawing,导致卡顿或闪动影响用户体验。

这种时候我们就需要调试下SwiftUI代码,看看影响体验的问题是怎么产生的。

我们写的SwiftUI代码是一个遵循了View协议的struct,它不是真正的view实例本身,而只是个“view应该长啥样”的描述。这就给了SwiftUI框架很高的优化自由度,开发者反而不太能干涉底层的渲染逻辑。同时SwiftUI又是闭源的,我们也无法通过阅读源码得知确切的渲染和优化逻辑。但通过苹果提供的调试工具以及对SwiftUI渲染原理的猜测,我们还是能在应用层做一些优化的。

一、影响SwiftUI性能的维度

SwiftUI学习笔记04 – 如何调试SwiftUI?

Xcode Instruments提供了SwiftUI专属模板,除了我们熟悉的Tim Profile维度以外,还提供了View Body, View Properties和Core Animation Commits三个维度。

SwiftUI的刷新机制是以body为单位计算和重绘的,优化时减少View Body的重绘符合直觉。

除了body重绘,Instruments也提供了View Properties维度的报告,可以细化分析哪些Properties发生了变化。

SwiftUI学习笔记04 – 如何调试SwiftUI?

最后是Core Animation Commits。SwiftUI默认用Core Animation来渲染,这种实现非常聪明,每次我们的View发生变化,SwiftUI都会计算关键帧然后作为CATransactoin提交,开发者实现UI元素的过渡动画就像呼吸一样简单。但是当CA Commit过于频繁的时候,也容易产生掉帧的问题。

二、优化SwiftUI List

我们List优化为例子,看看如何实现SwiftUI的调试和优化。

SwiftUI学习笔记04 – 如何调试SwiftUI?

上面是我的一个SwiftUI Mac练习作,可以选择Mac上的图片进行压缩。可以看到点开”Open”按钮弹出文件选择窗口时,底下任务列表的缩略图会闪一下,说明它们都被刷新了一遍或多遍。

SwiftUI学习笔记04 – 如何调试SwiftUI?

2.1 私有Debug接口: Self._printChanges()

这个界面有两个SwiftUI View组成,CompressionView里有一个List,包含了多个CompressViewCell(上图代码简化过)。

如何得知这些View因为什么而被刷新的呢?最简单的方法可以用Xcode的断点:

SwiftUI学习笔记04 – 如何调试SwiftUI?

但看堆栈不够直观,如果想知道是哪个property的更新导致View刷新了怎么办?有一个private API我们可以用于调试:

SwiftUI学习笔记04 – 如何调试SwiftUI?

Self._printChanges()是一个私有API,所以没有文档,根据这个回答,该函数是Apple engineer在WWDC21的Session回答的。以及据说Xcode有一段Summary(我的Xcode 14.2是看不到这一段了):

Summary
When called within an invocation of body of a view of this type, prints the names of the changed dynamic properties that caused the result of body to need to be refreshed. As well as the physical property names, “@self” is used to mark that the view value itself has changed, and “@identity” to mark that the identity of the view has changed (i.e. that the persistent data associated with the view has been recycled for a new instance of the same type).

在本例子中,我先选择10个图片,所以List里有10个Cell。但是SwiftUI的List Content应该都是lazy-loading,所以我们预期只初始化其中能被看到的4个。当fileImporter展示的时候,CompressionView的viewModel property isPresentingFilePicker从false变为true,所以CompressionView会redraw,但是CompressViewCell的viewModel没有发生任何变化,所以我们预期Cells都不应该被redraw

现在开启这个API,打印出来的结果如下:

SwiftUI学习笔记04 – 如何调试SwiftUI?

切换fileImporter的时候,有6个cells被刷新,每个cell被刷了两次。第一次是@self changed, 第二次是_viewModel changed。

2.2 在macOS上用LazyVStack实现lazy-loading

根据这里这里的讨论,初步推断虽然List Content都应该lazy-loading,但至少在macOS上还没有完美实现。用Ventura 13.2.1 + Xcode 14.2 我的测试结果是多渲染了2个,在旧的系统或SDK上可能会初始化全部cells。所以如果为了获得明确的lazy loading,我们可以使用ScrollView+LazyVStack来替代List

SwiftUI学习笔记04 – 如何调试SwiftUI?

实测使用LazyVStack只会渲染4个Cells。

2.3 给View Model增加Identifiable, Equatable

SwiftUI内部做了不少事情,在redraw之前会判断body是否相同以减少重绘次数。相同与否的判断跟View所绑定的@State, @ObservedObject等动态属性有关。

如果是POD views (POD = plain data, see Swift’s _isPOD() function.),SwiftUI会直接判断view的每个字段,如果不是POD views就优先取它的==方法,没有再fall back回去。The SwiftUI Lab的这篇文章对此有深入探讨。不过令我感兴趣的是Core Animation的设计者John Harper的现身说法

SwiftUI学习笔记04 – 如何调试SwiftUI?

他非常低调,Google到的信息不多,只有AppleInsiderDaring Fireball 2014年对他离开Apple加入Facebook的报道。看来他后来又回到了Apple并参加SwiftUI项目,WWDC19他在这个Session出现。如此说来,使用Core Animation作为SwiftUI的默认渲染就非常合理了。

回到我们的优化来,因为我的App采用MVVM架构,以前写RxSwift的时候就习惯从ViewController分一个ViewModel属性出来,现在把它作为View的一个@ObservedObject非常自然。但也因此让这个view struct不再是一个POD view,所以我们需要给ViewModel实现Identifiable, Equatable。

SwiftUI学习笔记04 – 如何调试SwiftUI?

如此一来,SwiftUI在决定哪些sub-views需要被redraw的时候就可以通过我们自定义的比较函数来判断,这里我的应用场景是只要id相同它就不需要改变,但诸位读者要视具体情况来实现自己的比较函数。

2.4 优化后的效果

SwiftUI学习笔记04 – 如何调试SwiftUI?

SwiftUI学习笔记04 – 如何调试SwiftUI?

只有CompressionView自己因为isPresentingFilePicker变化而刷新,所有的Cell都不会二次重绘了,Nice!👏🥳

三、使用Instruments

上述例子只是一个非常简单的案例,如果App变得复杂了就需要Instruments相助了。

SwiftUI学习笔记04 – 如何调试SwiftUI?

测试时已禁止进度条刷新

上图选取的时间段是一次fileImporter展开,引发了12次CompressViewCell的body重绘,符合Self._printChanges()的日志结果。

SwiftUI学习笔记04 – 如何调试SwiftUI?

优化后同样是一次fileImporter展开,不再有CompressViewCell重绘。

SwiftUI学习笔记04 – 如何调试SwiftUI?

View Properties展示了所有Properties的变化记录,拖动顶部的小三角形可以展示所有Properties的变化过程,有点厉害。不过目前它只能显示Propert Type,比如State,没有变量名,如果你有多个同类型的Properties就有点难找到对应的变量是哪个。希望今年的WWDC可以带来更多更强大的Debug功能。

SwiftUI学习笔记04 – 如何调试SwiftUI?

Core Animation Commits可以告诉我们哪些地方可能有渲染上的卡顿,Hacking with Swift这篇文章有不错的介绍。

Time Profile就无需多言了,平时用来查各种卡顿的必备工具,不再赘述。

四、下一步?

既然@ObservedObject会导致不好管理的view redrawing,那我们有没有更好的解决方案呢?

SwiftUI发布以来,开发者们有过不少讨论。Alexey Naumov在Why I quit using the ObservableObject中介绍他用Combine包装的AppState取代@ObservedObject,OneV Cat也分享过TCA – SwiftUI 的救星?

目前我还在使用@ObservedObject作为View Model的方案,还没尝试自己封装基于Combine的View Model,未来可以尝试一下。虽然对于使用Redux全局单一Store实现Single Source of Truth的方法我还持怀疑态度,一旦App大了这个State同样是要爆炸的。

SwiftUI学习笔记04 – 如何调试SwiftUI?

@Livid 开发的Planet就是采用这种方式实现的,有兴趣的读者可以看一下,这是GitHub Repo。哪天我也找个Side Project尝试一下看看。

The post SwiftUI学习笔记04 – 如何调试SwiftUI? first appeared on 枫言枫语

Read More 

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)

文心AIGC

2023 年 4 月
 12
3456789
10111213141516
17181920212223
24252627282930
文心AIGC
文心AIGC
人工智能ChatGPT,AIGC指利用人工智能技术来生成内容,其中包括文字、语音、代码、图像、视频、机器人动作等等。被认为是继PGC、UGC之后的新型内容创作方式。AIGC作为元宇宙的新方向,近几年迭代速度呈现指数级爆发,谷歌、Meta、百度等平台型巨头持续布局
文章搜索
热门文章
潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026 Jay 2025-12-22 09...
面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25 鹭羽 2025-12-13 22:37...
商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1 十三 2025-12-15 14:13:14 ...
跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026 一水 2025-1...
10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了 一水 2025-12-12 13:56:19 ...
最新评论
ufabet ufabet มีเกมให้เลือกเล่นมากมาย: เกมเดิมพันหลากหลาย ครบทุกค่ายดัง
tornado crypto mixer tornado crypto mixer Discover the power of privacy with TornadoCash! Learn how this decentralized mixer ensures your transactions remain confidential.
ดูบอลสด ดูบอลสด Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
Obrazy Sztuka Nowoczesna Obrazy Sztuka Nowoczesna Thank you for this wonderful contribution to the topic. Your ability to explain complex ideas simply is admirable.
ufabet ufabet Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
ufabet ufabet You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
ufabet ufabet Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
热评文章
跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026 一水 2025-1...
10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了 一水 2025-12-12 13:56:19 ...
IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别

IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别

IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别 量子位的朋友们 2025-1...