Leichtes Routing auf Microservices



Mobile Anwendungen sind in letzter Zeit sehr groß geworden - nicht nur im Sinne ihrer Bedeutung für Sie und mich, sondern auch im wörtlichen Sinne.



. , . , , MVVM : - — , - — ,  — ,  — .



, ? : - , , MVVM .  — iOS-.





, : . : , (, ), . , . :





, .



, :



,
  1. Nicht angeben. Dummer und verständlicher Code ist in den meisten Fällen besser als kluger und unverständlicher Code.
  2. Fass dich kurz. Der Code sollte so klein sein, dass es nicht schade wäre, ihn jederzeit wegzuwerfen und an einem Tag neu zu schreiben.
  3. . , SOLID, SOLID.
  4. . , .


.







?



MVVM , ( ). OrdersVC, - — OrdersVM. , :





, , - :



final class OrderDetailsVM: IPerRequest {
    typealias Arguments = Order

    let title: String

    required init(container: IContainer, args: Order) {
        self.title = "Details of \(args.name) #\(args.id)"
    }
}


IPerRequest ( — DI), , DI-. , . :



final class OrderDetailsVC: UIViewController, IHaveViewModel {
    typealias ViewModel = OrderDetailsVM

    private lazy var titleLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        view.addSubview(titleLabel)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.centerXAnchor
            .constraint(equalTo: view.centerXAnchor)
            .isActive = true
        titleLabel.topAnchor
            .constraint(equalTo: view.topAnchor, constant: 24)
            .isActive = true
    }

    func viewModelChanged(_ viewModel: OrderDetailsVM) {
        titleLabel.text = viewModel.title
    }
}


OrderDetailsVC IHaveViewModel ( — MVVM) , -. .



OrdersVC , :



extension OrdersVC: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        viewModel?.showOrderDetails(forOrderIndex: indexPath.row)
    }
}


, , MVC, iOS, MVVM . ( ) , iOS - . , , .



, OrdersVM, , :



final class OrdersVM: IPerRequest, INotifyOnChanged {
    typealias Arguments = Void

    var orders: [OrderVM] = []
    private let ordersProvider: OrdersProvider

    required init(container: IContainer, args: Void) {
        self.ordersProvider = container.resolve()
    }

    func loadOrders() {
        ordersProvider.loadOrders() { [weak self] model in
            self?.orders = model.map { OrderVM(order: $0) }
            self?.changed.raise()
        }
    }

    func showOrderDetails(forOrderIndex index: Int) {
        let order = orders[index].order
        //   ?
        // ...
    }
}


IPerRequest, , . OrdersProvider, . orders, - changed.raise().



showOrderDetails(forOrderIndex:) , . iOS, present(_:animated:completion:), .



MVC , MVVM : - . , - , - - .  — , .



, ?



, , OrdersVM , OrdersProvider.



 — , - . - .



, , . , .





, OrdersProvider, . ,  — , -. - : , . . : , -, .



 — . DI-, . , , DI- , .



, , :



  1. () .
  2. .
  3. DI- .


, - .



?



, , , , present(_:animated:completion:). , :



  1.  — . , VC - , -.
  2. , , . -, , - .
  3. , , . , .
  4.  — . , .


, . , , PresenterService.



, , ?



:



  1. UIViewController, .
  2. - - .
  3. .


, :



final class PresenterService: ISingleton {

    private unowned let container: IContainer

    public required init(container: IContainer, args: Void) {
        self.container = container
    }
}


, . , , - - , : , , ,  — . , PresenterService ,  — .



 —  — :



var topViewController: UIViewController? {
   let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
   return findTopViewController(in: keyWindow?.rootViewController)
}

func findTopViewController(in controller: UIViewController?) -> UIViewController? {
   if let navigationController = controller as? UINavigationController {
       return findTopViewController(in: navigationController.topViewController)
   } else if let tabController = controller as? UITabBarController,
       let selected = tabController.selectedViewController {
       return findTopViewController(in: selected)
   } else if let presented = controller?.presentedViewController {
       return findTopViewController(in: presented)
   }
   return controller
}


findTopViewController(in:) , , , . , , , , , , .



, . , - , . , , , , :



func present<VC: UIViewController & IHaveViewModel>(
    _ viewController: VC.Type,
    args: VC.ViewModel.Arguments) where VC.ViewModel: IResolvable {

    let vc = VC()
    vc.viewModel = container.resolve(args: args) //   
    topViewController?.present(vc, animated: true, completion: nil)
}


