對沸點頁面仿寫的補充-Combine
# 前言
本文就是 上篇 文章結尾所提到的替換 RxSwift
。
本文篇幅也會太長,涉及內容也僅僅是 Combine
的使用,您完全可以將此 demo
當入手 Combine
的上手實驗。文末會附上 Combine
學習的網址,同樣有文中原始碼附上。
老傳統了,看圖吧
# 網路層替換
新增對應的 Moya/Combine
後,對網路層採用 網路補充 中的封裝方式,同樣提供 request()
, memoryCacheIn(_)
,onStorage(_:atKeyPath:onDisk:)
方法。現將快取相關的 extension
抽離到 Moya+XTCache.swift
中,封裝如下:
```Swift extension TargetType {
public func request() -> AnyPublisher<Response, MoyaError> {
xtProvider.requestPublisher(.target(self))
}
/// 使用時間快取策略, 記憶體中有資料就不請求網路
public func memoryCacheIn(_ seconds: TimeInterval = 180) -> TargetOnMemoryCache {
TargetOnMemoryCache(target: self, cacheTime: seconds)
}
/// 讀取磁碟快取, 一般用於啟動時先載入資料, 而後真正的讀取網路資料
public func onStorage<T: Codable>(_ type: T.Type, atKeyPath keyPath: String? = nil, onDisk: ((T) -> ())?) -> TargetOnDiskStorage<T> {
let diskStore: TargetOnDiskStorage<T> = .init(target: self, keyPath: keyPath)
if let storage = diskStore.readDiskStorage(type) { onDisk?(storage) }
return diskStore
}
} ```
對於 memoryCache
的處理沒有采用,extension AnyPublisher {}
的方式,如下:
```Swift extension AnyPublisher {
func request() -> AnyPublisher<Response, MoyaError> where Output == (TargetType, TimeInterval), Failure == MoyaError {
flatMap { tuple -> AnyPublisher<Response, MoyaError> in
let target = tuple.0
let cacheKey = target.cacheKey
if let response = cachedResponse(for: cacheKey) {
return CurrentValueSubject(response).eraseToAnyPublisher()
}
return target.request().map { response -> Response in
kMemoryStroage.setObject(response, forKey: cacheKey, expiry: .seconds(seconds))
return response
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
} ```
使用 CurrentValueSubject
保證在被訂閱時是有值的,具體的 RxSwift
與 Combine
對照表見文末 補充 部分。
# ViewModel 替換
Combine
的 Publiser
指定了 Failure
,因此對於我們在 RxSwift
中不會發送 error
的 Observable
來說,可以清晰的定義為 AnyPublisher<SendType, Never>
型別,如:
```Swift
// RxSwift
var moreData: Observable
// Combine
var moreData: AnyPublisher
用 Combine
的 PassthroughSubject<SendType, Never>
替換 RxSwift
的 PublishSubject<SendType>
。
這裡要注意的一點就是 Combine
的 flatMap
操作符並不如 RxSwift
中那麼好用,如下圖展示錯誤所示:
AnyPublisher<XTListResultModel, MoyaError>
在 iOS 13
版本不支援轉換為 AnyPublisher<Result<XTListResultModel, Error>, Never>
,iOS 14
之後才能使用,這一部分我們使用 map
替換,如下圖:
# 補充
1. Combine 學習網址:
建議順序閱讀
2. Combine 補充
receive(on: main)
和 subscribe(on: main)
的區別。receive(on: main)
保證 .sink {}
中的程式碼是在 main thread
中執行。 具體可參考文章 subscribe-revcive
3. 自定義 Publisher
在 demo 的 TextureDemoViewController.swift
中有一個自定義的 JsonDataPublisher
。同樣在 Moya/Combine
和 Nuke/Combine
中也有自定義的 Publisher
可供學習。
4. 移除 Kingfisher,SnapKit
demo 的 MOON
分支移除了 SnapKit
理由是用的而地方太少,使用手寫約束佈局替換
```Swift // SnapKit button.snp.makeConstraints { make in make.left.equalToSuperview() make.top.equalToSuperview() make.bottom.equalToSuperview() make.width.equalToSuperview().dividedBy(CGFloat(count)) }
// AutoLayout button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.leftAnchor.constraint(equalTo: previousButton?.rightAnchor ?? self.leftAnchor), button.topAnchor.constraint(equalTo: self.topAnchor), button.bottomAnchor.constraint(equalTo: self.bottomAnchor), button.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/CGFloat(count)) ]) ```
使用 Nuke
替換 Kingfisher
純個人興趣。更多 Nuke
說明,參照 Nuke-Doc 與 Nuke-demo。
感謝您的閱讀。