iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 28
0
自我挑戰組

30 天了解 Swift 的 Combine系列 第 28

30 天了解 Swift 的 Combine: [28] GitHub 專案分享: SwiftUI-Combine

在 GitHub 有許多開源的專案是值得我們學習的, 尤其是當接觸新事物的時候, SwiftUI-Combine 是一個使用 SwiftUI 及 Combine 的資料顯示樣板, 是新接觸的人可以有的不錯練習!

https://github.com/ra1028/SwiftUI-Combine

本篇文章將注重與 repo 內 Combine的部分.

ViewModel 的建立

// SearchUserViewModel.swift

import UIKit.UIImage
import Combine

final class SearchUserViewModel: ObservableObject {
    // 1
    @Published var name = "ra1028"

    @Published private(set) var users = [User]()

    @Published private(set) var userImages = [User: UIImage]()
    
    private var searchCancellable: Cancellable? {
        didSet { oldValue?.cancel() }
    }

    // 2
    func search() {...}

    func fetchImage(for user: User) {...}
}
  1. 建立上游事件流, @Pubulished 可以想成 CurrentValueSubject, 使用 private(set) 的方式限制 Subjcet.value 可用範圍
  2. 將詢問 GitHub 的網路細節組合

Publisher 的組合

 func search() {
     ...
        _ = URLSession.shared.dataTaskPublisher(for: request) // 1
            .map { $0.data } // 2
            .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
            .map { $0.items }
            .replaceError(with: []) // 3
            .receive(on: RunLoop.main)
            .assign(to: \.users, on: self)
    }
  1. 建立 dataTaskPublisher
  2. 取得 Data 與 Decodable, 由於主角是 Data, Decoder 要使用注入的方式使用
  3. 將 URLError 與 DecodeError 都 強制轉換為空陣列

如此一來, 就可以在 SwiftUI 內使用

SwiftUI 接收 事件流

import SwiftUI

struct SearchUserView: View {
    @ObservedObject var viewModel = SearchUserViewModel()

    var body: some View {
        NavigationView {
            VStack {
                SearchUserBar(text: $viewModel.name) { // 1
                    self.viewModel.search()
                }

                List(viewModel.users) { user in // 2
                    SearchUserRow(viewModel: self.viewModel, user: user) 
                        .onAppear { self.viewModel.fetchImage(for: user) }
                }
                }
                .navigationBarTitle(Text("Users"))
        }
    }
}

  1. 使用 two-way binding 的方式交給 TextField 設定 ViewModel.name
  2. 使用 單向接收通知的方式, 在 ViewModel.users 變動時(Suject.value)觸發 List 跟新

在 UIKit 與 非 Combine 的情形下, 要實作出同樣的功能時需要使用 UITableView.reloadData() 的方式注入 URLSession.dataTask. 使用 Combine 之後, 仿佛 FlowChart(控制流程圖)在程式碼現型一般, 是很好理解的 code!


上一篇
30 天了解 Swift 的 Combine: [27] 好書介紹 Combine: Asynchronous Programming with Swift
下一篇
30 天了解 Swift 的 Combine: [29] GitHub 專案分享: CombineRxSwiftPerformance
系列文
30 天了解 Swift 的 Combine30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言