. MVVM DI- , , .



  1. , , , .
  2. - . - , IResolvable ( DI). , . - , - viewModel IHaveViewModel ( MVVM). , VC.ViewModel.Arguments . - DI- . : DI-, MVVM , — . !
  3. , , , , - , present(_:animated:completion:).


, PresenterService, :



final class PresenterService: ISingleton {

    private unowned let container: IContainer

    private var topViewController: UIViewController? {
        let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
        return findTopViewController(in: keyWindow?.rootViewController)
    }

    required init(container: IContainer, args: Void) {
        self.container = container
    }

    func present<VC: UIViewController & IHaveViewModel>(
        _ viewController: VC.Type,
        args: VC.ViewModel.Arguments) where VC.ViewModel: IResolvable {

        let vc = VC()
        vc.viewModel = container.resolve(args: args)
        topViewController?.present(vc, animated: true, completion: nil)
    }

    func dismiss() {
        topViewController?.dismiss(animated: true, completion: nil)
    }

    private func findTopViewController(
        in controller: UIViewController?) -> UIViewController? {

        if let navigationController = controller as? UINavigationController {
            return findTopViewController(in: navigationController.topViewController)
        } else if let tabController = controller as? UITabBarController,
            let selected = tabController.selectedViewController {
            return findTopViewController(in: selected)
        } else if let presented = controller?.presentedViewController {
            return findTopViewController(in: presented)
        }
        return controller
    }
}


, , — dismiss(), . OrdersVM, PresenterService , :



final class OrdersVM: IPerRequest, INotifyOnChanged {
    typealias Arguments = Void

    var orders: [OrderVM] = []

    private let ordersProvider: OrdersProvider
    private let presenter: PresenterService

    required init(container: IContainer, args: Void) {
        self.ordersProvider = container.resolve()
        self.presenter = container.resolve()
    }

    func loadOrders() {
        ordersProvider.loadOrders() { [weak self] model in
            self?.orders = model.map { OrderVM(order: $0) }
            self?.changed.raise()
        }
    }

    func showOrderDetails(forOrderIndex index: Int) {
        let order = orders[index].order
        //     
        presenter.present(OrderDetailsVC.self, args: order)
    }
}


, PresenterService showOrderDetails(forOrderIndex:).



, . ?



UINavigationController . , , NavigationService. , , :



  1. UINavigationController, .
  2. - - .
  3. .


, PresenterService, , . , .



NavigationService
final class NavigationService: ISingleton {

    private unowned let container: IContainer

    private var topNavigationController: UINavigationController? {
        let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
        let root = keyWindow?.rootViewController
        let topViewController = findTopViewController(in: root)
        return findNavigationController(in: topViewController)
    }

    required init(container: IContainer, args: Void) {
        self.container = container
    }

    func pushViewController<VC: UIViewController & IHaveViewModel>(
        _ viewController: VC.Type,
        args: VC.ViewModel.Arguments) where VC.ViewModel: IResolvable {

        let vc = VC()
        vc.viewModel = container.resolve(args: args)
        topNavigationController?.pushViewController(vc, animated: true)
    }

    func popViewController() {
        topNavigationController?.popViewController(animated: true)
    }

    private func findTopViewController(
        in controller: UIViewController?) -> UIViewController? {

        if let navigationController = controller as? UINavigationController {
            return findTopViewController(in: navigationController.topViewController)
        } else if let tabController = controller as? UITabBarController,
            let selected = tabController.selectedViewController {
            return findTopViewController(in: selected)
        } else if let presented = controller?.presentedViewController {
            return findTopViewController(in: presented)
        }
        return controller
    }

    private func findNavigationController(
        in controller: UIViewController?) -> UINavigationController? {

        if let navigationController = controller as? UINavigationController {
            return navigationController
        } else if let navigationController = controller?.navigationController {
            return navigationController
        } else {
            for child in controller?.children ?? [] {
                if let navigationController = findNavigationController(in: child) {
                    return navigationController
                }
            }
        }
        return nil
    }
}


, NavigationService PresenterService, ,  — UITabBarController, . .



. ?



 — , , . MVVM, , DI- — . , :





() - . - - . - ,  — . .  — , . « — -» , . DI-.



. , , , .  — . , . .





() .  — MVC MVVM. - DI- « — -».



PresenterService, , — , MVVM . PresenterService MVVM DI-, , .






Swift Playground.




All Articles