SwiftUI基础篇Instruments和Tools

Instrument和Tools

概述

文章主要分享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"进行树的反转,以便更清晰的查看函数。
相关推荐
MaoJiu6 小时前
Flutter造轮子系列:flutter_permission_kit
flutter·swiftui
iOS阿玮12 小时前
社交的本质是价值交换,请不要浪费别人的时间。
uni-app·app·apple
大熊猫侯佩12 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩12 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩13 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩13 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
iOS阿玮1 天前
苹果2024透明报告看似更加严格的背后是利好!
uni-app·app·apple
大熊猫侯佩1 天前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩1 天前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩1 天前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple