最近、モバイルアプリケーションは非常に大きくなっています。あなたと私にとっての重要性という意味だけでなく、文字通りの意味でもそうです。
. , . , , MVVM : - — , - — , — , — .
, ? : - , , MVVM . — iOS-.
, .
, :
- 自慢しないでください。ばかで理解しやすいコードは、ほとんどの場合、賢くて理解できないコードよりも優れています。
- 簡潔に。コードは非常に小さいので、いつでも捨てて1日で書き直すのは残念ではありません。
- . , SOLID, SOLID.
- . , .
.
?
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- , .
, , :
- () .
- .
- DI- .
, - .
?
, , , , present(_:animated:completion:)
. , :
- — . , VC - , -.
- , , . -, , - .
- , , . , .
- — . , .
, . , , PresenterService
.
, , ?
:
-
UIViewController
, . - - - .
- .
, :
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- , , .
- , , , .
- - . - ,
IResolvable
( DI). , . - , -viewModel
IHaveViewModel
( MVVM). ,VC.ViewModel.Arguments
. - DI- . : DI-, MVVM , — . ! - , , , , - ,
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
. , , :
-
UINavigationController
, . - - - .
- .
, PresenterService
, , . , .
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-, , .