--- layout: default title: iOS Architecture Patterns categories: swift --- ### Remove story board dependency 1. Remove `Main.storyboard` file 1. Remove storyboard reference from `Info.plist` → In Scene Configuration find `Storyboard Name` and delete it 1. Go to build settings and remove `UIKit MainStoryboard File Base Name` field 1. Create a window in Scene Delegate ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: scene) window.rootViewController = ViewController() window.makeKeyAndVisible() self.window = window } ``` ## Dependency injection [Explanation of the code under this link](https://www.avanderlee.com/swift/dependency-injection/) ```swift public protocol InjectionKey { associatedtype Value static var currentValue: Self.Value { get set } } ``` ```swift struct InjectedValues { private static var current = InjectedValues() static subscript(key: K.Type) -> K.Value where K : InjectionKey { get { key.currentValue } set { key.currentValue = newValue } } static subscript(_ keyPath: WritableKeyPath) -> T { get { current[keyPath: keyPath] } set { current[keyPath: keyPath] = newValue } } } ``` ```swift @propertyWrapper struct Injected { private let keyPath: WritableKeyPath var wrappedValue: T { get { InjectedValues[keyPath] } set { InjectedValues[keyPath] = newValue } } init(_ keyPath: WritableKeyPath) { self.keyPath = keyPath } } ``` #### Define dependency ```swift private struct UsersRepositoryKey: InjectionKey { static var currentValue: AnyUsersRepository = UsersRepository() } extension InjectedValues { var usersRepository: AnyUsersRepository { get { Self[UsersRepositoryKey.self] } set { Self[UsersRepositoryKey.self] = newValue } } } protocol AnyUsersRepository { func getUsers(_ result: @escaping (Result<[User], Error>)->Void) } class UsersRepository: AnyUsersRepository { func getUsers(_ result: @escaping (Result<[User], Error>)->Void) { <#Implementation#> } } ``` ```swift @Injected(\.usersRepository) var usersRepository: AnyUsersRepository ``` ```swift InjectedValues[\.usersRepository] = MockedUsersRepository() ``` ## Model-View-Controller #### Clasic version - `View` and `Model` are linked together, so reusability is reduced. - Note: Views in iOS apps are quite often reusable.

{% svg ../svgs/classic-mvc.svg class="center-image" %}

#### Apple version

{% svg ../svgs/apple-mvc.svg class="center-image" %}

**Model** responsibilities: - Business logic - Accessing and manipulating data - Persistence - Communication/Networking - Parsing - Extensions and helper classes - Communication with models Note: The `Model` must not communicate directly with the `View`. The `Controller` is the link between those **View** responsibilities: - Animations, drawings (`UIView`, `CoreAnimation`, `CoreGraphics`) - Show data that controller sends - Might receive user input **Controller** responsibilities: - Exchange data between `View` and `Model` - Receive user actions and interruptions or signals from the outside the app - Handles the view life cycle #### Advantages - Simple and usually less code - Fast development for simple apps #### Disadvantages - Controllers coupled views - Massive `ViewController`s #### Communication between components - [Delegation](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html) pattern - [Target-Action](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html) pattern - [Observer](https://developer.apple.com/documentation/foundation/nsnotificationcenter) pattern with `NSNotificationCenter` - [Observer](https://developer.apple.com/documentation/swift/using-key-value-observing-in-swift) pattern with `KVO` ## Model-View-Presenter In this design pattern View is implemented with classes `UIView` and `UIViewController`. The `UIViewController` has less responsibilities which are limited to: - Routing/Coordination - Navigation - Passing informations via a delegation pattern #### View ```swift class ExampleController: UIViewController { private let exampleView = ExampleView() override func loadView() { super.loadView() setup() } private func setup() { let presenter = ExamplePresenter(exampleView) exampleView.presenter = presenter exampleView.setupView() self.view = exampleView } } ``` #### Presenter ```swift protocol ExampleViewDelegate { func updateView() } class ExamplePresenter { private weak var exampleView: ExampleViewDelegate? init(_ exampleView: ExampleViewDelegate) { self.exampleView = exampleView } } ``` #### Advantages - Easier to test business logic - Better separation of responsibilities #### Disadvantages - Usually not a better choice for smaller projects - Presenters might become massive - Controllers still handle navigation. Possible solutions → extend the pattern with Router or Coordinator. #### Common layers - Data access layer: `CRUD` operations facilitated with `CoreData` `Realm` etc. - Services: Classes that interacts with database entities, like retrieve data, transform them into objects. - Extensions/utils ## Model-View-ViewModel `ViewModel` has no references to the view.

{% svg ../svgs/mvvm.svg class="center-image" %}

