在App开发中,数据层往往会做本地缓存,以便在网络不好的情况下能够先显示本地缓存数据。接下来将讲解到如何设计本地数据源中间件来实现业务数据的读取。在讲解之前我们会先了解一下简单泛型在Swift中的应用,结合associated types, 你也可以在协议中使用泛型。

在介绍之前,我刚提到我想要对泛型数据存储在本地,然后还需要一个远程数据接口API提供数据以便在本地数据没有的情况下调用。假设我需要用户信息数据:

1
2
3
4
5
6
7
let 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
20
class 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
2
let words1 = [String]()
let words2 = Array<String>()

第二种方式,我们留意到方式注明是字符串类型。我们查看Array的代码可以看到。
1
2
3
struct Array<T> {
// implementation code
}

T即代表我们将要注入的类型。我们再来看一串代码:
1
2
3
4
5
struct SpecializedPrinter<T> {
func print(_ object: T) {
print(object)
}
}

我们定义了一个SpecializedPrinter泛型结构体,print方法使用了泛型参数。在使用的时候,如果我们传入了字符串类型,那么输出打印的就是字符串。
1
2
3
let 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
8
struct 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
8
protocol 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
12
protocol 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
17
struct 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泛型结构体,其中两个变量localStore和remoteStore将会编译出错,提示“Protocol ‘LocalStore’ can only be used as a generic constraint because it has Self or associated type requirements” 意思是LocalStore协议里有associatedtype关联类型,只能够通过泛型约束来使用。
继续改进代码:
1
2
3
4
5
6
7
8
struct 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
23
struct 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
6
let localUserStore = ArrayBackedUserStore()
let remoteUserStore = RemoteUserStore()
let cache = CacheBackedDataSource(localStore: localUserStore, remoteStore: remoteUserStore)
cache.find("someObjectId") { (result: Result<User, Error>) in

}

通过这篇文章,你已经知道使用给对象定义泛型,使用泛型参数,以及在在协议中使用关联类型来约束参数类型。以及在对象中包含定义了关联的协议变量,需使用泛型参数来对协议变量进行约束。泛型的功能强大,可使组件抽象,复用。可帮助我们构建弹性组件。

评论