前几篇博文对“幽灵架构”做了用法的介绍和相关技术点的补充,本文是一篇总结性质的文章,分析该架构的适用场景和限制,首先让我们回顾一下iOS开发的MVC模式,参考斯坦福公开课里Paul老爷子的讲解,如下图所示:
在MVC模式下Model和View是不能直接通信的,在“幽灵架构”体系中Model和View依旧不能直接通信,在传统的MVC中,这种通信的阻隔很多时候是因为在没有得到Model和View实体的情况下无法完成二者的数据绑定,在过去你可能想过在View中写一个方法来接受一个模型,然后写具体的实现,在没有泛型的帮助下只能用方法的重载来做,没有协议扩展的情况下需要在父类和写重复的代码中做选择。
在“幽灵架构”体系中,通过ModelType和ViewType可以在不借助控制器的帮助下实现二者的逻辑绑定,依旧是使用了方法传值的用法,Controller起到了“开关”的作用,依靠泛型和协议对数据类型进行了约束,并且避免了重复代码,不需要在意数据源的同构和异构。
我们需要在Controller中创建Model和View的实例,并且和MVC一样可以对Model和View做处理,但是Model和View之间的逻辑关系是定义在View的getData中的,比如Demo中Event的Cell会被标注为红色,如果要通过修改Controller的标志位来影响Model和View之间的逻辑,比如Demo中在Controller中加入一个开关,点击后会互换Event和Festival的背景色,这就属于Controller中引入了标志位来影响View和Model的逻辑,此时如果要继续维持“幽灵架构”的体系,那么需要重载giveData和getData方法,传入Controller中的标志位。首先给getData增加默认实现,因为在View的代码中我们只想重写需要的重载的方法:
//视图使用的协议
protocol ViewType{
func getData<M:ModelType>(model:M)
}
//数据使用的协议
protocol ModelType{
}
//定义默认方法giveData
extension ModelType{
func giveData<V:ViewType>(view:V){
view.getData(self)
}
}
extension ViewType{
func getData<M:ModelType>(model:M){
}
}
为了维持“幽灵架构”的灵活性,我们重写了giveData和getData,不是在具体的遵守者中重写,而是直接扩展协议:
extension ModelType where Self:HasDate{
func giveData<V:ViewType>(view:V,tag:Bool){
if let tableCell = view as? ShowedTableViewCell{
tableCell.getData(self,tag:tag)
}
}
}
extension ViewType where Self:ShowedTableViewCell{
func getData<M : ModelType>(model: M,tag:Bool){
}
}
现在不需要修改所有的Model,但是需要在View中重写新定义的方法:
func getData<M : ModelType>(model: M,tag:Bool) {
//这里不能写成guard let dateModel = model as? DateViewModel else{}令我有些意外
guard let dateModel = model as? HasDate else{
return
}
//处理相同属性
dateLabel.text = dateModel.date
//处理数据源异构
if let event = dateModel as? Event{
MixLabel.text = event.eventTitle
backgroundColor = tag ? UIColor.redColor():UIColor.whiteColor()
} else if let festival = dateModel as? Festival{
MixLabel.text = festival.festivalName
backgroundColor = !tag ? UIColor.redColor():UIColor.whiteColor()
}
}
这是协议扩展的另一个神奇之处:不会引入多余的状态,避免了“上帝类”的出现,如果你的工程中有其他的Model和View的绑定,不会受到任何影响。最后一步,修改Controller中的数据绑定:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
//调用重载的方法
dataList[indexPath.row].giveData(cell,tag:true)
return cell
}
现在尝试向tag传入true和false看看效果吧:
tag传入false
tag的值取决于Controller,从上面的例子中看出在Controller中切换View和Model的绑定逻辑是完全没问题的。之前讲过giveData方法是为了在异构数据源中避免Box类型的出现,提高运行效率,针对同构的数据源,我们只需要重载getData并在控制器中直接使用getData对数据进行捆绑就好了。
所以,我之所以在最初的文章中称“幽灵架构”为MV,其实并没有弱化控制器的职责,但是把控制器中有关于模型和视图逻辑绑定的代码移到了View中,并且集中在了getData一个方法中,便于维护,为控制器减压,所以其实这是一个“伪架构”。从上面的示例中可以看到,MVC和MVVM能做到的“幽灵架构”一样能做到,并且比MVC中控制器的代码更好维护,比MVVM少写了很多代码。“幽灵架构”今后的发展,需要经历时间的考验了。