在App开发中,数据层往往会做本地缓存,以便在网络不好的情况下能够先显示本地缓存数据。接下来将讲解到如何设计本地数据源中间件来实现业务数据的读取。在讲解之前我们会先了解一下简单泛型在Swift中的应用,结合associated types, 你也可以在协议中使用泛型。
在介绍之前,我刚提到我想要对泛型数据存储在本地,然后还需要一个远程数据接口API提供数据以便在本地数据没有的情况下调用。假设我需要用户信息数据:1
2
3
4
5
6
7let localDataStore = UserDataStore()
let remoteDataStore = UserApi()
let dataStore = CacheBackedDataStore(localDataStore, remoteDataStore)
dataStore.fetch(userID) { result in
// handle result
}
以上为大致逻辑简写,继续拆解CacheBackedDataStore:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class CacheBackedDataStore {
let localStore: LocalStore
let remoteStore: RemoteStore
func fetch(_ identifier: IdentifierType, completion: @escaping Result<T, Error>) {
localStore.fetchObject(identifier) { result in
if let result = try? result.get() {
completion(.success(result))
} else {
remoteStore.fetchObject(identifier) { result in
if let result = try? result.get() {
completion(.success(result))
} else {
// extract error and forward to the completion handler
}
}
}
}
}
}
注意到Result<T, Error>, T 为泛型的一个占位符。可代表任何对象。讲解泛型之前,回顾一下在Swift中,我们定义一个字符串数组。一般有两种方式:1
2let words1 = [String]()
let words2 = Array<String>()
第二种方式,我们留意到1
2
3struct Array<T> {
// implementation code
}
T即代表我们将要注入的类型。我们再来看一串代码:1
2
3
4
5struct SpecializedPrinter<T> {
func print(_ object: T) {
print(object)
}
}
我们定义了一个SpecializedPrinter泛型结构体,print方法使用了泛型参数。在使用的时候,如果我们传入了字符串类型,那么输出打印的就是字符串。1
2
3let printer = SpecializedPrinter<String>()
printer.print("Hello!") // this is fine
printer.print(10) // this is not okay since T for this printer is String, not Int
现在你已经了解了泛型,我们接下来对CacheBackedDataStore进行改进:1
2
3
4
5
6
7
8struct CacheBackedDataSource<T> {
let localStore: LocalStore
let remoteStore: RemoteStore
func find(_ objectID: String, completion: @escaping (Result<T?, Error>) -> Void) {
}
}
之前的代码,我们localStore,和remoteStore不能够写死,需要制定一套标准。即localStore可以做什么,remoteStore可以做什么。进而继续改进:1
2
3
4
5
6
7
8protocol LocalStore {
func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void)
func persist(_ object: T)
}
protocol RemoteStore {
func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void)
}
当改成上述代码的时候,这个时候编译器会报错,在协议中,泛型参数T没有声明!接下来我们需要在协议中使用泛型机制。这个时候就用到AssociateType进行类型关联。1
2
3
4
5
6
7
8
9
10
11
12protocol LocalStore {
associatedtype StoredObject
func find(_ objectID: String, completion: @escaping (Result<StoredObject, Error>) -> Void)
func persist(_ object: StoredObject)
}
protocol RemoteStore {
associatedtype TargetObject:Decodable //为了约束TargetObject要符合Decodable协议规则
func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void)
}
接下来我们定义ArrayBackedUserStore的业务类实现LocalStore协议,RemoteUserStore业务类实现 RemoteStore 协议:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct ArrayBackedUserStore: LocalStore {
func find(_ objectID: String, completion: @escaping (Result<User, Error>) -> Void) {
//本地数据读取
}
func persist(_ object: User) {
//本地持久化操作
}
}
struct RemoteUserStore: RemoteStore {
typealias TargetObject = User //如果所使用的对象User不是Decodable协议,则会报错
func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void) {
}
}
回到上述CacheBackedDataSource
继续改进代码:1
2
3
4
5
6
7
8struct CacheBackedDataSource<Local:LocalStore,Remote:RemoteStore> {
let localStore: Local
let remoteStore: Remote
func find(_ objectID: String, completion: @escaping (Result<Local.StoredObject, Error>) -> Void) {
}
}
这样就编译正常了。此时CacheBackedDataSource含有两个泛型参数,而Result<T, Error> 改成了Result<Local.StoredObject, Error>。其实用Result<Remote.TargetObject, Error>也是可以,为了保证local和remote关联的类型为同一对象类型,我们继续对CacheBackedDataSource增加约束:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23struct CacheBackedDataSource<Local:LocalStore,Remote:RemoteStore> where Local.StoredObject == Remote.TargetObject {
let localStore: Local
let remoteStore: Remote
func find(_ objectID: String, completion: @escaping (Result<Local.StoredObject, Error>) -> Void) {
localStore.find(objectID) { result in
do {
let object = try result.get()
completion(.success(object))
} catch {
self.remoteStore.find(objectID) { result in
do {
let object = try result.get()
self.localStore.persist(object)
completion(.success(object))
} catch {
completion(.failure(error))
}
}
}
}
}
}
最后调用的时候:1
2
3
4
5
6let localUserStore = ArrayBackedUserStore()
let remoteUserStore = RemoteUserStore()
let cache = CacheBackedDataSource(localStore: localUserStore, remoteStore: remoteUserStore)
cache.find("someObjectId") { (result: Result<User, Error>) in
}
通过这篇文章,你已经知道使用