Observation 实践模板

如何使用 Observation,实际上是很容易搜索到的内容,即使是在中文互联网,也能找到不少讲解清晰的 Observation 框架使用指南,原理剖析。但是问题出在 AI 不能很好的去实践 Observation 框架,我想肯定和之前推出的 @ObservableObject@StateObject 等看起来有点类似的关键字有关,AI 有的时候能搞清楚两者区别,有的时候又会将两者混用,让人胆战心惊。

所以我在这边准备了一份给 AI 的 Observation 实践模板,在需要的时候,能让AI 更懂 Observation

每个项目的架构、每个人的代码风格不同,会导致模板代码有差异,请检查后使用

实践模板

写在代码前

Observation 需要 iOS 17.0+ 支持,如需支持更早版本,有第三方库可以选择,例如:ObservationBP

如果即将编写的代码,从结构上是分离,与其他部分没有过多耦合,那么可以只使用 Observation 框架,不使用 @ObservableObject@StateObject 等关键字。

具体如何使用 Observation

给类前面加一个 @observable 就可以让它可被观察:

@Observable class Blog {
    var title: String
    var author: String
  	var progress: Double = 0.0
    
    init(title: String, author: String) {
        self.title = title
        self.author = author
    }
}

在 SwiftUI 相关的 View 里直接使用:

struct BlogView: View {
    @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
    var body: some View {
        VStack {
            Text("Title: \(blog.title)")
            Text("Author: \(blog.author)")
        }
        .padding()
    }
}

通过直接传递的形式使用:

struct BlogView: View {
    @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
    var body: some View {
        DisplayView(blog: blog).padding()
    }
}

struct DisplayView: View {
    let blog: Blog
    var body: some View {
        VStack {
            Text("Title: \(blog.title)")
            Text("Author: \(blog.author)")
        }
    }
}

通过直接传递的形式使用,同时需要实现数据的绑定:

struct BlogView: View {
    @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
    var body: some View {
        VStack {
            Text("Title: \(blog.title)")
            Text("Author: \(blog.author)")
            Text("Progress: \(Int(blog.progress * 100))%")
            ControlView(blog: blog)
        }
        .padding()
    }
}

struct ControlView: View {
    @Bindable var blog: Blog
    var body: some View {
        VStack {
            Slider(value: $blog.progress, in: 0...1)
            Button("Change Blog Info") {
                blog.title = "山雨欲来风满楼"
                blog.author = "Mimoku"
            }
        }
    }
}

另一种通过传递的形式使用;可行,但性质变了,不推荐这样使用:

struct BlogView: View {
    @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
    var body: some View {
        VStack {
            Text("Title: \(blog.title)")
            Text("Author: \(blog.author)")
            Text("Progress: \(Int(blog.progress * 100))%")
            ControlView(blog: $blog)
        }
        .padding()
    }
}

struct ControlView: View {
    @Binding var blog: Blog
    var body: some View {
        VStack {
            Slider(value: $blog.progress, in: 0...1)
            Button("Change Blog Info") {
                blog.title = "山雨欲来风满楼"
                blog.author = "Mimoku"
            }
        }
    }
}

通过 enviroment 注入可观察对象,供 @Enviroment 来使用;注意 @Bindable 的绑定实现:

struct BlogView: View {
    @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
    var body: some View {
        VStack {
            Text("Title: \(blog.title)")
            Text("Author: \(blog.author)")
            Text("Progress: \(Int(blog.progress * 100))%")
            ControlView()
        }
        .padding()
        .environment(blog)
    }
}

struct ControlView: View {
    @Environment(Blog.self) var blog: Blog // 这里也可以是 Blog? 可选
    var body: some View {
        VStack {
            @Bindable var bindableBlog = blog
            Slider(value: $bindableBlog.progress, in: 0...1)
            Button("Change Blog Info") {
                blog.title = "山雨欲来风满楼"
                blog.author = "Mimoku"
            }
        }
    }
}
如何与 @AppStorage 配合使用

实际上在原生上,并没有特别好的配合方案,所以约定按下方这样使用;请注意,通常情况下尽量让 @AppStorage 在 SwiftUI View 中直接声明,谨慎将其在类似 ViewModel 的组织中使用:

enum BlogAppStorageKeys: String {
    case lastOpenedBlogTitle
    case myFavoriteBlogAuthor
}

struct AnyBlogView: View {
    @AppStorage(BlogAppStorageKeys.lastOpenedBlogTitle.rawValue) var lastOpenedBlogTitle = ""
    @AppStorage(BlogAppStorageKeys.myFavoriteBlogAuthor.rawValue) var myFavoriteBlogAuthor = ""
    var body: some View {
        VStack {
            Text("Title: \(lastOpenedBlogTitle)")
            Text("Author: \(myFavoriteBlogAuthor)")
            Button("Update AppStorage Values") {
                lastOpenedBlogTitle = "山雨欲来风满楼"
                myFavoriteBlogAuthor = "Mim0sa"
            }
        }
        .padding()
    }
}
不要这样做⚠️

Observation 框架之前,可能会有类似这样的数据传输形式,将数据拆分开,向下传递;在之前,这样的传递形式可以减少 View 的不必要更新次数;但是在 Observation 框架下不要这样写❌:

struct ContentView: View {
  @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
  var body: some View {
    TitleView(title: blog.title)
    AuthorView(author: blog.author)
  }
}

struct TitleView: View {
  let title: String
  var body: some View { Text(title) }
}

struct AuthorView: View {
  let author: String
  var body: some View { Text(author) }
}

✅正确的写法:

struct ContentView: View {
  @State var blog = Blog(title: "溪云初起日沉阁", author: "Mim0sa")
  var body: some View {
    TitleView(blog: blog)
    AuthorView(blog: blog)
  }
}

struct TitleView: View {
  @Bindable var blog: Blog
  var body: some View { Text(blog.title) }
}

struct AuthorView: View {
  @Bindable var blog: Blog
  var body: some View { Text(blog.author) }
}