SwiftUI是iOS13新出的声明式UI框架,将会完全改变以前命令式操作UI的开发方式。此文章主要介绍SwiftUI中状态管理的方式。

可变状态

@State

与React和Flutter中的State类似,只不过React和Flutter中需要显式调用setState方法。在SwiftUI 中直接修改State属性值,就触发视图更新。

因为State是使用了@propertyDelegate修饰的属性值,其内部实现应该是在状态值set方法中进行变更视图的操作。

class Model: BindableObject {

var didChange = PassthroughSubject()

var count: Int = 0 {

didSet {

didChange.send(self)// 调用didChange触发变更操作

}

}

}

struct ContentView: View {

@State private var text: String = "a"// 使用@State修饰

@State private var model = Model()// 使用@State修饰

var body: some View {

VStack {

Text(text)

Text(model.count)

Button(action: {

self.text = "b"// 修改text会更新视图

self.count += 1

}) {

Text("update-text")

}

}

}

}

如果想使用class类型属性作为State属性,类对象需要实现BindableObject协议。当调用didChange的send方法时,会通知关联的View更新视图。didChange是Publisher(新出的Combine异步事件处理框架,类似RxSwift)类型,调用send时会发送一个新的值给订阅者。

当修改的State属性值没有在body中使用或者修改后的State属性值和上一次相同,并不会触发重新计算body。

State属性修改时,会检测State属性被使用和检测值变更来决定要不要更新视图和触发body方法。

State属性用class类型。在触发body重新计算前会检查State值有没有改变,当修改类对象属性时,因为类对象指针并没有改变,所以并不会触发视图更新。如果想触发视图变更,可以在修改State时生成新的对象(这种方式不太好)或者使用BindableObject。

属性

Property

与React中的Props类似,用于父视图向子视图传递值。

struct PropertyView: View {

let text: String// 当text改变时,会重新计算`body`。

var body: some View {

Text(text)

}

}

struct ContentView: View {

var body: some View {

PropertyView(text: "a")

}

}

使用let变量。使用var变量修饰属性,在body方法里也不能修改,因为修改属性会创建新的结构体。

@Binding

与Property功能类似,用于父视图向子视图传递值。只不过Binding属性可以修改,修改Binding属性会触发父视图State改变重新计算body。可以实现反向数据流的功能,有点类似MVVM的双向绑定。

struct BindingView : View {

@Binding var text: String // 使用@Binding修饰

var body: some View {

VStack {

Button(action: {

self.text = "b"

}) {

Text("update-text")

}

}

}

}

struct ContentView : View {

@State private var text: String = "a" // State

var body: some View {

VStack {

BindingView(text: $text)// State变量使用$获取Binding

Text(text)

}

}

}

@ObjectBinding

@ObjectBinding似乎和State相似,暂时不太清楚使用上有什么区别。@State替换@ObjectBinding使用没有问题,@Binding替换@ObjectBinding使用也没有问题。

class Model: BindableObject {

var didChange = PassthroughSubject()

var count: Int = 0 {

didSet {

didChange.send(self)

}

}

}

struct ChildView: View {

// @Binding var model: Model

// @ObjectBinding var model: Model

var body: some View {

VStack {

Text("count2-\(model.count)")

Button(action: {

self.model.count += 1

}) {

Text("update")

}

}

}

}

struct ContentView : View {

// @State private var model = Model()

// @ObjectBinding private var model = Model()

var body: some View {

VStack {

ChildView(model: model)

Text("count1-\(model.count)")

}

}

}

上面State,ObjectBinding,Binding注释的地方任意使用结果都一样,视图能正确更新。

@EnvironmentObject

通过Property或者Binding的方式,我们只能显式的通过组件树逐层传递。

显式逐层传递的缺点

当组件树复杂的时候特别繁琐,修改起来也很麻烦。

有些属性在视图树中间的层级不会使用到,只有底层会使用。会增加中间层级视图的复杂度。也可以避免中间的层级重复计算body触发视图更新。

为了避免层层传递属性,可以使用Environment变量。Environment属性可以在任意子视图获取并使用。和React中的Context很相似。

