iOS重构之面向协议编程实践

最近一段时间都在进行iOS客户端的重构,参考了许多iOS重构方面的资料,在重构的过程中也遇到一些困难,同时总结了不少经验,在这里和大家分享一下。这将会是一个系列的文章,每一篇文章都会从一个具体的、普遍性的问题出发,然后分析和解决这个问题。

插一句,软件开发本身是一件工程化的事情,虽然有一些理论上的指导和支持,但终归还是要从实际项目中出发,找到适合项目本身的最优解。我在这里提到的一些实践不一定适合每一个项目,甚至在某些情况下不适用,所以欢迎大家多多讨论交流,我们的终极目标都是写出具有可读性、可维护性、可拓展性的高质量代码。

问题:如何管理界面跳转的代码

回想一下你的项目中,大部分界面跳转的代码是写在哪里的?最简单方便的,直接写在ViewController中。这种方式很好理解,因为ViewController原生就提供了界面跳转的方法prsentVC/pushToVC。
随着项目逐渐复杂,你可能会发现,有一些VC可以从很多不同的VC跳转而来,如果按照原来的方式,就会有很多界面跳转代码的重复。于是我们可能会创建一个XXRouter的类,把跳转到XXViewController的代码提取出来,放到XXRouter中管理。

class XXRouter {
  var newVC:XXViewController {
    // 或者从Storyboard/Xib创建
    return XXViewController()
  }
  func presentXXVC(_ fromVC:UIViewController){
    let xxVC = newVC
    //XXViewController初始化
    vc.present(xxVC)
  }
}

于是,在其他需要跳转到XXViewController的VC中,只需要新建一个XXRouter,调用router.presentXXVC()就可以实现界面跳转,也做到了统一管理。

class MyViewController:UIViewController {
  fileprivate var router = XXRouter()
  func someFunc() {
    // do something 
    router.presentXXVC()
  }
}

这种方式已经能满足我们大部分的要求了,只不过每次都需要创建一个Router实例,当然也可以使用单例。
其实我们可以借助Swift的协议扩展Protocol Extension,可以把Router的实现变得更加直观和优雅

协议扩展Router模式

Swift相比其他OOP语言的一个比较大的特点就是面向协议Protocol Oriented,理论知识这里就不在赘述了,给一个苹果的官方文档的链接Protocol-Oriented Programming in Swift
下面就是我们的实践,这次我们使用protocol来处理我们的界面跳转的代码

protocol XXRouter {}
extension XXRouter {
  var newVC:XXViewController {
    // 或者从Storyboard/Xib创建
    return XXViewController()
  }
  func presentXXVC(_ fromVC:UIViewController){
    let xxVC = newVC
    //XXViewController初始化
    vc.present(xxVC)
  }
}

看起来和之前的实现几乎一模一样,但是在VC中我们就不需要创建XXRouter的实例,而是直接让ViewController实现我们的XXRouter

extension MyViewController:XXRouter{}
class MyViewController:UIViewController {
  func someFunc() {
    // do something 
    self.presentXXVC(self)
  }
}

这种方式还有一个好处就是可以很直观的看到界面间的跳转关系,尤其是一个界面可以跳转到不同界面的情况,比如

extension MyViewController:XXRouter,YYRouter,ZZRouter{}
class MyViewController:UIViewController{}

上面的代码几乎达到了代码即文档MyViewController这个界面,可以跳转到XX、YY和ZZ三个ViewController。
更进一步,借助extensionwhere条件扩展,我们还可以省略掉fromVC呢!

protocol XXRouter {}
extension XXRouter where Self:UIViewController {
  var newVC:XXViewController {
    // 或者从Storyboard/Xib创建
    return XXViewController()
  }
  
  func presentXXVC(){
    let xxVC = newVC
    // XXViewController初始化
    // 直接使用self,因为where指定当前扩展的是UIViewController
    self.present(xxVC)
  }
}

这种方式修改的成本很小,只是提取和挪动界面间跳转代码,几乎不会出现什么错误,用非常小的成本很大程度提高了项目代码的可读性和可维护性。目前我们项目中的界面跳转就是用这种方式进行了重构。

其他的跳转方案

最后,再简单分析一下其他两种界面跳转的解决方案

  1. Storyboard的segue跳转
    这种方式对于Storyboard内ViewController之间的简单跳转来说十分方便,不用写一行代码。而且.storyboard文件中把一个个分离的ViewController通过图的方式有机地连接在一起,清晰得展现出了项目内ViewController跳转的路径。
    不足:
    涉及到页面之间传值或者需要做额外初始化工作的ViewController,需要在代码中实现delegate,和直接代码跳转相比没有太大差别。
    对于需要额外初始化的ViewController,把一个跳转流程分离到两个地方实现,个人觉得更加不利于维护。
  2. 模仿前端的URL Router跳转,比如MGJRouter
    这种方式受Web URL跳转方式的启发,通过注册页面为URL Scheme的方式进行跳转,比较适合Hybird应用,给跳转网页和跳转ViewController一个统一的入口,便于维护
    不足:
    额外多了注册ViewController或实现Router Protocol的操作。
    侵入性大,需要在项目初期就使用这种方式,不利于重构。

这篇文章就写到这里啦,iOS重构的文章还有挖了几个坑,近期会填完!如果各位觉得本文对你有帮助的话,请点一个喜欢,谢谢~

推荐阅读更多精彩内容