感谢@叶孤城在近一段时间组织的斗鱼直播讲解代码的活动,一些开发技巧和工具让我受益匪浅,再次感谢。
昨天是由36氪的iOS Team Leader@罗琦aidenluo讲解项目的架构, 有种茅塞顿开的感觉。
所以就在这里总结一下学习到的知识点。
项目地址是: https://github.com/aidenluo177/GitHubber
1、架构比较
一直以来自己都是遵循 MVC
模式,这个应该是苹果首推的开发架构,按照这个模式开发APP实现功能问题不大,但是存在的问题也非常明显,后期项目比较大了的话,Controller会便得越来越臃肿。不利于需求的改动和维护、后面入职的小伙伴也会经常看得一头雾水。
后来出现了 MVVM
、 VIPER
. 具体是什么就不介绍了,可以谷歌一下。
MVVM一般都会结合RAC来使用,之前了解过RAC,函数式编程的思想在某些地方用的是很方便、比如监听一些事件。 用RAC可以很快写出紧凑的代码,而且封装的很好,思路也很清晰。但是RAC的学习成本在我看来应该是最大的,看到一些开源的项目,一般都是部分功能用RAC去实现, 这里 有个纯RAC的项目,不过是个人作品。团队开发如果要每个人都熟悉RAC,估计成本太高。
VIPER 还没有真正在项目中使用过,看了这介绍的DEMO,分层有点多,有种过度设计的感觉。
2、视频里的VIP架构
VIP就是 ViewController
、 Interaction
、 Present
,看一下下面这张图
首先数据的流向是单向的、职责都狠明确, Interaction
做了一些 Controller
的逻辑操作、 Presnet
主要做一些转换的工作,例如一些数据的处理显示,因为很多时候数据拉取回来,并不能马上使用,通常需要而外转换一层,最后再把处理完成的数据丢会给 Controller
做显示的工作。
之前写代码很多时候数据都需要双向交互,这样的确有不确定性,开发久了有可能就忘了约定好的数据交互方式,不知不觉按照自己的逻辑开发,但是这个单向的数据流可以避免这个问题,因为有且只有一个方法,每个模块有输入和输出。
视频中作者把这个输入和输出的接口定义成一个协议(也就是接口),很清楚指明了数据的流向,每个协议要做的事情,
protocol TrendingViewControllerToInteractorPipline {
func refreshData(request: TrendingDataRequest)
}
protocol TrendingInteractorToPresenterPipline {
func presentData(response: TrendingDataResponse)
}
protocol TrendingPresenterToViewControllerPipline: class {
func displayData(viewModel: TrendingDataViewModel)
}
每个模块里面用了 typealias
关键字重新命名了协议,为了区分不同的模块的协议方法,增加了可读性。
比如在ViewController里面:
typealias TrendingViewControllerInput = TrendingPresenterToViewControllerPipline
typealias TrendingViewControllerOutput = TrendingViewControllerToInteractorPipline
输入流只接受present的数据,方便present做好数据的转换之后,执行视图的渲染显示操作。
extension TrendingViewController: TrendingViewControllerInput {
func displayData(viewModel: TrendingDataViewModel) {
data.removeAll()
viewModel.list.forEach { data.append($0) }
refreshControl?.endRefreshing()
tableView.reloadData()
}
}
每个Controller里面都有初始化各个模块的工作,把 interactor
和 presnet
组织起来了
private func setupVIP() {
let interactor = TrendingInteractor()
let presenter = TrendingPresenter()
output = interactor
interactor.output = presenter
presenter.output = self
}
再来看看 Interactor
的工作,这里有一个请求的worker,做数据的刷新处理工作,至于怎么去请求可以不用知道,只管结果。然后把拉取回来的数据传递给 presnet
extension TrendingInteractor: TrendingInteractorInput {
func refreshData(request: TrendingDataRequest) {
worker.fetchTrendingData({ (data) -> Void in
self.output.presentData(data)
}) { (error) -> Void in
// handle error
}
}
}
Presnet
做了数据的转换工作、生成了viewModel丢回给Controller
extension TrendingPresenter: TrendingPresenterInput {
func presentData(response: TrendingDataResponse) {
//Transform etc...
let viewModel = TrendingDataViewModel(list: response.list.map {
var cellModel = TrendingDataViewCellModel()
cellModel.cellText = $0.name
cellModel.cellLink = $0.link
return cellModel
})
output.displayData(viewModel)
}
}
从上面看出这简单的操作数据流向非常明确,模块与模块之间只有输入和输出,职责也非常明确,职责明确地好处就是调试方便、找问题可以集中到出问题的部分查找、节省时间。
至于单元测试,还不太清楚、之前写代码也没怎么写过单元测试,这块知识还是空白状态。