SwiftUI - MVVM之ViewModel
什么是ViewModal
ViewModal是View和數據的中間層。ViewModel是視圖和數據之間的一層。 ViewModel通常使用service objects來獲取數據,對其進行格式化后向View提供格式化的數據。
蘋果什么時候開始推動MVVM
當蘋果將ObservableObject協議移至Combine框架時,蘋果公司開始推廣MVVM模式。讓我們看一下ObservableObject協議,以了解發生了什么。
/// A type of object with a publisher that emits before the object has changed. public protocol ObservableObject : AnyObject { /// The type of publisher that emits before the object has changed. associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never /// A publisher that emits before the object has changed. var objectWillChange: Self.ObjectWillChangePublisher { get } }
ObservableObject協議具有唯一的要求,即在對象更改之前發出的發布者。讓我們編寫第一個符合ObservableObject協議的ViewModel。
final class PostsViewModel: ObservableObject { let objectWillChange = PassthroughSubject<Void, Never>() private (set) var posts: [Post] = [] func fetch() { // fetch posts objectWillChange.send() // assign new data to the posts variable } }
在這里,我們有ViewModel來獲取帖子,將它們存儲在變量中,並通過objectWillChange發布者發出通知。讓我們看一下使用此ViewModel的示例ViewController。
final class PostsViewController: UIViewController { let viewModel: PostsViewModel override func viewDidLoad() { super.viewDidLoad() bindViewModel() viewModel.fetch() } private func bindViewModel() { viewModel.objectWillChange.sink { [weak self] in guard let self = self else { return } self.renderPosts(self.viewModel.posts) } } }
如您在上面的示例中看到的,我們有PostsViewController,它開始觀察ViewModel中的更改,然后要求ViewModel提取數據。一旦ViewModel提取數據,它就會發出,並且ViewController調用renderPosts函數,該函數顯示下載的帖子。
Published property wrapper
我們可以使用@Published屬性包裝器進行進一步操作。 @Published屬性包裝器允許我們將發布者包裝任何屬性,只要屬性更改,發布者就會發出當前值。
final class PostsViewModel: ObservableObject { @Published private(set) var posts: [Post] = [] func fetch() { // fetch posts and assign them to `posts` variable } }
正如您在上面的示例中看到的那樣,我們不需要手動將值發送給objectWillChange發布者,這是Swift編譯器合成的所有工作。並且我們可以保持PostsViewController的相同實現。
如前所述,@ Published屬性包裝器將我們的屬性與發布者包裝在一起。讓我們看看如何在PostsViewController中使用它
final class PostsViewController: UIViewController { let viewModel: PostsViewModel override func viewDidLoad() { super.viewDidLoad() bindViewModel() viewModel.fetch() } private func bindViewModel() { viewModel.$posts.sink { [weak self] posts in self?.renderPosts(posts) } } }
在這里,我們有一個PostsViewController的重構版本。請看一下我們如何更改bindViewModel函數。它現在訂閱$ posts,並且僅當特定屬性更改時,它才允許我們更新視圖