SwiftUI學習之@State、@Binding、@ObservedObject、@EnvironmentObject、@StateObject

一、@State

和一般的存儲屬性不同,@State 修飾的值,在 SwiftUI 內(nèi)部會被自動轉(zhuǎn)換為一對 setter 和 getter,對這個屬性進行賦值的操作將會觸發(fā) View 的刷新,它的 body 會被再次調(diào)用,底層渲染引擎會找出界面上被改變的部分,根據(jù)新的屬性值計算出新的 View,并進行刷新。

  • 1、基本使用
struct ContentView: View {
    @State var count = "eeer"
    var body: some View {
        VStack{
         Text(count)
           Button("點我") { //添加一個按鈕,指定標題文字為 First button
              self.count = "1234567890"
           }.background(.red)
        }
    }
}

注意ContentView是一個結(jié)構(gòu)體,它可以被創(chuàng)建為一個常量。如果你回想一下你學習結(jié)構(gòu)體的時候,那意味著它是不可變的——我們不能自由地改變它的值。 當創(chuàng)建想要更改屬性的結(jié)構(gòu)體方法時,我們需要添加mutating關(guān)鍵字:mutating func doSomeWork(),例如。然而,Swift不允許我們創(chuàng)建可變計算屬性,這意味著我們不能編寫mutating var body: some View——這是不允許的。幸運的是,Swift為我們提供了一個稱為屬性包裝器的特殊解決方案:我們可以在屬性之前放置一個特殊的屬性,有效地賦予它們超能力。在存儲簡單的程序狀態(tài)(如按鈕被點擊的次數(shù))的情況下,我們可以使用SwiftUI中名為@state的屬性包裝器。@State允許我們繞過結(jié)構(gòu)體的限制:我們知道不能更改它們的屬性,因為結(jié)構(gòu)是固定的,但是@State允許SwiftUI將該值單獨存儲在可以修改的地方。
上面代碼運行一下:self.count重新賦值,會觸發(fā)View刷新,Text顯示的文字將會變?yōu)椋?234567890

  • 2、值傳遞
    例一
struct ContentView: View {
    @State var count = "eeer"
    var body: some View {
        VStack{
           
            MapView(count1: count)
        }
        Text(count)
            .padding()
    }
}
struct MapView: View{
  @State  var count1: String
    var body: some View {
          Button("點我") { //添加一個按鈕,指定標題文字為 First button
          self.count1 = "1234567890"
       }
    }
}

MapView從父視圖傳遞count值給內(nèi)部count1,是值類型的傳遞。正如普通的Swift函數(shù)傳遞參數(shù)一樣,是值的拷貝,修改MapView的count1,不會影響ContentView的count值。
例二

struct Item {
    var name = "1"
    var age = "10"
}
struct ContentView: View {
    @State var item = Item()
    var body: some View {
      VStack{
          Text(item.name)
              .padding()
            Button("點我") { //添加一個按鈕,指定標題文字為 First button
                item.name = "測試"
            }
      }
    }
}

這里Item是結(jié)構(gòu)體,本來就是值類型就可以直接修改Item的成員屬性
例三

class Item {
    var name = "1"
    var age = "10"
}
struct ContentView: View {
    @State var item = Item()
    var body: some View {
      VStack{
          Text(item.name)
              .padding()
            Button("點我") { //添加一個按鈕,指定標題文字為 First button
                item.name = "測試"
            }
      }
    }
}

注意這里的Item是個類對象,我們知道類對象一般是引用類型,這里雖然@State可以修飾 item 對象,編譯器不會報錯。但是你點擊修改 item.name = "測試"時,運行一下Text顯示的值不會有任何變化。說明@State修飾引用對象,修改值是無效的。

二、@Binding

和 @State 類似,@Binding 也是對屬性的修飾,它做的事情是將值語義的屬性“轉(zhuǎn)換”為引用語義。對被聲明為 @Binding 的屬性進行賦值,改變的將不是屬性本身,而是它的引用,這個改變將被向外傳遞.

上面2-例1的例子是值傳遞,假如我們想實現(xiàn)引用傳遞,就可以使用@Binding解決這個問題。

struct ContentView: View {
    @State var count = "eeer"
    var body: some View {
        VStack{
           
            MapView(count1: $count)
        }
        Text(count)
            .padding()
    }
}
struct MapView: View{
  @Binding  var count1: String
    var body: some View {
          Button("點我") { //添加一個按鈕,指定標題文字為 First button
          self.count1 = "1234567890"
       }
    }
}
  • @Binding將 count1的引用改變成向外傳遞
  • 在傳遞 count時,我們在它前面加上美元符號 $。

在 Swift 中,對一個由 @ 符號修飾的屬性,在它前面使用 $ 所取得的值,被稱為投影屬性 (projection property)。有些 @ 屬性,比如這里的 @State 和 @Binding,它們的投影屬性就是自身所對應(yīng)值的 Binding 類型。不過要注意的是,并不是所有的 @ 屬性都提供 $ 的投影訪問方式。
這里運行一下改變self.count1 = "1234567890",ContentView的count會隨著一起改變,此時Text顯示1234567890

三、屬性包裝

在 SwiftUI中,像@State,@Binding這樣對屬性進行修飾,稱之為屬性包裝 (Property Wrapper)。

四、@ObservedObject