struct EnvironmentView1: View {

var body: some View {

return VStack {

EnvironmentView2()

EnvironmentView3()

}

}

}

struct EnvironmentView2: View {

@EnvironmentObject var model: Model// 使用@EnvironmentObject修饰属性

var body: some View {

Button(action: {

self.model.change()

}) {

Text("update-Environment")

}

}

}

struct EnvironmentView3: View {

@EnvironmentObject var model: Model// EnvironmentObject

var body: some View {

Text(model.text)

}

}

struct ContentView: View {

var body: some View {

//EnvironmentObject需要使用environmentObject方法注入到组件树中

EnvironmentView1().environmentObject(Model())

}

}

通过environmentObject方法注入对象到组件树中,子组件树中共享同一个对象并且可以监听变更。

@EnvironmentObject查找如何能获取到对应的对象,大概是根据属性的类型进行查找,所以多个属性只要类型相同,就能取到同样的对象。当组件树有多个组件使用environmentObject方法注入同类型的对象时,获取时会查找最近的父组件的对象。

目前好像没有方式实现根据不同的key来注入多个对象并获取。

数据流

父视图 -> 子视图向下传递

不需要修改使用Property

需要修改使用@Binding

父视图 -> 子视图跨层级向下传递

Environment

全局状态层管理

应该是结合Combine框架根据模块功能领域分层进行管理。

视图更新流程

修改State触发视图更新,检测State是否被使用以及值是否被改变。

重新计算body生成新的视图树,会重新创建所有子视图的View结构体。

遍历所有子视图,判断View结构体与更新前是否一致。当不一致时,触发子视图更新,调用子视图body。

Tips

关于 State

class Model: BindableObject {

var didChange = PassthroughSubject()

var count: Int = 0 {

didSet {

didChange.send(self)

}

}

init() {

print("Model-init-\(count)")// 这里count始终为0

}

}

struct Struct {

private(set) var count = 0

init() {

print("Struct-\(count)")// 这里count始终为0

}

mutating func update() {

print("update-\(count)")

count += 1

}

}

struct ChildView: View {

@State private var model2 = Struct()

@State private var model = Model2()

@State private var count = 0

var body: some View {

return VStack {

Text("\(model.count)")

Text("\(model2.count)")

Text("\(count)")

Button(action: {// 修改 State

self.model.count += 1

self.count += 1

self.model2.update()

}) {

Text("update")

}

}

}

}

struct ContentView: View {

@State private var count = 0

var body: some View {

return VStack {

ChildView()

Button(action: {

self.count += 1

}) {

Text("update")

}

Text("\(count)")

}

}

}

当ContentView更新时,会重新创建ChildView结构体。

ChildView中的State都会重新创建,Struct和Model初始化方法中,count一直为0,即使ContentView里State曾经修改过。但是下一次修改State值时,State会使用之前的值做运算。

不太清楚这里是如何处理的,State虽然重新初始化了一次,似乎还是使用的之前的State。

例如当点击Button时,会修改ChildView中model, model2中count+=1,当前count=1。

当ChildView重新创建时,model,model2初始化方法中,count=0。

当下一次点击Button修改count值时,count会在1的基础上+1,之后count=2。

性能

当视图发生变更时,由于body会经常重新计算,所以应该尽量避免在body中进行重复和耗时计算。

视图变更时,视图组件View结构体会重新创建,所以应该避免在init方法中进行重复和耗时计算。(包括属性的重新生成)

根据上面 State的特性,当State属性为结构体或类时,应避免在init方法中访问或修改属性。因为当State修改过后,在init方法中获取到的值不是正确的,修改值也会生效。

