SwiftUI之深入解析@StateObject、@ObservedObject和@EnvironmentObject的联系和区别
生活随笔
收集整理的這篇文章主要介紹了
SwiftUI之深入解析@StateObject、@ObservedObject和@EnvironmentObject的联系和区别
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、@State 屬性包裝器
① 什么是 @State 屬性包裝器?
- 狀態在任何現代應用程序中都是不可避免的,但在 SwiftUI 中,重要的是所有的視圖都是它們狀態的簡單函數,我們不需要直接改變視圖,而是操縱狀態,讓狀態決定結果。SwiftUI 提供了幾種在應用程序中存儲狀態的方法,但它們之間有細微的差別,為了正確地使用它們,理解它們之間的差別是很重要的。
- 處理 state 的最簡單的方法是 @State 屬性包裝器,使用如下:
- 這在視圖中創建了一個屬性,但是它使用 @State 屬性包裝器來要求 SwiftUI 管理內存。這一點非常重要,我們所有的視圖都是結構體,這意味著它們不能被改變,如果甚至不能在應用程序中修改一個整數,那么我們也做不了什么。所以,當我們說 @State 來創建屬性時,把它的控制權交給 SwiftUI,這樣只要視圖存在,它就會一直存在于內存中;當狀態發生變化時,SwiftUI 會自動重新加載帶有最新變化的視圖,這樣它就可以反映它的新信息。
- @State 對于屬于特定視圖且永遠不會在該視圖之外使用的簡單屬性非常有用,因此,將這些屬性標記為 private 非常重要,以強化這樣一種想法,即此類狀態是專門設計的,永遠不會逃逸其視圖。
② 為什么 @State 只適用于結構體?
- SwiftUI 的 State 屬性包裝器是為當前視圖本地的簡單數據而設計的,但是一旦您想在視圖之間共享數據,它就不再有用了。讓我們用一些代碼來分解它,如下所示,是一個存儲用戶名字和姓氏的結構:
- 現在可以通過創建 @State 屬性并將內容附加到 $user.firstName 和 $user.lastName 來在 SwiftUI 視圖中使用它,如下所示:
- 可以看到程序一切正常,SwiftUI 足夠聰明,可以理解一個對象包含所有數據,并且會在任一值更改時更新 UI。在背后,實際發生的情況是,每次結構中的值發生變化時,整個結構都會發生變化,每次鍵入名字或姓氏的鍵時,就像一個新用戶,這樣聽起來可能很浪費,但實際上非常快。
- 類和結構體之間的區別,有兩個重要的區別:
-
- 首先,結構體始終具有唯一的所有者,而對于類,多個事物可以指向相同的值;
-
- 其次,類在更改其屬性的方法之前不需要 mutating 關鍵字,因為可以更改常量類的屬性。
- 實際上,這意味著如果我們有兩個 SwiftUI 視圖,并且向它們發送相同的結構體以供使用,那么它們實際上每個都有該結構體的唯一副本;如果一個更改它,另一個將不會看到該更改;另一方面,如果創建一個類的實例并將其發送到兩個視圖,它們將共享更改。
- 對于 SwiftUI 開發人員來說,這意味著如果我們想在多個視圖之間共享數據,希望兩個或多個視圖指向相同的數據,以便在一個更改時它們都會得到這些更改,那么需要使用的是類而不是結構。
- 因此,請將 User 結構體更改為一個類,如下:
- 現在再次運行程序,會發生什么呢?可以看到,它不再起作用了,當然還可以像以前一樣在文本字段中輸入,但上面的文本視圖不會改變。
- 當使用@State 時,我們要求 SwiftUI 觀察屬性的變化。因此,如果改變一個字符串,改變一個布爾值,添加到一個數組等,屬性已經改變,SwiftUI 將重新調用視圖的 body 屬性。
- 當 User 是一個結構體時,每次修改該結構體的屬性時,Swift 實際上都是在創建該結構體的一個新實例。@State 能夠發現這種變化,并自動重新加載視圖。現在我們改成了一個類,這種行為不再發生:Swift 可以直接修改值。
- 還記得我們是如何對修改屬性的結構體方法使用 mutating 關鍵字的嗎?這是因為如果我們將結構體的屬性創建為變量,但結構體本身是常量,無法更改屬性,Swift 需要能夠在屬性更改時銷毀并重新創建整個結構體,而這對于常量結構;類不需要 mutating 關鍵字,因為即使類實例被標記為常量 Swift 仍然可以修改變量屬性。
二、什么是 @StateObject?
① @StateObject 的概念和使用
- SwiftUI 的 @StateObject 屬性包裝器旨在填補狀態管理中的一個非常具體的空白:當需要在一個視圖中創建引用類型并確保它保持活動狀態以在該視圖和共享的其他視圖中使用時。例如,考慮一個簡單的 User 類,如下:
- 如果想在各種視圖中使用它,需要在 SwiftUI 外部創建它并將其注入,或者在一個 SwiftUI 視圖中創建它并使用 @StateObject,如下所示:
- 這將確保 User 實例在視圖更新時不會被破壞。以前可能已經使用 @ObservedObject 來獲得相同的結果,但這是有風險的。有時 @ObservedObject 可能會意外釋放它正在存儲的對象,因為它不是設計為最終的真相來源目的,但 @StateObject 就不會發生這種情況,因此應該改用它。
- 在 @State 和 @ObservedObject 之間存在 @StateObject,這是 @ObservedObject 的特有版本,它們幾乎是相同的工作方式:必須符合 ObservableObject 協議,可以使用 @Published 將屬性標記為導致更改通知,和任何觀察 @StateObject 的視圖都會在對象發生變化時刷新它們的主體。
- @StateObject 和 @ObservedObject 之間有一個重要的區別,即所有權,哪個視圖創建了對象,哪個視圖只是在監視它。規則是這樣的:首先創建對象的視圖必須使用 @StateObject,告訴 SwiftUI 它是數據的所有者并負責保持它的活動,所有其它視圖都必須使用 @ObservedObject 來告訴 SwiftUI,它們想要觀察對象的變化但不直接擁有它。
- 每個對象應該只使用一次 @StateObject,它應該在負責創建對象的任何視圖中,共享對象的所有其它視圖都應使用 @ObservedObject。
② 如何使用 @StateObject 創建和監控外部對象?
- SwiftUI 的 @StateObject 屬性包裝器是 @ObservedObject 的一種特殊形式,具有所有相同的功能,但有一個重要的補充:它應該用于創建觀察對象,而不僅僅是存儲從外部傳入的對象。
- 當使用 @StateObject 向視圖添加屬性時,SwiftUI 認為該視圖是可觀察對象的所有者,傳遞該對象的所有其他視圖都應使用 @ObservedObject,這非常重要,如果弄錯,可能會發現對象被意外破壞了,這會導致應用程序看似隨機崩潰。因此明確一點:我們應該使用 @StateObject 在某處創建可觀察對象,并且在傳遞該對象的所有后續地方應該使用 @ObservedObject。
- 如下所示:
- 如果發現很難記住區別,可以嘗試以下操作:每當在屬性包裝器中看到“State”時,例如 @State、@StateObject、@GestureState,表示“當前視圖擁有這些數據”。
三、什么是 @ObservedObject?
① @ObservedObject 的概念和使用
- 對于一些更復雜的屬性,當想要使用的自定義類型的時候,可能會有多個屬性和方法,或者可能在多個視圖之間共享,那么通常會使用 @ObservedObject。SwiftUI 提供了 @ObservedObject 屬性包裝器,以便視圖可以觀察外部對象的狀態,并在重要的事情發生變化時得到通知,它在行為上與@StateObject 相似,除了它不能用于創建對象。@ObservableObject 只能用于在其他地方創建的對象,否則 SwiftUI 可能會意外破壞該對象。如下所示:
- 該 Order 類使用 @Published 因此它會在項目更改時自動發送更改通知,而 ContentView 使用 @ObservedObject 來監視這些通知,如果沒有@ObservedObject,更改通知將被發送但被忽略。
- @ObservedObject 與 @State 也非常相似,只是現在使用的是外部引用類型,而不是簡單的本地屬性(如字符串或整數)。如果仍然說視圖依賴于將要更改的數據,除了現在它是我們自己負責管理的數據,那么需要創建類的一個實例,創建它自己的屬性等。
- @ObservedObject 使用的任何類型都應該符合 ObservableObject 協議,當向可觀察對象添加屬性時,需要決定對每個屬性的更改是否應該強制監視對象的視圖刷新,通常可能會這樣做,但這不是必需的。這反過來意味著它必須是一個類而不是結構體,這不是可選的,SwiftUI 要求使用一個類。
- 不過,還是有號幾種方法可以讓被觀察對象通知視圖重要數據發生了更改,但最簡單的方法是使用 @Published 屬性包裝器,符合 ObservableObject 的類型被賦予一個默認的 objectWillChange 發布者,以根據需要制作自定義公告。如果需要更多的控制,也可以使用 Combine 框架中的自定義發布者,但實際上這是非常少見的。如果這個可觀察對象碰巧有幾個視圖在使用它的數據,任何一個選項都會自動通知所有視圖(注意:當使用自定義發布程序來宣布對象已更改時,這必須在主線程中發生)。
- 觀察對象是專門為視圖外部的數據設計的,這意味著它可能會在多個視圖之間共享。 @ObservedObject 屬性包裝器將自動確保密切關注該屬性,以便重要的更改將重新加載使用它的任何視圖。這也意味著數據必須在別處創建,然后發送到您的視圖中。
② 如何使用 @ObservedObject 管理來自外部對象的狀態?
- 在使用觀察對象時,我們需要處理三個關鍵的事情:ObservableObject 協議與某種可以存儲數據的類一起使用,@ObservedObject 屬性包裝器用于在視圖中存儲可觀察對象實例,以及 @ObservedObject 屬性包裝器已發布的屬性包裝器被添加到觀察對象內的任何屬性,這些屬性在視圖更改時應導致視圖更新。
- 需要注意的是,僅將 @ObservedObject 用于從其它地方傳入的視圖,不應該使用這個屬性包裝器來創建一個可觀察對象的初始實例,這就是 @StateObject 的用途。如下所示,是一個符合 ObservableObject 的 UserProgress 類:
- 不過你可能會說,這看起來不像很多代碼,但那是因為 SwiftUI 為我們做了大量的工作,有兩點很重要:
-
- ObservableObject 一致性允許在視圖中使用此類的實例,以便在發生重要更改時,視圖將重新加載;
-
- @Published 屬性包裝器告訴 SwiftUI,對 score 的更改應觸發視圖重新加載。
- 我們可以使用 UserProgress 類和這樣的代碼:
- 如您所見,除了使用帶有進度的 @ObservedObject 屬性包裝器外,其他一切或多或少看起來都一樣,SwiftUI 為我們處理了所有的實現細節。但是,有一個重要區別:progress 屬性未聲明為私有,這是因為綁定對象可以被多個視圖使用,所以公開共享它是很常見的。
- 需要注意,請不要使用 @ObservedObject 創建對象的實例,如果這是想要做的,請改用 @StateObject。
四、什么是 @EnvironmentObject?
- 我們已經看到 @State 如何為一個類型聲明簡單的屬性,當它改變時會自動導致視圖刷新,以及@ObservedObject 如何為一個外部類型聲明一個屬性,當它改變時可能會也可能不會導致視圖刷新,這兩者都必須由視圖設置,但 @ObservedObject 可能與其它視圖共享。
- 還有另一種類型的屬性包裝器可供使用,它是 @EnvironmentObject,這是一個通過應用程序本身提供給視圖的值,它是每個視圖都可以讀取的共享數據。因此,如果應用程序有一些所有視圖都需要讀取的重要模型數據,可以將它從一個視圖傳遞到另一個視圖,或者只是將其放入每個視圖都可以即時訪問它的環境中。
- 當需要在應用程序周圍傳遞大量數據時,@EnvironmentObject 將帶來巨大的便利,因為所有的視圖都指向同一個模型,如果一個視圖改變了模型,所有的視圖都會立即更新,沒有讓應用程序的不同部分出現不同步的風險。
- SwiftUI 的 @EnvironmentObject 屬性包裝器,可以創建依賴于共享數據的視圖,通常跨整個 SwiftUI 應用程序。 例如,如果創建一個將在應用程序的許多部分共享的用戶,則應使用@EnvironmentObject。例如,我們可能有一個像這樣的 Order 類:
- 這符合 ObservableObject,這意味著可以將它與 @ObservedObject 或 @EnvironmentObject 一起使用。在這種情況下,我們可能會創建一個使用@EnvironmentObject 的視圖,如下所示:
- 請注意 order 屬性是如何沒有給出默認值的,通過使用 @EnvironmentObject,我們說該值將由 SwiftUI 環境提供,而不是由該視圖顯式創建。
- @EnvironmentObject 與@ObservedObject 有很多共同點:兩者都必須引用一個符合 ObservableObject 的類,兩者都可以在多個視圖之間共享,并且都將在發生重大變化時更新任何正在觀察的視圖。但是,@EnvironmentObject 特指此對象將從某個外部實體提供,而不是由當前視圖創建或專門傳入。
- 實際上,想象一下如果您有視圖 A,并且視圖 A 有一些視圖 E 想要的數據,使用@ObservedObject 視圖 A 需要將對象交給視圖 B,視圖 B 將把它交給視圖 C,然后是視圖 D,最后是視圖 E,所有中間視圖都需要發送對象,即使它們實際上并沒有需要它。
- 使用 @EnvironmentObject 時,視圖 A 可以創建一個對象并將其放入環境中;然后,其中的任何視圖都可以隨時通過請求訪問該環境對象,而不必顯式傳遞它,這樣會使我們的代碼更簡單。
- 當顯示使用 @EnvironmentObject 的視圖時,SwiftUI 將立即在環境中搜索正確類型的對象,如果找不到這樣的對象,即如果你忘記把它放在環境中,那么你的應用程序將立即崩潰。當您使用 @EnvironmentObject 時,實際上是在保證對象將在需要時存在于環境中,有點像使用隱式解包選項。
五、三者區分
- @State 用于屬于單個視圖的簡單屬性,它們通常應標記為私有。
- @ObservedObject 用于可能屬于多個視圖的復雜屬性,大多數情況下,如果使用的是引用類型,那么應該為它使用 @ObservedObject。
- 對使用的每個可觀察對象使用 @StateObject 一次,無論代碼的哪個部分負責創建它。
- @EnvironmentObject 用于在應用程序中其它地方創建的屬性,例如共享數據。
總結
以上是生活随笔為你收集整理的SwiftUI之深入解析@StateObject、@ObservedObject和@EnvironmentObject的联系和区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Swift之字符串String的常规操作
- 下一篇: SwiftUI之如何使用@Environ