共计 4197 个字符,预计需要花费 11 分钟才能阅读完成。
随着业务减少,我的项目中的模块越来越多,并且这些模块进行互相的调用,使得它们交缠在一起,减少了保护老本,并且会升高开发效率。此时就须要对整个我的项目进行模块划分,将这些模块划分给多个开发人员(组)进行保护,而后在主工程中对这些模块进行调用。
每个模块独立存在,提供接口供其余模块调用。从而如何无效且解耦的进行模块间的调用成了重中之重。
那么如何传递参数并且初始化对应模块的主窗口成为了次要问题。上面会介绍两种计划来解决这个问题。
计划 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