隨着業務增長,項目中的模塊愈來愈多,而且這些模塊進行相互的調用,使得它們交纏在一塊兒,增長了維護成本,而且會下降開發效率。此時就須要對整個項目進行模塊劃分,將這些模塊劃分給多個開發人員(組)進行維護,而後在主工程中對這些模塊進行調用。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