Perutean ringan pada layanan microser



Aplikasi seluler telah menjadi sangat besar belakangan ini - tidak hanya dalam arti pentingnya bagi Anda dan saya, tetapi juga dalam arti harfiah.



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



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





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





, .



, :



,
  1. Jangan pamer. Kode bodoh dan dapat dimengerti dalam banyak kasus lebih baik daripada kode pintar dan tidak bisa dipahami.
  2. Bersikap singkat. Kode tersebut harus sangat kecil sehingga tidak sayang untuk membuangnya kapan saja dan menulis ulang dalam satu hari.
  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