如果說 @State 是全自動駕駛的話,ObservableObject 就是半自動,它需要一些額外的聲明。ObservableObject 協(xié)議要求實現(xiàn)類型是 class,它只有一個需要實現(xiàn)的屬性:objectWillChange。在數(shù)據(jù)將要發(fā)生改變時,這個屬性用來向外進行“廣播”,它的訂閱者 (一般是 View 相關(guān)的邏輯) 在收到通知后,對 View 進行刷新。
創(chuàng)建 ObservableObject 后,實際在 View 里使用時,我們需要將它聲明為 @ObservedObject。這也是一個屬性包裝,它負責通過訂閱 objectWillChange 這個“廣播”,將具體管理數(shù)據(jù)的 ObservableObject 和當前的 View 關(guān)聯(lián)起來。

final class Person:ObservableObject{
    @Published var name = "張三"
}

struct ContentView: View {
    @ObservedObject var p = Person()
    var body: some View {
        VStack{
            Text(p.name)
                .padding()
            Button("點我") { //添加一個按鈕,指定標題文字為 First button
                p.name = "1234567890"
            
         }
        }
    }
}
  • @ObservedObject修飾的必須是遵守ObservableObject 協(xié)議的class對象
  • class對象的屬性只有被@Published修飾時,屬性的值修改時,才能被監(jiān)聽到。

五、@EnvironmentObject

在 SwiftUI 中,View 提供了 environmentObject(_:) 方法,來把某個 ObservableObject 的值注入到當前 View 層級及其子層級中去。在這個 View 的子層級中,可以使用 @EnvironmentObject 來直接獲取這個綁定的環(huán)境值。

final class Person:ObservableObject{
  @Published var name = "測試測試測試測試測試測試"
   
}
struct ContentView: View {
    var body: some View {
        VStack{
            let p = Person()
            MapView().environmentObject(p)
        }
    }
}

struct MapView: View {
    @EnvironmentObject var p:Person
    var body: some View {
        VStack{
            Text(p.name)
            Button("點我") { //添加一個按鈕,指定標題文字為 First button
                p.name = "1234567890"
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
     
        ContentView()
    }
}
  • @EnvironmentObject 修飾器是針對全局環(huán)境的。通過它,我們可以避免在初始 View 時創(chuàng)建 ObservableObject, 而是從環(huán)境中獲取 ObservableObject

  • 可以看出我們獲取 p這個 ObservableObject 是通過 @EnvironmentObject 修飾器,但是在入口需要傳入 .environmentObject(p) 。@EnvironmentObject 的工作方式是在 Environment 查找 Person 實例。

六、@StateObject

StateObject行為類似ObservedObject對象,區(qū)別是StateObject由SwiftUI負責針對一個指定的View,創(chuàng)建和管理一個實例對象,不管多少次View更新,都能夠使用本地對象數(shù)據(jù)而不丟失

import SwiftUI
final class Person:ObservableObject{
  @Published  var name = "測試"
    deinit{
        print("銷毀")
    }
}
struct ContentView: View {
    @StateObject var p = Person()
    var body: some View {
        VStack{
            Text(p.name)
            Button("點擊"){
                  p.name = "哈哈哈"
            }
           
        }
    }
}

@StateObject和@ObservedObject區(qū)別:

  • 1、@ObservedObject只是作為View的數(shù)據(jù)依賴,不被View持有,View更新時ObservedObject對象可能會被銷毀
    適合數(shù)據(jù)在SwiftUI外部存儲,把@ObservedObject包裹的數(shù)據(jù)作為視圖的依賴,比如數(shù)據(jù)庫中存儲的數(shù)據(jù)`
  • 2、針對引用類型設(shè)計,當View更新時,實例不會被銷毀,與State類似,使得View本身擁有數(shù)據(jù)
例子1:
import SwiftUI
final class Person:ObservableObject{
  @Published  var name = 1
    deinit{
        print("銷毀")
    }
}

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack{
            Text("刷新CounterView計數(shù) :\(count)")
                       Button("刷新"){
                           count += 1
            }
           MapView()
        }
    }
}

struct MapView: View {
    @ObservedObject var p = Person()
    var body: some View {
        VStack{
            Text("\(p.name)")
            Button("+1") { //添加一個按鈕,指定標題文字為 First button
                p.name += 1
            }

        }
    }
}
  • 點擊刷新時,Person 的deinit方法被調(diào)用,說明p對象被銷毀;
    先連續(xù)點擊+1,Text上的數(shù)字在一直遞增,當點擊刷新時Text上的數(shù)字恢復為1,這個現(xiàn)象也說明p對象被銷毀

例子2

import SwiftUI
final class Person:ObservableObject{
  @Published  var name = 1
    deinit{
        print("銷毀")
    }
}

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack{
            Text("刷新CounterView計數(shù) :\(count)")
                       Button("刷新"){
                           count += 1
            }
           MapView()
        }
    }
}

struct MapView: View {
    @StateObject var p = Person()
    var body: some View {
        VStack{
            Text("\(p.name)")
            Button("+1") { //添加一個按鈕,指定標題文字為 First button
                p.name += 1
            }

        }
    }
}
  • 和例1不同的是怎么操作,p都不會銷毀

小結(jié):

@StateObject的聲明周期與當前所在View生命周期保持一致,即當View被銷毀后,StateObject的數(shù)據(jù)銷毀,當View被刷新時,StateObject的數(shù)據(jù)會保持;而ObservedObject不被View持有,生命周期不一定與View一致,即數(shù)據(jù)可能被保持或者銷毀;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容