First, create a class that inherits Cacher<PARAM: Hashable, DATA>. Put the type you want to use as a param in <PARAM: Hashable>. If you don’t need the param, put in the UnitHash.
class UserCacher: Cacher<UserId, UserData> {
static let shared = UserCacher()
private override init() {}
}
GettingFrom parameter specifies where to get the data.
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
}
let state1: LoadingStateSequence<Int> = ...
let state2: LoadingStateSequence<Int> = ...
let combinedState: LoadingStateSequence<Int> = state1.combineState(state2) { value1: Int, value2: Int in
value1 + value2
}
Manage Cache
Manage cache expire time
You can easily set the cache expiration time. Override expireSeconds variable in your Cacher<PARAM: Hashable, DATA> class.
The default value is TimeInterval.infinity (= will NOT expire).
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.
}
}
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? {
...
}
}
StoreFlowable.swift
Repository pattern support library for Swift with Concurrency.
Available for iOS or any Swift projects.
Related projects
Overview
This library provides remote and local cache abstraction and observation with Swift Concurrency.
Created according to the following 5 policies.
The following is the class structure of Repository pattern using this library.
The following is an example of screen display using
LoadingState
.Install
Install as Swift Package Manager exchanging
*.*.*
for the latest tag.Get started
There are only 2 things you have to implement:
1. Create a class to manage the in-app cache
First, create a class that inherits
Cacher<PARAM: Hashable, DATA>
.Put the type you want to use as a param in
<PARAM: Hashable>
. If you don’t need the param, put in theUnitHash
.Cacher<PARAM: Hashable, DATA>
needs to be used in Singleton pattern.2. Create a class to get data from origin server
Next, create a class that implements
Fetcher
.Put the type you want to use as a Data in
DATA
associatedtype.An example is shown below.
You need to prepare the API access class.
In this case,
UserApi
classe.3. Build StoreFlowable from Cacher & Fetcher class
After that, you can get the
StoreFlowable<DATA>
class from theAnyStoreFlowable.from()
method.Be sure to go through the created
StoreFlowable<DATA>
class when getting / updating data.You can get the data in the form of
LoadingStateSequence<DATA>
(Same asAsyncSequence<LoadingState<DATA>>
) by using thepublish()
method.LoadingState
is a enum that holds raw data.4. Subscribe
FlowLoadingState<DATA>
You can observe the data by for-in
AsyncSequence
.and branch the data state with
doAction()
method orswitch
statement.Example
Refer to the example module for details. This module works as an Android app.
See GithubMetaCacher + GithubMetaFetcher or GithubUserCacher + GithubUserFetcher.
This example accesses the Github API.
Other usage of
StoreFlowable
classGet data without LoadingState enum
If you don’t need value flow and
LoadingState
enum, you can userequireData()
orgetData()
.requireData()
throws an Error if there is no valid cache and fails to get new data.getData()
returns nil instead of Error.GettingFrom
parameter specifies where to get the data.However, use
requireData()
orgetData()
only for one-shot data acquisition, and consider usingpublish()
if possible.Refresh data
If you want to ignore the cache and get new data, add
forceRefresh
parameter topublish()
.Or you can use
refresh()
if you are already observing thePublisher
.Validate cache data
Use
validate()
if you want to verify that the local cache is valid.If invalid, get new data remotely.
Update cache data
If you want to update the local cache, use the
update()
method.Publisher
observers will be notified.LoadingStateSequence<DATA>
operatorsMap
LoadingStateSequence<DATA>
Use
mapContent(transform)
to transform content inLoadingStateSequence<DATA>
.Combine multiple
LoadingStateSequence<DATA>
Use
combineState(state, transform)
to combine multipleLoadingStateSequence<DATA>
.Manage Cache
Manage cache expire time
You can easily set the cache expiration time. Override expireSeconds variable in your
Cacher<PARAM: Hashable, DATA>
class. The default value isTimeInterval.infinity
(= will NOT expire).Persist data
If you want to make the cached data persistent, override the method of your
Cacher<PARAM: Hashable, DATA>
class.Pagination support
This library includes Pagination support.
Inherit
PaginationCacher<PARAM: Hashable, DATA>
&PaginationFetcher
instead ofCacher<PARAM: Hashable, DATA>
&Fetcher
.An example is shown below.
You need to additionally implements
fetchNext(nextKey: String, param: PARAM)
.And then, You can get the state of additional loading from the
next
parameter ofonCompleted {}
.To display in the
UITableView
, Please use the difference update function. See alsoUITableViewDiffableDataSource
.Request additional data
You can request additional data for paginating using the
requestNextData()
method.Pagination Example
The GithubOrgsCacher + GithubOrgsFetcher or GithubReposCacher + GithubReposFetcher classes in example module implement pagination.
License
This project is licensed under the Apache-2.0 License - see the LICENSE file for details.