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"进行树的反转,以便更清晰的查看函数。
相关推荐
humiaor1 天前
Xcode报错:No exact matches in reference to static method ‘buildExpression‘
swiftui·xcode
humiaor9 天前
Xcode报错:Return from initializer without initializing all stored properties
swiftui·binding
墨痕诉清风9 天前
苹果软件产品使用的 TCP 和 UDP 端口列表(apple、mac)
网络协议·tcp/ip·apple
HyperAI超神经10 天前
Apple Intelligence深夜炸场!苹果发布4颗自研芯片,iPhone/iWatch/AirPods大升级
ios·iphone·apple·apple 发布会·iphone 16·a18芯片
喜好儿aigc15 天前
Apple “Glowtime”活动:iPhone 16、Apple Intelligence亮相
ios·aigc·iphone·apple·苹果
2401_854391081 个月前
SwiftUI 革命:打造未来派用户界面的艺术
ui·ios·swiftui
肥肥呀呀呀1 个月前
苹果应用提审基本规范
apple·提审
Swift社区2 个月前
Swift 中的函数式核心与命令式外壳:单向数据流
ios·swiftui·swift
一牛2 个月前
Swift Actors: 防止数据竞争
swift·apple
东坡肘子2 个月前
肘子的 Swift 周报 #042| 经验是柄双刃剑
swiftui·swift·apple