Instrument和Tools
- @warn_unqualified_access
- [对PreView 设置不同的调试尺寸](#对PreView 设置不同的调试尺寸 "#View2")
- 如何查找那些数据更改导致SwiftUI视图更新
- 如何使用Instrument分析SwiftUI代码并识别缓慢布局
概述
文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,Instruments和Tools主要表达工具使用,主要是对过程进行探索,可以移步Github下载code -> github案例链接
1、如何使SwiftUI修饰符与@warn_unqualified_access一起使用更安全。
每个SwiftUI都会在某些时候犯同样的错误,而且会不止一次犯这种错误:将.someModifier()写成someModifier(),导致程序出现EXC_BAD_ACCESS。解决方案是用Swift的@warn_unqualified_access
属性,这意味着如果不使用变量名或类似名称,则无法访问属性和方法。
创建titleStyle()
//扩展一个titleStyle()方法,向视图添加一些修饰符以使其匹配子弟你主题,那么将@warn_unqualified_access在方法签名之前使用。
Swift
extension View {
@warn_unqualified_access
func titleStyle() -> some View {
self
.font(.largeTitle)
.fontWeight(.black)
.padding()
.background(.blue)
.foregroundStyle(.white)
.cornerRadius(10)
}
}
具体使用情况
Swift
struct FFToolingSafe: View {
var body: some View {
//当使用它是,正常情况下
Text("Meta BBLv")
.titleStyle()
//异常情况,删除掉了.
Text("Meta BBLv Error")
titleStyle()
}
}
这产生了区别,首先这是一个不合格的访问,我没有声明titleStyle()在哪里调用。所以SwiftUI假设我实际上FFToolingSafe在调用他。这意味着实际上正在调用self.padding(),因此会出现内存激增,导致出现一个无限递归视图并最终崩溃。
SwiftUI的规则是不使用@warn_unqualified_access作用在自己的修饰符上,但是我们可以为自己构建的自定义修饰符添加它,如果出现了使用错误的情况下(比如删除了".")
,这个时候SwiftUI会为你添出提示:
- Use of 'titleStyle' treated as a reference to instance method in protocol 'View'
- Use 'self.' to silence this warning
Xcode贴图
2、对PreView 设置不同的调试尺寸
构建程序时,要对所有的支持机型进行匹配,避免出现不同屏幕间的size差异。比如在大一些的屏幕(iPhone 14 Pro Max)上面布局是OK的,下放到小一些的屏幕(iPhone 14 Pro)出现问题。在SwiftUI中,所有组件本身都适应Dynamic Type Size,这是非常完美的,但是如果你想在Preview中也想要预览不同size又该如何操作呢?
Xcode 14
在Xcode 14的版本中,关于preview的设置还可以添加.environment(\.sizeCategory, .extraSmall)
来设定多预览。在Xcode 14以前的版本不知道具体情况如何,未研究SwiftUI。
Swift
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.environment(\.sizeCategory, .extraSmall)
}
}
}
Xcode Version 15.0 beta 8 (15A5229m)
通过测试,无法在#Preview
下声明任何属性,和使用上述的方法。
Swift
#Preview {
FFToolingDiffenertSize()
}
使用方式也更加简便,更改为可视化操作
Xcode操作贴图
3、如何查找那些数据更改导致SwiftUI视图更新
SwiftUI提供了一个特殊的、仅供调试的方法,可以使用它来识别导致视图重新加载自身的更改。该方法专门用于调试,不应在body实际程序中声明。但是在check视图正在重复调用属性还不确定原因时使用。 该方法是Self._printChanges()
,并且应在属性内部调用。这意味着你可能暂时需要添加显示返回来发挥常规的代码视图。
Swift
class EvilStateObject: ObservableObject {
var timer: Timer?
init() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
if Int.random(in: 1...5) == 1 {
self.objectWillChange.send()
}
})
}
}
extension ShapeStyle where Self == Color {
static var random: Color {
Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
}
struct FFToolingSwiftUIChange: View {
@StateObject private var eviObject = EvilStateObject()
var body: some View {
let _ = Self._printChanges()
//通过设置背景色可以直观的观察body视图属性何时被调用。现象是如果Body被多次调用,那么视图背景色也会改变多次
Text("META BBLV")
.background(.random)
}
}
监视视图的刷新
4、如何使用Instrument分析SwiftUI代码并识别缓慢布局
Xcode的Instruments
工具附带了一组出色的SwiftUI分析功能,使我们能够确定视图重回的频率、计算视图主体缓慢的次数,甚至状态在实时变化的情况。
创建测试条件
创建一个每0.01秒触发一次的计时器,并有一个显示随机值UUID的视图和一个button
Swift
class FrequentUdater: ObservableObject {
var timer: Timer?
init() {
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { _ in
self.objectWillChange.send()
})
}
}
测试ContentView
代码非常简单,有一个每0.01s就触发的Timer,动态切换UUID,同时创建了一个Button,点击时tapCount+1
Swift
struct FFToolingInstruments: View {
@StateObject private var updater = FrequentUdater()
@State private var tapCount = 0
var body: some View {
VStack {
Text("\(UUID().uuidString)")
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
}
测试过程
默认情况下,SwiftUI工具会告诉我们很多数据:
- 在此期间创建了多少视图以及创建它们所用的时间(View Body)
- 视图的属性是什么以及它们如何随时间变化(View Properties)
- 发生了多少次核心动画commit( Core Animation Commits)
- 每个函数调用具体花费了多少时间(Time Profiler)
这些工具都可以帮助诊断和解决SwiftUI应用程序中的性能问题。
View Body
当选择针对View Body进行探索时,Instroments将结果分解为SwiftUI和我自己的项目,前者是原始类型(如文本按钮等),后者是我的自定义类型视图。在我的例子中,"FFToolingInstruments"是我的自定义视图
在此区域,重要的是Count数据以及平均持续时间(每个事物被创建了多少次以及花费的时长)。我这里做的是一个0.01的Timer,我暂时将它当作一个压力测试,这样会看到非常高的count,因为布局非常简单,在上面结果看来。平均创建时间是微秒
级别。
View Properties(跟踪状态变化)
选择View Properties,这里显示了所有视图的所有属性,包括当前的值和所有以前的值。因为我为程序添加了一个Button,就是用来调试这里的,每次点击都会使tapCount+1。查找State<Int>
的Property Type。这里有一个小技巧,当你选择停止测试的时候,在时间轴的最后会有一个倒三角图形的时间线标记,拖动这根时间线,就可以看见你在测试过程中的tapCount随着Button点击+1的效果了。
Core Animation Commits(识别慢速绘制)
在SwiftUI中,可以直接使用Metal来提升性能,但大多数时候SwiftUI都使用Core Animation进行渲染。使用Instroments内置的Core Animation分析工具可以对其进行分析。当多个更改一起放入一个事物时,core Animation效果最佳。可以在一个 事物中有效的堆叠一系列的工作,再要求CA继续渲染,那么这个过程称为commit事物。
因此,当Instruments向我表示Core Animation的commit时,这代表着很昂贵的代价,我无法描述,就像Ben老头讲的dirty memory一个道理,这里是一个dirty commit。它真正向我们展示的是SwiftUI由于更新而被迫重新绘制屏幕上的像素的次数。理论上,只有当App的实际状态导致不同的视图层次结构时,才会发生这种情况,因为SwiftUI应该能够将body属性的新输入与旧输出进行比较。
Time Profiler(查找用时较长的函数)
Time Profiler精确的向我显示了代码每个部分花费多少时间。这与Instruments中常规时间分析器的工作原理相同。
- 默认情况下,右侧扩展的详细信息窗口显示最重要的堆栈跟踪,
- 在左侧,可以看到所有的线程,以及可让你深入了解它们调用的函数以及这些函数的公开指示器。
- 可以对"Call Tree"进行过滤,选择"Hide System Libraries"可以隐藏系统库,这是智慧显示我自己编写的代码。
- 还可以对"Call Tree"进行再次过滤,选择"invert Call Tree"进行树的反转,以便更清晰的查看函数。