随着业务增加,项目中的模块越来越多,并且这些模块进行相互的调用,使得它们交缠在一起,增加了维护成本,并且会降低开发效率。此时就需要对整个项目进行模块划分,将这些模块划分给多个开发人员(组)进行维护,然后在主工程中对这些模块进行调用。
每个模块独立存在,提供接口供其他模块调用。从而如何有效且解耦的进行模块间的调用成了重中之重。
那么如何传递参数并且初始化对应模块的主窗口成为了主要问题。下面会介绍两种方案来解决这个问题。
方案1
得益于Swift
中枚举可以传参的特性,我们可以通过枚举来进行参数传递,然后添加方法来映射控制器。
在枚举中通过参数来确定初始化控制器的数据,外部进行调用时可以很明确的知道需要传入哪些参数,同时还能传递非常规参数(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
映射的问题后,我们接下来解决如何统一跳转方法。
项目中,基本上都会使用UINavigationController
来进行界面的跳转,主要涉及push
、pop
操作。此时简便的做法是扩展UIViewController
为其添加统一界面跳转方法。
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)
}
}
}
最后我们跳转界面时进行如下调用即可
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
为其添加默认的实现。
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)
}
}
}
}
经过上述的面向协议的改造,我们在使用时的代码如下:
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
存在两种情况:需要传参、不需要传参。不需要传参的比较简单,直接调用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
Demo对方案1进行了演示,也是我目前在项目中使用的方案。
参考连接:
https://github.com/meili/MGJRouter
https://casatwy.com/iOS-Modulization.html
http://blog.cnbang.net/tech/3080/