背景:
当然,架构方法的选择会影响导航的实施以及项目中数据传输的组织,但是,该方法本身由多种情况组成:团队的组成,上市时间,技术规范的状态,项目的可扩展性等。
- 强制使用MVVM;
- 快速在导航过程中添加新屏幕(控制器及其视图模型)的能力;
- 业务逻辑的更改不应影响导航;
- 导航更改不应影响业务逻辑;
- 快速重用屏幕而无需更正导航的能力;
- 快速了解现有屏幕的能力;
- 快速了解项目中依赖项的能力;
- 不要提高开发人员进入项目的门槛。
切入点
应该注意的是,最终解决方案并不是一日之内就形成的,它并非没有缺点,它更适合于中小型项目。为了清楚起见,可以在此处查看测试项目:github.com/ArturRuZ/NavigationDemo
1.为了能够快速了解现有屏幕,决定创建一个名称明确的枚举ControllersList。
enum ControllersList {
case textInputScreen
case textConfirmationScreen
}
2.由于多种原因,该项目不想为DI使用第三方解决方案,而我想获得DI,包括能够快速查看项目中的依赖项,因此决定对每个单独的屏幕(按Assembly协议关闭)和RootAssembly使用Assembly一般范围。
protocol Assembly {
func build() -> UIViewController
}
final class TextInputAssembly: Assembly {
func build() -> UIViewController {
let viewModel = TextInputViewModel()
return TextInputViewController(viewModel: viewModel)
}
}
final class TextConfirmationAssembly: Assembly {
private let text: String
init(text: String) {
self.text = text
}
func build() -> UIViewController {
let viewModel = TextConfirmationViewModel(text: text)
return TextConfirmationViewController(viewModel: viewModel)
}
}
3.为了在屏幕之间(确实需要)之间传输数据,ControllersList变成了具有关联值的枚举:
enum ControllersList {
case textInputScreen
case textConfirmationScreen(text: String)
}
4.为了使业务逻辑既不影响导航,也不影响业务逻辑上的导航以及快速重用屏幕,有必要将导航移到单独的层。这是协调器和协调协议的出现方式:
protocol Coordination {
func show(view: ControllersList, firstPosition: Bool)
func popFromCurrentController()
}
final class Coordinator {
private var navigationController = UINavigationController()
private var factory: ControllerBuilder?
private func navigateWithFirstPositionInStack(to: UIViewController) {
navigationController.viewControllers = [to]
}
private func navigate(to: UIViewController) {
navigationController.pushViewController(to, animated: true)
}
}
extension Coordinator: Coordination {
func popFromCurrentController() {
navigationController.popViewController(animated: true)
}
func show(view: ControllersList, firstPosition: Bool) {
guard let controller = factory?.buildController(for: view) else { return }
firstPosition ? navigateWithFirstPositionInStack(to: controller) : navigate(to: controller)
}
}
重要的是要注意,该协议可以描述更多的方法,包括。像协调器一样,它可以根据需要实现不同的协议。
5.除此之外,我还想通过向应用程序添加新屏幕来限制开发人员必须执行的一组操作。此刻,有必要记住,您需要在某个地方注册依赖项,并且可能需要执行一些其他操作才能使导航正常工作。
6.我根本不想创建其他路由器和协调器。此外,为导航创建其他逻辑可能会使导航的感知和屏幕的重用大大复杂化。所有这些导致了一系列的变化,最终看起来像这样:
//MARK - Dependences with controllers associations
fileprivate extension ControllersList {
typealias scope = AssemblyServices
var assembly: Assembly {
switch self {
case .textInputScreen:
return TextInputAssembly(coordinator: scope.coordinator)
case .textConfirmationScreen(let text):
return TextConfirmationAssembly(coordinator: scope.coordinator, text: text)
}
}
}
//MARK - Services all time in memory
fileprivate enum AssemblyServices {
static let coordinator: oordinationDependencesRegstration = Coordinator()
static let controllerFactory: ControllerBuilderDependencesRegistration = ControllerFacotry()
}
//MARL: - RootAssembly Implementation
final class RootAssembly {
fileprivate typealias scope = AssemblyServices
private func registerPropertyDependences() {
// this place for propery dependences
}
}
// MARK: - AssemblyDataSource implementation
extension RootAssembly: AssemblyDataSource {
func getAssembly(key: ControllersList) -> Assembly? {
return key.assembly
}
}
现在,在创建新屏幕时,开发人员只需要对ControllersList进行更改,然后编译器本身就会显示需要进行更改的位置。将新的屏幕添加到ControllersList不会以任何方式影响当前的导航方案,并且依赖管理逻辑很容易遵循。同样,使用ControllersList,您可以轻松地找到特定屏幕的所有入口点,并且变得易于重用屏幕。
结论
此示例是该思想的简化实现,并不涵盖所有用例;不过,该方法本身证明是非常灵活和自适应的。
这种方法的缺点如下:
- , , . ControllersList NavigationEvents, , ;
- , ;
- , , . , .
关于IOS应用程序中导航和数据传输的大多数帖子都会影响协调器和路由器的使用(针对每个屏幕或一组屏幕),或者影响通过segue,singleton等的导航,但是这些选项都不适合我一个或另一个原因。
也许这种方法适合您解决问题,谢谢您的时间!