Lightweight routing on microservices



Mobile applications have become really big lately - not only in the sense of their importance to you and me, but also in the literal sense.



. , . , , MVVM : - โ€” , - โ€” ,  โ€” ,  โ€” .



, ? : - , , MVVM .  โ€” iOS-.





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





, .



, :



,
  1. Don't show off. Dumb and understandable code is in most cases better than clever and incomprehensible code.
  2. Be brief. The code should be so small that it would not be a pity to throw it away and write it again in one day.
  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