swift x输入流_SwiftUI 探索 - 状态和数据流相关推荐

  1. 重新思考Swift中的设计模式:状态模式

    本文展示了如何在Swift中实现State模式,利用Swift的强大功能使实现更简单,更灵活.我们通过一个例子来探索状态模式的各个方面,并评估其处理需求变化的能力.然后,我们将了解关键实现注意事项以及 ...

  2. swift php 类型判断,iOS开发 - Swift实现检测网络连接状态及网络类型

    一.前言 在移动开发中,检测网络的连接状态尤其检测网络的类型尤为重要.本文将介绍在iOS开发中,如何使用Swift检测网络连接状态及网络类型(移动网络.Wifi). 二.如何实现 Reachabili ...

  3. Swift - 判端网络连接状态,连接类型(3G还是Wifi)

    IJReachability是一个使用Swift写的第三方网络检测类.可以测试网络是否连接,并支持3G和Wifi的检测. 使用样例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  4. swift x输入流_Swift 中不同窗体的切换和传递数据 (segue 的用法)

    废话不多说,直接进入正题 首先在 storyboard 中新建一个 ViewController,再新建一个名为 NewViewController 的 cocoa touch class 文件,继承 ...

  5. swift 3.0 再探索 - 1.String

    swift 3.0 - 1.String 最近有空可以学习点新东西,有段时间在学习java和mySQL,转方向没那么快可以做到,眼下iOS 10 和 Xcode8正式版准备上线,swift3.0也标示 ...

  6. 如何将某个groupbox中的数据赋值到另一个groupbox_React中的数据和数据流

    第2章大致介绍了React.我们花了些时间学习了React,了解它的设计和API背后的一些概念,我们甚至还逐步说明了如何用React组件构建一个简单注释框.在第4章中,我们将开始更全面地使用组件并开始 ...

  7. 百度App Objective-C/Swift 组件化混编之路(一)

    作者丨郭金.陈佳 来源丨百度App技术 一. 背景 1.1 Swift 发展历史 2010 年 7 月,克里斯(Chris Lattner)开始设计 Swift.完成基础架构后,克里斯带领开发小组陆续 ...

  8. swift和swiftui_在swiftui中查看布局和演示

    swift和swiftui 您一直在等待的完整SwiftUI 2文档 (The Complete SwiftUI 2 Documentation You've Been Waiting For) At ...

  9. 干货分享 | Swift在淘系技术的演进历程

    7月初,淘系技术联合老司机技术周报举办了今年淘系技术.T 沙龙杭州专场,此次活动共邀请4位国内嘉宾,2位国外嘉宾. 淘系技术专家倾寒为大家带来关于<淘系工程渐进式拥抱Swift>的分享. ...

最新文章

  1. (十二)struts2的类型转换
  2. C#常量——const和readonly
  3. linux ssh 登录后 空闲断开 解决方法
  4. 在局域网访问_局域网访问共享文件需要密码怎么办?取消访问密码的方法
  5. [转帖]Oracle 11G RAC For Windows 2008 R2部署手册
  6. [C++] - 面向对象-图书管理系统
  7. Flash和margue字幕滚动效果
  8. linux telnet 127.0.0.1 不通,Linux 出现telnet: 127.0.0.1: Connection refused错误解决办法
  9. html5的web存储详解
  10. 11.2 正睿停课训练 Day15
  11. ThinkpadT470接通电源开机显示电量0%充不进电且电源指示灯不亮的解决办法
  12. C#对称加密(3des)和非对称加密(rsa)算法
  13. Linux下设置定期执行脚本
  14. paip.navicat form mysql导入文本文件时CPU占用100%的解决
  15. C语言实现HDB3编码与译码
  16. NB-IoT蜂窝窄带物联网概念
  17. React.js 菜鸟入门
  18. 获取emf图片格式的方法
  19. 网易云音乐html代码报告,网易云音乐H5案例欣赏:你的2017年度听歌报告
  20. 红帽linux系统内核版本7,如何查看Linux发行版内核版本及系统版本?

热门文章

  1. 简单DP【p2642】双子序列最大和
  2. BZOJ 2957楼房重建
  3. pku 3422 Kaka's Matrix Travels 最大费用最大流
  4. scrollLeft,scrollWidth,clientWidth,offsetWidth到底指的哪到哪的距离
  5. 【C++】关联容器学习记录
  6. 【C++】C/C++ 中多态情形下的虚函数表查看方法
  7. wireshark的使用教程--用实践的方式帮助我们理解TCP/IP中的各个协议是如何工作的
  8. .NET2.0隐形的翅膀,正则表达式搜魂者【月儿原创】
  9. 第四层到第七层的高层交换技术及其应用
  10. 矩阵奇异值分解简介及C++/OpenCV/Eigen的三种实现