Binding is done using: `Combine Framework`, `RxSwift`, `Bond` or `KVO` or using delegation pattern * **`Model`** does same things as in `MVP` and `MVC`. * **`View`** also is similar, but binds with `ViewModel` * **`ViewModel`** keeps updated state of the view, and process data for i #### Advantages - Better reparation of responsibilities - Better testability, without needing to take into account the views #### Disadvantages - Might be slower and introduce dependency on external libraries - Harder to learn and can become complex #### **Extension with Coordinator MVVM-C** Role of `Coordinator` is to manage navigation flow. ```swift protocol Coordinator { var navigationController: UINavigationController { get set } func start() } ``` and an example implementation ```swift class ExampleCoordinator: Coordinator { var navigationController: UINavigationController init(navigationController: UINavigationController) { self.navigationController = UINavigationController } func start() { let viewModel = ExampleViewModel(someService: SomeService(), coordinator: self) navigationController.pushViewController(ExampleController(viewModel), animated: true) } func showList(_ list: ExampleListModel) { let listCoordinator = ListCoordinator(navigationController: navigationController list: list) listCoordinator.start() } } ``` In the book I am reading the author created an `ExampleCoordinatorProtocol` with a `func showList(_ list: ExampleListModel)` where the `ExampleCoordinator` implemented it. I think it does not make any sense, however if we might want to inject the coordinator then we might want to relay on an abstraction. ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: scene) let navigationController = UINavigationController() exampleCoordinator = ExampleCoordinator(navigationController: navigationController) exampleCoordinator?.start() window.rootViewController = navigationController window.makeKeyAndVisible() self.window = window } ``` ## VIPER

{% svg ../svgs/viper-ownership.svg class="center-image" %}

#### View It includes `UIViewController` - Made only to preserve elements like Buttons Labels - It sends informations to presenters, and receive messages what to show and knows how #### Interactor - Receives informations form databases, servers etc. - The book says that the Interactor receive actions from presenter, and returns the result via Delegation Pattern. - The interactor never sends entities to the Presenter #### Presenter - Is in the centre and serves as a link - Process events from the view and requests data from the Interctor. It receives that as primitives, never Entities. - it handles navigation to the other screens using the Router #### Entity - Simple models usually data structures - They can only be used by the Interactor #### Router - Creates screens - Handles navigation, but itself does not know where to go to. - _The book says it is the owner of the `UINavigationController` and UIViewController, but it is contrary to other parts of the book, so I do not know_ - Similar to `Coordinator` form MVVM-C **`Entity.swift`** ```swift struct User: Codable { let name: String } ``` **`Interactor.swift`** ```swift enum FetchError: Error { case failed } protocol AnyInteractor { var presenter: AnyPresenter? { get set } func getUsers() } class UserInteractor: AnyInteractor { @Injected(\.usersRepository) var usersRepository: AnyUsersRepository var presenter: AnyPresenter? func getUsers() { usersRepository.getUsers { [weak self] in self?.presenter?.interactorDidFetchUsers(with: $0) } } } ``` **`Presenter.swift`** ```swift protocol AnyPresenter { var router: AnyRouter? { get set } var interactor: AnyInteractor? { get set } var view: AnyView? { get set } func interactorDidFetchUsers(with result: Result<[User], Error>) } class UserPresenter: AnyPresenter { func interactorDidFetchUsers(with result: Result<[User], Error>) { switch result { case let .success(users): view?.update(with: users) case let .failure(error): view?.update(with: error.localizedDescription) } } var router: AnyRouter? var interactor: AnyInteractor? { didSet { interactor?.getUsers() } } var view: AnyView? } ``` **`Router.swift`** ```swift typealias EntryPoint = AnyView & UIViewController protocol AnyRouter { var entry: EntryPoint? { get } static func start() -> AnyRouter } class UserRouter: AnyRouter { var entry: EntryPoint? static func start() -> AnyRouter { let router = UserRouter() var view: AnyView = UserViewController() var presenter: AnyPresenter = UserPresenter() var interactor: AnyInteractor = UserInteractor() view.presenter = presenter interactor.presenter = presenter presenter.router = router presenter.view = view presenter.interactor = interactor router.entry = view as? EntryPoint return router } } //There are a few retain cycles with view, presenter, router and interactor. One option you can do is to make those protocols conforms to AnyObject, and mark these references as "weak": //1. router's ref to presenter //2. router's ref to view //3. presenter's ref to view //4. interactor's ref to presenter ``` **`View.swift`** ```swift protocol AnyView { var presenter: AnyPresenter? { get set } func update(with users: [User]) func update(with error: String) } class UserViewController: UIViewController, AnyView { var presenter: AnyPresenter? private let tableView: UITableView = { let tableView = UITableView() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") tableView.isHidden = true return tableView }() var users = [User]() override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) tableView.delegate = self tableView.dataSource = self } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() tableView.frame = view.bounds } func update(with users: [User]) { DispatchQueue.main.async { self.users = users self.tableView.reloadData() self.tableView.isHidden = false } } func update(with error: String) { } } extension UserViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { users.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = users[indexPath.row].name return cell } } ```