转载

iOS架构学习 - VIP总结

感谢@叶孤城在近一段时间组织的斗鱼直播讲解代码的活动,一些开发技巧和工具让我受益匪浅,再次感谢。

昨天是由36氪的iOS Team Leader@罗琦aidenluo讲解项目的架构, 有种茅塞顿开的感觉。

所以就在这里总结一下学习到的知识点。

项目地址是: https://github.com/aidenluo177/GitHubber

1、架构比较

一直以来自己都是遵循 MVC 模式,这个应该是苹果首推的开发架构,按照这个模式开发APP实现功能问题不大,但是存在的问题也非常明显,后期项目比较大了的话,Controller会便得越来越臃肿。不利于需求的改动和维护、后面入职的小伙伴也会经常看得一头雾水。

后来出现了 MVVMVIPER . 具体是什么就不介绍了,可以谷歌一下。

MVVM一般都会结合RAC来使用,之前了解过RAC,函数式编程的思想在某些地方用的是很方便、比如监听一些事件。 用RAC可以很快写出紧凑的代码,而且封装的很好,思路也很清晰。但是RAC的学习成本在我看来应该是最大的,看到一些开源的项目,一般都是部分功能用RAC去实现, 这里 有个纯RAC的项目,不过是个人作品。团队开发如果要每个人都熟悉RAC,估计成本太高。

VIPER 还没有真正在项目中使用过,看了这介绍的DEMO,分层有点多,有种过度设计的感觉。

2、视频里的VIP架构

VIP就是 ViewControllerInteractionPresent ,看一下下面这张图

iOS架构学习 - VIP总结

首先数据的流向是单向的、职责都狠明确, 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里面都有初始化各个模块的工作,把 interactorpresnet 组织起来了

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)
}

}

从上面看出这简单的操作数据流向非常明确,模块与模块之间只有输入和输出,职责也非常明确,职责明确地好处就是调试方便、找问题可以集中到出问题的部分查找、节省时间。

至于单元测试,还不太清楚、之前写代码也没怎么写过单元测试,这块知识还是空白状态。

正文到此结束
Loading...