隨著業(yè)務(wù)增加,項目中的模塊越來越多癞己,并且這些模塊進行相互的調(diào)用膀斋,使得它們交纏在一起,增加了維護成本痹雅,并且會降低開發(fā)效率仰担。此時就需要對整個項目進行模塊劃分,將這些模塊劃分給多個開發(fā)人員(組)進行維護绩社,然后在主工程中對這些模塊進行調(diào)用摔蓝。
每個模塊獨立存在,提供接口供其他模塊調(diào)用愉耙。從而如何有效且解耦的進行模塊間的調(diào)用成了重中之重贮尉。
那么如何傳遞參數(shù)并且初始化對應(yīng)模塊的主窗口成為了主要問題。下面會介紹兩種方案來解決這個問題朴沿。
方案1
得益于Swift
中枚舉可以傳參的特性猜谚,我們可以通過枚舉來進行參數(shù)傳遞,然后添加方法來映射控制器赌渣。
在枚舉中通過參數(shù)來確定初始化控制器的數(shù)據(jù)魏铅,外部進行調(diào)用時可以很明確的知道需要傳入哪些參數(shù),同時還能傳遞非常規(guī)參數(shù)(Data
坚芜、UIImage
等)
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
映射的問題后览芳,我們接下來解決如何統(tǒng)一跳轉(zhuǎn)方法。
項目中货岭,基本上都會使用UINavigationController
來進行界面的跳轉(zhuǎn)路操,主要涉及push
、pop
操作千贯。此時簡便的做法是擴展UIViewController
為其添加統(tǒng)一界面跳轉(zhuǎn)方法。
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)
}
}
}
最后我們跳轉(zhuǎn)界面時進行如下調(diào)用即可
push(to: .targetA)
push(to: .targetB(data: Data()))
面向協(xié)議進行改造
protocol Scene: UIViewController {
}
protocol SceneAdpater {
func transToScene() -> Scene
}
protocol Navigable: UIViewController {
func push(to scene: SceneAdpater, animated: Bool)
func pop(toRoot: Bool, animated: Bool)
}
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)
}
}
}
}
經(jīng)過以上面向協(xié)議改造搞坝,我們在使用時的代碼如下:
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")
}
}
方案2
初始化UIViewController
存在兩種情況:需要傳參搔谴、不需要傳參。不需要傳參的比較簡單桩撮,直接調(diào)用UIKit
提供的初始化方法即可敦第。而需要傳參的UIViewController
,就涉及到如何確定傳參的類型店量、傳入哪些參數(shù)芜果。此時需要利用協(xié)議的關(guān)聯(lián)類型來確定傳入的參數(shù)。
基于上述結(jié)論融师,制定的協(xié)議如下:
protocol Scene: UIViewController {
}
protocol NeedInputScene: Scene {
associatedtype Input
init(input: Input)
}
protocol NoneInputScene: Scene {
}
由此在跳轉(zhuǎn)方法中需要用到泛型
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最顯著的區(qū)別就是不再需要維護映射UIViewController
的枚舉類型右钾。
使用枚舉來作為入?yún)ⅲ獠吭谡{(diào)用時可以很清晰的確定需要傳入?yún)?shù)的意義。而關(guān)聯(lián)類型則不具備這種優(yōu)勢舀射,不過這個問題通過使用枚舉作為關(guān)聯(lián)類型來解決窘茁,但是在UIViewController
僅需要一個字符串類型時這種做法就顯得有點重。
方案2在模塊需要提供多個入口時脆烟,需要暴露出多個控制器的類型山林,增加了耦合。而方案1則僅需用暴露出枚舉類型即可邢羔。
Demo
Demo對方案1進行了演示驼抹,也是我目前在項目中使用的方案。
更多路由相關(guān)信息請查看一下參考鏈接
參考連接:
https://github.com/meili/MGJRouter