在现代 Swift 项目中,很流行一种形式叫做库房形式,英文是 Repository Pattern。这个形式主要用于构建数据层代码。依照一般的 App 层级区分,一般从上到下区分为 UI 层,事务层,数据层,那么库房形式的运用方位能够参阅下图:
能够看到库房运用在数据层,事务层经过接口来拜访库房。
不运用库房形式时的代码
为了研讨为什么要运用库房形式,咱们先看看不运用库房形式时咱们是怎样写代码的。一般打开一个界面,会发送网络恳求来获取这个界面所需的数据,这时会在 ViewController 写相似下面的代码:
func viewDidLoad() {
super.viewDidLoad()
requestData()
}
func requestData() {
API.request(xxxId: 12345) { result in
switch result {
case .success(let model):
handle(model)
DispatchQueue.main.async {
render(model)
}
case .failure(let error):
print(error.localizedString)
}
}
}
这种写法在小项目中是没有问题的,可是在稍具规划的项目中,就会对项目的扩展性,保护性,团队合作,开发效率有比较高的要求,这时分就更应该依据科学的软件规划准则来规划更好的架构。
上面的代码在稍具规划的项目中会有以下的缺陷:
- 数据拜访代码写在了 ViewController 中,无法测验
- ViewController 会过于臃肿,难以保护
- 假如数据拜访方法修正了,需求修正 ViewController 中的代码
稍具规划的项目一般会选用 MVVM 等架构,所以以上的代码会写在 ViewModel 中,来防止 ViewController 过分臃肿,可是仍是会有无法测验和修正数据拜访方法时改动比较大的问题。
运用库房有什么优点?
总的来说,便是能供给高层次的笼统,然后获得由于笼统带来的一系列优点。
库房形式供给了数据层的笼统,能够让你的事务代码只依靠一个简略的笼统接口就能够工作。这使得代码松耦合,事务代码不需求知道数据的详细获取和存储细节。
库房形式让代码能够测验,关于网络恳求和数据库读写的部分,能够完成一个供给测验数据的库房实例,这样就能够编写相应的测验代码。
下面就来看一看怎样运用库房形式。
规划库房接口
依照依靠倒转准则,数据拜访层作为架构中的一个整体,上层对象在调用数据拜访层时应该依靠接口,而不依靠于完成,这样数据拜访层的逻辑就能够灵敏的改变、替换,比方在网络恳求和本地数据之间切换,在不同的网络恳求协议之间切换等。
所以规划库房形式应该先界说接口,界说接口有两种方法,一种是依据详细的数据界说特定的库房接口,例如关于一个新闻列表的接口:
protocol NewsListRepository {
func readNewsList() async throws -> [News]
}
或者是依据不同的事务数据的拜访逻辑其实大同小异,能够规划统一的泛型接口:
protocol Repository {
associatedtype T
func query(with predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?) async throws -> [T]
func save(entity: T) async throws
func delete(entity: T) async throws
}
完成库房接口
咱们一般会依据数据的存放方法来界说不同的库房完成,比方关于网络恳求的数据,界说一种库房的完成,关于本地数据库中存放的数据界说一种库房的完成,也能够界说一种假数据库房来编写测验代码。
比方关于网络恳求的数据,能够界说一个如下的库房完成:
class DefaultNewsListRepository: NewsListRepository {
let remoteDataSource: NewsListRemoteAPI
init(remoteDataSource: NewsListRemoteDataSource) {
self.remoteDataSource = remoteDataSource
}
func readNewsList() async throws -> [News] {
return remoteDataSource.requestNewsList();
}
}
关于本地数据,能够规划一个如下的库房完成:
class DatabaseNewsListRepository: NewsListRepository {
let newsListDataStore: NewsListDataStore
init(newsListRemoteAPI: NewsListRemoteAPI) {
self.newsListRemoteAPI = newsListRemoteAPI
}
func readNewsList() async throws -> [News] {
return newsListRemoteAPI.requestNewsList();
}
}
为了编写测验代码,能够供给一个假数据的库房完成:
class FakeNewsListRepository: NewsListRepository {
func readNewsList() async throws -> [News] {
return [
News(),
News(),
News()
]
}
}
假如需求从接口恳求到数据后放入本地数据库缓存,然后从本地数据库中读取数据渲染在界面上,也能够用一个库房搞定。
class DefaultNewsListRepository: NewsListRepository {
let newsListRemoteAPI: NewsListRemoteAPI
let newsListDataStore: NewsListDataStore
init(newsListRemoteAPI: NewsListRemoteAPI, newsListDataStore: NewsListDataStore) {
self.newsListRemoteAPI = newsListRemoteAPI
self.newsListDataStore = newsListDataStore
}
func readNewsList() async throws -> [News] {
var newsList = newsListDataStore.readNewsList()
if newsList.count == 0 {
let news = newsListRemoteAPI.requestNewsList()
newsListDataStore.save(newsList)
newsList = news
}
return newsList
}
}
选择用哪个库房完成
在现代 App 项目中,一般会用 MVVM 等架构来安排代码。这里以 MVVM 为例,ViewModel 会依靠库房接口来存取数据。
class NewsListViewModel: ViewModel {
let newsListRepository: NewsListRepository
init(newsListRepository: NewsListRepositoy) {
self.newsListRepository = newsListRepository
}
}
为了进步代码的保护性和扩展性,最好运用依靠注入的方法来给 ViewModel 注入 Repository 的依靠,这样能够便利得替换库房的完成而不用修正 ViewModel 的代码。
能够在创立 ViewModel 时创立对应的库房对象,也能够运用依靠注入容器。
init(newsListRepository: NewsListRepository = DIContainer.shared.resolve(NewsListRepository.self)) {
self.newsListRepository = newsListRepository
}
处理数据源的改变
当遇到需求改变数据源的时分,例如本地数据库从 CoreData 切换到 SQLite 或 Realm。或者更换了网络库,从 NSURLSession 换成 Alamofire,这些状况库房形式就能发挥它的优势。无需修正事务方代码,只需求替换成一种新的库房完成即可。
还有另一种状况,便是关于同一个事务,后端协议改变了。假如运用了库房形式,也能够很便利的进行代码调整。
一般,事务层会有一个事务模型,比方关于用户的信息,在事务层界说了一套模型:
struct DomainUser {
let name: String
let age: Int
let nickname: String
}
本来经过接口回来的 json 字符串:
{
"name": "zhangsan",
"age": 20,
"nickname": "xiaozhang"
}
能够直接经过解析 JSON 然后构造出 DomainUser 对象,可是突然某一天后端说要技能调整,迁移到新的接口,新接口回来的结构和曾经不一样了。
假如没有用库房形式,事务方直接依靠详细的数据模型,假如接口结构调整了,那么一切的事务调用方的代码都要调整。
运用了库房形式,事务方依靠于库房,库房能够在获取到数据结构后将它转为事务方需求的数据模型,这样不管后端协议怎样改变,都能够仅在数据层增加一种新的库房完成,不需求改动事务方代码。这遵守了开放-关闭程序规划准则。
总结
在稍具规划的项目中运用库房形式,能够让代码笼统度更高,耦合度更低,便利扩展和保护,能够编写测验代码,在大型项目中,能够便利的完成数据源和数据结构的切换。库房形式也将数据存储的细节和程序的其它部分分脱离,使得责任更清晰。
库房形式也有一些缺陷:为了完成这一形式需求编写更多的代码,增加了代码复杂性。需求编写映射代码来讲数据映射为事务模型。假如是小型的项目就不需求运用库房形式了。
参阅资料
- iOS: Repository pattern in Swift
- Repository Pattern in Swift
- github.com/kudoleh/iOS…
- Repository pattern in Swift