为 Swift 提供并发支持的 Repository 模式 支持库。
适用于 iOS 或任何 Swift 项目。
该库提供使用 Swift 并发的远程和本地缓存抽象和观察。
根据以下 5 项策略创建。
以下是使用此库的 Repository 模式的类结构。
以下是使用 LoadingState
的屏幕显示示例。
作为 Swift Package Manager 安装,将 *.*.*
替换为最新的 tag。
dependencies: [
.package(url: "https://github.com/KazaKago/StoreFlowable.swift.git", from: "*.*.*"),
],
您只需实现以下 2 项内容
首先,创建一个继承自 Cacher<PARAM: Hashable, DATA>
的类。
将您想用作参数的类型放入 <PARAM: Hashable>
中。如果您不需要参数,放入 UnitHash
。
class UserCacher: Cacher<UserId, UserData> {
static let shared = UserCacher()
private override init() {}
}
Cacher<PARAM: Hashable, DATA>
需要在 Singleton 模式中使用。
接下来,创建一个实现 Fetcher
的类。
将您想用作 Data 的类型放入 DATA
associatedtype 中。
以下是一个示例。
struct UserFetcher : Fetcher {
typealias PARAM = UserId
typealias DATA = UserData
private let userApi = UserApi()
func fetch(param: UserId) async throws -> UserData {
userApi.fetch(userId: param)
}
}
您需要准备 API 访问类。
在本例中,是 UserApi
类。
之后,您可以从 AnyStoreFlowable.from()
方法获取 StoreFlowable<DATA>
类。
在获取/更新数据时,请务必通过创建的 StoreFlowable<DATA>
类。
let userFlowable: AnyStoreFlowable<UserData> = AnyStoreFlowable.from(cacher: userCacher, fetcher: userFetcher, param: userId)
let userStateSequence: LoadingStateSequence<UserData> = userFlowable.publish()
您可以使用 publish()
方法以 LoadingStateSequence<DATA>
(与 AsyncSequence<LoadingState<DATA>>
相同)的形式获取数据。
LoadingState
是一个保存原始数据的 enum。
您可以通过 for-in AsyncSequence
来观察数据。
并使用 doAction()
方法或 switch
语句对数据状态进行分支。
for await userState in userStateSequence {
userState.doAction(
onLoading: { (content: UserData?) in
...
},
onCompleted: { (content: UserData, _, _) in
...
},
onError: { (error: Error) in
...
}
)
}
有关详细信息,请参阅 example module。此模块作为一个 Android 应用运行。
请参阅 GithubMetaCacher + GithubMetaFetcher 或 GithubUserCacher + GithubUserFetcher。
此示例访问 Github API。
如果您不需要值流和 LoadingState
枚举,您可以使用 requireData()
或 getData()
。
如果不存在有效的缓存,并且获取新数据失败,requireData()
会抛出一个 Error。
getData()
返回 nil 而不是 Error。
public extension StoreFlowable {
func getData(from: GettingFrom = .both) async -> DATA?
func requireData(from: GettingFrom = .both) async throws -> DATA
}
GettingFrom
参数指定从何处获取数据。
public enum GettingFrom {
// Gets a combination of valid cache and remote. (Default behavior)
case both
// Gets only remotely.
case origin
// Gets only locally.
case cache
}
但是,仅对于一次性数据获取使用 requireData()
或 getData()
,如果可能,请考虑使用 publish()
。
如果您想忽略缓存并获取新数据,请将 forceRefresh
参数添加到 publish()
。
public extension StoreFlowable {
func publish(forceRefresh: Bool = false) -> LoadingStateSequence<DATA>
}
如果您已经在观察 Publisher
,您可以使用 refresh()
。
public protocol StoreFlowable {
func refresh() async
}
如果您想验证本地缓存是否有效,请使用 validate()
。
如果无效,则从远程获取新数据。
public protocol StoreFlowable {
func validate() async
}
如果您想更新本地缓存,请使用 update()
方法。
Publisher
观察者将被通知。
public protocol StoreFlowable {
func update(newData: DATA?) async
}
使用 mapContent(transform)
来转换 LoadingStateSequence<DATA>
中的内容。
let state: LoadingStateSequence<Int> = ...
let mappedState: LoadingStateSequence<String> = state.mapContent { value: Int in
value.toString()
}
使用 combineState(state, transform)
来组合多个 LoadingStateSequence<DATA>
。
let state1: LoadingStateSequence<Int> = ...
let state2: LoadingStateSequence<Int> = ...
let combinedState: LoadingStateSequence<Int> = state1.combineState(state2) { value1: Int, value2: Int in
value1 + value2
}
您可以轻松设置缓存过期时间。 在您的 Cacher<PARAM: Hashable, DATA>
类中重写 expireSeconds 变量。 默认值为 TimeInterval.infinity
(= 不会过期)。
class UserCacher: Cacher<UserId, UserData> {
static let shared = UserCacher()
private override init() {}
override var expireSeconds: TimeInterval {
get { TimeInterval(60 * 30) } // expiration time is 30 minutes.
}
}
如果您想使缓存的数据持久化,请重写您的 Cacher<PARAM: Hashable, DATA>
类的方法。
class UserCacher: Cacher<UserId, UserData> {
static let shared = UserCacher()
private override init() {}
override var expireSeconds: TimeInterval {
get { TimeInterval(60 * 30) } // expiration time is 30 minutes.
}
// Save the data for each parameter in any store.
override func saveData(data: GithubMeta?, param: UnitHash) async {
...
}
// Get the data from the store for each parameter.
override func loadData(param: UnitHash) async -> GithubMeta? {
...
}
// Save the epoch time for each parameter to manage the expiration time.
// If there is no expiration time, no override is needed.
override func saveDataCachedAt(epochSeconds: Double, param: UnitHash) async {
...
}
// Get the date for managing the expiration time for each parameter.
// If there is no expiration time, no override is needed.
override func loadDataCachedAt(param: UnitHash) async -> Double? {
...
}
}
此库包含分页支持。
继承 PaginationCacher<PARAM: Hashable, DATA>
& PaginationFetcher
而不是 Cacher<PARAM: Hashable, DATA>
& Fetcher
.
以下是一个示例。
class UserListCacher: PaginationCacher<UnitHash, UserData> {
static let shared = UserListCacher()
private override init() {}
}
struct UserListFetcher : PaginationFetcher {
typealias PARAM = UnitHash
typealias DATA = UserData
private let userListApi = UserListApi()
func fetch(param: UnitHash) async throws -> PaginationFetcher.Result<UserData> {
let fetched = userListApi.fetch(pageToken: nil)
return PaginationFetcher.Result(data: fetched.data, nextKey: fetched.nextPageToken)
}
func fetchNext(nextKey: String, param: UnitHash) async throws -> PaginationFetcher.Result<UserData> {
let fetched = userListApi.fetch(pageToken: nextKey)
return PaginationFetcher.Result(data: fetched.data, nextKey: fetched.nextPageToken)
}
}
您需要额外实现 fetchNext(nextKey: String, param: PARAM)
。
然后,您可以从 onCompleted {}
的 next
参数中获取额外加载的状态。
let userFlowable = AnyStoreFlowable.from(cacher: userListCacher, fetcher: userListFetcher)
for await loadingState in userFlowable.publish() {
loadingState.doAction(
onLoading: { (content: UserData?) in
// Whole (Initial) data loading.
},
onCompleted: { (content: UserData, next: AdditionalLoadingState, _) in
// Whole (Initial) data loading completed.
next.doAction(
onFixed: { (canRequestAdditionalData: Bool) in
// No additional processing.
},
onLoading: {
// Additional data loading.
},
onError: { (error: Error) in
// Additional loading error.
}
)
},
onError: { (error: Error) in
// Whole (Initial) data loading error.
}
)
}
要在 UITableView
中显示,请使用差异更新功能。另请参阅 UITableViewDiffableDataSource
。
您可以使用 requestNextData()
方法请求用于分页的额外数据。
public extension PaginationStoreFlowable {
func requestNextData(continueWhenError: Bool = true) async
}
GithubOrgsCacher + GithubOrgsFetcher 或 GithubReposCacher + GithubReposFetcher 类在 example module 中实现了分页。
此项目根据 Apache-2.0 License 获得许可 - 有关详细信息,请参阅 LICENSE 文件。