SwiftUI 探索 - 狀態(tài)和數(shù)據(jù)流

SwiftUIiOS13新出的聲明式UI框架捧搞,將會完全改變以前命令式操作UI的開發(fā)方式砰琢。此文章主要介紹SwiftUI中狀態(tài)管理的方式芙贫。

可變狀態(tài)

@State

ReactFlutter中的State類似落萎,只不過ReactFlutter中需要顯式調用setState方法。在SwiftUI 中直接修改State屬性值躯砰,就觸發(fā)視圖更新。

因為State是使用了@propertyDelegate修飾的屬性值携丁,其內部實現(xiàn)應該是在狀態(tài)值set方法中進行變更視圖的操作琢歇。

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    var count: Int = 0 {
        didSet {
            didChange.send(self)// 調用didChange觸發(fā)變更操作
        }
    }
}
struct ContentView: View {
   @State private var text: String = "a"http:// 使用@State修飾
   @State private var model = Model()// 使用@State修飾
    var body: some View {
        VStack {
          Text(text)
          Text(model.count)
          Button(action: {
              self.text = "b"http:// 修改text會更新視圖
              self.count += 1
            }) {
              Text("update-text")
          }
        }
    }
}
  • 如果想使用class類型屬性作為State屬性,類對象需要實現(xiàn)BindableObject協(xié)議梦鉴。當調用didChangesend方法時李茫,會通知關聯(lián)的View更新視圖。didChangePublisher(新出的Combine異步事件處理框架肥橙,類似RxSwift)類型魄宏,調用send時會發(fā)送一個新的值給訂閱者。
  • 當修改的State屬性值沒有在body中使用或者修改后的State屬性值和上一次相同存筏,并不會觸發(fā)重新計算body宠互。

State屬性修改時,會檢測State屬性被使用和檢測值變更來決定要不要更新視圖和觸發(fā)body方法椭坚。

  • State屬性用class類型予跌。在觸發(fā)body重新計算前會檢查State值有沒有改變,當修改類對象屬性時藕溅,因為類對象指針并沒有改變匕得,所以并不會觸發(fā)視圖更新。如果想觸發(fā)視圖變更巾表,可以在修改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方法里也不能修改秽之,因為修改屬性會創(chuàng)建新的結構體。

@Binding

Property功能類似吃既,用于父視圖向子視圖傳遞值考榨。只不過Binding屬性可以修改,修改Binding屬性會觸發(fā)父視圖State改變重新計算body鹦倚『又剩可以實現(xiàn)反向數(shù)據(jù)流的功能,有點類似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相似掀鹅,暫時不太清楚使用上有什么區(qū)別。@State替換@ObjectBinding使用沒有問題媒楼,@Binding替換@ObjectBinding使用也沒有問題乐尊。

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    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)")
        }
    }
}

上面StateObjectBinding划址,Binding注釋的地方任意使用結果都一樣扔嵌,視圖能正確更新。

@EnvironmentObject

通過Property或者Binding的方式猴鲫,我們只能顯式的通過組件樹逐層傳遞对人。

顯式逐層傳遞的缺點

  • 當組件樹復雜的時候特別繁瑣,修改起來也很麻煩拂共。
  • 有些屬性在視圖樹中間的層級不會使用到牺弄,只有底層會使用。會增加中間層級視圖的復雜度宜狐。也可以避免中間的層級重復計算body觸發(fā)視圖更新势告。

為了避免層層傳遞屬性,可以使用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方法注入對象到組件樹中俭驮,子組件樹中共享同一個對象并且可以監(jiān)聽變更回溺。
  • @EnvironmentObject查找如何能獲取到對應的對象,大概是根據(jù)屬性的類型進行查找混萝,所以多個屬性只要類型相同遗遵,就能取到同樣的對象。當組件樹有多個組件使用environmentObject方法注入同類型的對象時逸嘀,獲取時會查找最近的父組件的對象车要。

目前好像沒有方式實現(xiàn)根據(jù)不同的key來注入多個對象并獲取。

數(shù)據(jù)流

父視圖 -> 子視圖向下傳遞

  • 不需要修改使用Property
  • 需要修改使用@Binding

父視圖 -> 子視圖跨層級向下傳遞

  • Environment

全局狀態(tài)層管理

  • 應該是結合Combine框架根據(jù)模塊功能領域分層進行管理崭倘。

視圖更新流程

  • 修改State觸發(fā)視圖更新翼岁,檢測State是否被使用以及值是否被改變类垫。
  • 重新計算body生成新的視圖樹,會重新創(chuàng)建所有子視圖的View結構體琅坡。
  • 遍歷所有子視圖悉患,判斷View結構體與更新前是否一致。當不一致時脑蠕,觸發(fā)子視圖更新购撼,調用子視圖body跪削。

Tips

關于 State

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    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更新時谴仙,會重新創(chuàng)建ChildView結構體。
  • ChildView中的State都會重新創(chuàng)建碾盐,StructModel初始化方法中晃跺,count一直為0,即使ContentViewState曾經(jīng)修改過毫玖。但是下一次修改State值時掀虎,State會使用之前的值做運算。

不太清楚這里是如何處理的付枫,State雖然重新初始化了一次烹玉,似乎還是使用的之前的State

  1. 例如當點擊Button時阐滩,會修改ChildViewmodel, model2count+=1二打,當前count=1。
  2. ChildView重新創(chuàng)建時掂榔,model继效,model2初始化方法中,count=0装获。
  3. 當下一次點擊Button修改count值時瑞信,count會在1的基礎上+1,之后count=2穴豫。

性能

  • 當視圖發(fā)生變更時凡简,由于body會經(jīng)常重新計算,所以應該盡量避免在body中進行重復和耗時計算精肃。
  • 視圖變更時秤涩,視圖組件View結構體會重新創(chuàng)建,所以應該避免在init方法中進行重復和耗時計算肋杖。(包括屬性的重新生成)
  • 根據(jù)上面 State的特性溉仑,當State屬性為結構體或類時,應避免在init方法中訪問或修改屬性状植。因為當State修改過后浊竟,在init方法中獲取到的值不是正確的怨喘,修改值也會生效。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末振定,一起剝皮案震驚了整個濱河市必怜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌后频,老刑警劉巖梳庆,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卑惜,居然都是意外死亡膏执,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門露久,熙熙樓的掌柜王于貴愁眉苦臉地迎上來更米,“玉大人,你說我怎么就攤上這事毫痕≌髀停” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵消请,是天一觀的道長栏笆。 經(jīng)常有香客問我,道長臊泰,這世上最難降的妖魔是什么蛉加? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮因宇,結果婚禮上七婴,老公的妹妹穿的比我還像新娘。我一直安慰自己察滑,他們只是感情好打厘,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贺辰,像睡著了一般户盯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饲化,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天莽鸭,我揣著相機與錄音,去河邊找鬼吃靠。 笑死硫眨,一個胖子當著我的面吹牛,可吹牛的內容都是我干的巢块。 我是一名探鬼主播礁阁,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼巧号,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姥闭?” 一聲冷哼從身側響起丹鸿,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棚品,沒想到半個月后靠欢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡铜跑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年门怪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疼进。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡薪缆,死狀恐怖伞广,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情疼电,我是刑警寧澤嚼锄,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蔽豺,受9級特大地震影響区丑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜修陡,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一沧侥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魄鸦,春花似錦宴杀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绢记,卻和暖如春扁达,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蠢熄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工跪解, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人签孔。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓叉讥,卻偏偏與公主長得像砾跃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子节吮,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344