SwiftUI何时为值类型的视图提供持久标识

首先来一段英文,这是apple wwdc中的一句,没兴趣的话可以略过不看

value types do not have a canonical reference that SwiftUI can use as a persistent identity for its views

developer.apple.com/videos/play...

"值类型没有规范引用,SwiftUI无法将其用作视图的持久标识"-果然每个字都认识,不知道在说啥

规范引用(Canonical Reference):指的是一个唯一的、持久的引用,比如对象在内存中的地址。对于引用类型(类),每个实例在内存中都有唯一的地址,因此可以将其作为标识。这里说的引用是指索引,标记,使用来找到对应值的东西,并非我们经常提到的"引用类型"中的引用。

SwiftUI 面临的挑战

less 复制代码
// 初始状态
VStack {    
    Text("Hello") // 视图1    
    Button("Click") // 视图2
}
// 更新后状态
VStack {    
    Button("Clicked") // 这是原来的按钮吗?    
    Text("Hello")     // 这是原来的文本吗?
}

当视图更新时:

  • 如果 SwiftUI 无法识别视图是"同一个"

    1、会导致状态丢失(如输入框内容清空)

    2、动画会中断而不是平滑过渡

    3、性能下降(因为要重建整个视图、

我们看下if- else的情况

less 复制代码
import SwiftUI
struct ContentView: View {
    @State var showFirst = true
    @State var counter = 0
    
    var body: some View {
        VStack {
            Button("切换视图") {
                showFirst.toggle()
            }
            
            // 示例1: 不设置 id - SwiftUI 会认为是两个不同的 view
            Text("示例1: 不设置 id")
            if showFirst {
                CounterView(title: "第一个", counter: $counter)
                    .background(Color.red.opacity(0.3))
            } else {
                CounterView(title: "第二个", counter: $counter)
                    .background(Color.blue.opacity(0.3))
            }
            
            Divider()
            
            // 示例2: 设置相同的 id - SwiftUI 会认为是同一个 view
            Text("示例2: 设置相同的 id")
            if showFirst {
                CounterView(title: "第一个", counter: $counter)
                    .background(Color.red.opacity(0.3))
                    .id("same_view")
            } else {
                CounterView(title: "第二个", counter: $counter)
                    .background(Color.blue.opacity(0.3))
                    .id("same_view")
            }
        }
        .padding()
    }
}
struct CounterView: View {
    let title: String
    @Binding var counter: Int
    @State private var internalCounter = 0
    
    var body: some View {
        VStack {
            Text(title)
            Text("内部计数器: \(internalCounter)")
            Button("内部 +1") {
                internalCounter += 1
            }
            Text("外部计数器: \(counter)")
            Button("外部 +1") {
                counter += 1
            }
        }
        .padding()
        .border(Color.gray)
        .onAppear {
            print("创建子视图")
        }
    }
}

可以发现只要是if-else的swiftUI都会使用结构化标识,不论是否设置id,if和else中的view设置id相同与否,都不影响结果:每一次if和else的转换都会导致生产新的CounterView实例,因为其内部计数都是从0开始。

结论:我们无法在if和else中通过设置view的id来达到优化为只创建一个view的效果对于使用结构化标识的view,swiftUI还会自动优化,将上述例子中的一些代码做一下修改:

css 复制代码
var body: some View {
        VStack {
            Button("切换视图") {
                showFirst.toggle()
            }

            //第一种
    //            showFirst ? CounterView(title: "第一个", counter: $counter)
    //                .background(Color.red.opacity(0.3)) : CounterView(title: "第二个", counter: $counter)
    //                .background(Color.blue.opacity(0.3))

            //第二种
            showFirst ? CounterView(title: "第一个")
                .background(Color.red.opacity(0.3)) : CounterView(title: "第二个")
                .background(Color.blue.opacity(0.3))
        }
        
    
    }

可以发现无论是第一种方式-使用了绑定,还是第二种-无任何绑定,counterView都只生成了一次,无论我们切换试图多少次,已经生成的counterView的内部计数都是连续的如果我们给每个CounterView的实例设置了不同的id,就可以生成两个view,并且每次切换showFirst,都会重新生成要展示的view可以看出给view设置不同的id,会分割生命周期,这也会导致不显示的view的回收

SwiftUI 的两种身份标识

**结构化标识(位置标识)

通过视图在视图树中的位置识别:

scss 复制代码
- ContentView  
    - VStack    
        - Text("标题")      // 位置0    
        - CounterButton()  // 位置1 ← 靠这个位置识别

显式标识(唯一ID)

手动指定唯一标识符:

scss 复制代码
CounterButton()    
.id("mainCounter") // 显式标识
相关推荐
SoaringHeart9 小时前
SwiftUI组件封装:仿 Flutter 原生组件 Wrap实现
ios·swiftui
YungFan9 小时前
SwiftUI-自定义与扩展
swiftui·swift
东坡肘子13 小时前
WWDC 2025 开发者特辑 | 肘子的 Swift 周报 #088
swiftui·swift·wwdc
大熊猫侯佩13 小时前
Swift 中强大的 Key Paths(键路径)机制趣谈(下)
swift·编程语言·apple
season_zhu13 小时前
RxSwift:dispose() 和 disposed(by:) 以及NSObject+Rx
ios·swift·rxswift
健了个平_2420 小时前
iOS 26 适配笔记
ios·swift·wwdc
大熊猫侯佩1 天前
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(上)
swift·协议·protocol·coredata·协议扩展·托管基类·协议关联类型