随着业务增长,项目中的模块愈来愈多,而且这些模块进行相互的调用,使得它们交缠在一块儿,增长了维护成本,而且会下降开发效率。此时就须要对整个项目进行模块划分,将这些模块划分给多个开发人员(组)进行维护,而后在主工程中对这些模块进行调用。html
每一个模块独立存在,提供接口供其余模块调用。从而如何有效且解耦的进行模块间的调用成了重中之重。git
那么如何传递参数而且初始化对应模块的主窗口成为了主要问题。下面会介绍两种方案来解决这个问题。github
得益于Swift
中枚举能够传参的特性,咱们能够经过枚举来进行参数传递,而后添加方法来映射控制器。swift
在枚举中经过参数来肯定初始化控制器的数据,外部进行调用时能够很明确的知道须要传入哪些参数,同时还能传递很是规参数(Data
、UIImage
等)async
enum Scene { case targetA case targetB(data: Data) func transToViewController() -> UIViewController { switch self { case .targetA: return ViewControllerA() case let .targetB(data): return ViewControllerB(data: data) } } }
解决了UIViewController
映射的问题后,咱们接下来解决如何统一跳转方法。ide
项目中,基本上都会使用UINavigationController
来进行界面的跳转,主要涉及push
、pop
操做。此时简便的作法是扩展UIViewController
为其添加统一界面跳转方法。ui
extension UIViewController { func push(to scene: Scene, animated: Bool = true) { let scene = scene.transToViewController() DispatchQueue.main.async { [weak self] in self?.navigationController?.pushViewController(scene, animated: animated) } } func pop(toRoot: Bool = false, animated: Bool = true) { DispatchQueue.main.async { [weak self] in if toRoot { self?.navigationController?.popToRootViewController(animated: animated) } else { self?.navigationController?.popViewController(animated: animated) } } }
最后咱们跳转界面时进行以下调用便可spa
push(to: .targetA) push(to: .targetB(data: Data()))
protocol Scene: UIViewController { } protocol SceneAdpater { func transToScene() -> Scene } protocol Navigable: UIViewController { func push(to scene: SceneAdpater, animated: Bool) func pop(toRoot: Bool, animated: Bool) }
扩展Navigable
为其添加默认的实现。.net
extension Navigable { func push(to scene: SceneAdpater, animated: Bool = true) { let scene = scene.transToScene() DispatchQueue.main.async { [weak self] in self?.navigationController?.pushViewController(scene, animated: animated) } } func pop(toRoot: Bool = false, animated: Bool = true) { DispatchQueue.main.async { [weak self] in if toRoot { self?.navigationController?.popToRootViewController(animated: animated) } else { self?.navigationController?.popViewController(animated: animated) } } } }
通过上述的面向协议的改造,咱们在使用时的代码以下:code
enum TestScene: SceneAdpater { case targetA case targetB(data: Data) func transToScene() -> Scene { switch self { case .targetA: return ViewControllerA() case let .targetB(data): return ViewControllerB(data: data) } } } class ViewControllerA: UIViewController, Navigable, Scene { override func viewDidLoad() { super.viewDidLoad() push(to: TestScene.targetB(data: Data())) } } class ViewControllerB: UIViewController, Scene { override func viewDidLoad() { super.viewDidLoad() } init(data: Data) { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
初始化UIViewController
存在两种状况:须要传参、不须要传参。不须要传参的比较简单,直接调用UIKit
提供的初始化方法便可。而须要传参的UIViewController
,就涉及到如何肯定传参的类型、传入哪些参数。此时须要利用协议的关联类型来肯定传入的参数。
基于上述结论,制定的协议以下:
protocol Scene: UIViewController { } protocol NeedInputScene: Scene { associatedtype Input init(input: Input) } protocol NoneInputScene: Scene { }
由此在跳转方法中须要用到泛型
protocol Navigable: UIViewController { func push<S: NeedInputScene>(to scene: S.Type, input: S.Input, animated: Bool) func push<S: NoneInputScene>(to scene: S.Type, animated: Bool) } extension Navigable { func push<S: NeedInputScene>(to scene: S.Type, input: S.Input, animated: Bool = true) { let scene = scene.init(input: input) DispatchQueue.main.async { [weak self] in self?.navigationController?.pushViewController(scene, animated: animated) } } func push<S: NoneInputScene>(to scene: S.Type, animated: Bool = true) { let scene = scene.init() DispatchQueue.main.async { [weak self] in self?.navigationController?.pushViewController(scene, animated: animated) } } }
使用示例:
class ViewControllerA: UIViewController, Navigable { override func viewDidLoad() { super.viewDidLoad() push(to: ViewControllerB.self, input: Data()) push(to: ViewControllerC.self) } } class ViewControllerB: UIViewController, NeedInputScene { typealias Input = Data required init(input: Data) { super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class ViewControllerC: UIViewController, NoneInputScene { override func viewDidLoad() { super.viewDidLoad() } }
方案2相较于方案1最显著的区别就是再也不须要维护映射UIViewController
的枚举类型。
使用枚举来做为入参,外部在调用时能够很清晰的肯定须要传入参数的意义。而关联类型则不具有这种优点,不过这个问题经过使用枚举做为关联类型来解决,可是在UIViewController
仅须要一个字符串类型时这种作法就显得有点重。
方案2在模块须要提供多个入口时,须要暴露出多个控制器的类型,增长了耦合。而方案1则仅需用暴露出枚举类型便可。
Demo对方案1进行了演示,也是我目前在项目中使用的方案。
参考链接:
https://github.com/meili/MGJRouter