Irbisa · Cheatsheet
iOS Architecture
MVC, MVVM, VIPER, Coordinators, TCA, Clean Swift
What is MVC in UIKit, and why does it so often turn into a Massive View Controller?
What is dependency injection, and how does it make a view model testable?
protocol UserAPI: Sendable { func user(id: String) async throws -> User} @MainActor @Observablefinal class ProfileViewModel {…What is MVVM, what problem does it solve, and how does it look in UIKit and in SwiftUI?
@MainActor @Observablefinal class ProfileViewModel { // no import UIKit or SwiftUI private(set) var name = "" private let api: ProfileAPI init(api: ProfileAPI) { self.api = api }…Find the bug: type
"iphone"quickly and the list sometimes shows the results for"iph".@MainActor @Observablefinal class SearchViewModel { private(set) var results: [Product] = [] private let api: any ProductAPI & Sendable init(api: any ProductAPI & Sendable) { self.api = api } func queryChanged(_ query: String) { Task { let found = (try? await api.search(query)) ?? [] results = found } }}Delegate, closure, NotificationCenter, KVO, Combine or AsyncSequence: how do you choose how two objects talk?
// Delegate: long-lived, several messages, one known counterpartprotocol BarcodeScannerDelegate: AnyObject { func scanner(_ scanner: BarcodeScanner, didFind code: String) func scannerDidFail(_ scanner: BarcodeScanner, error: Error)}final class BarcodeScanner {…Which classic design patterns do UIKit and Foundation use, and where do you see them?
What are the SOLID principles, with an iOS example for each?
What is the Coordinator pattern, and when is it worth using?
@MainActor protocol Coordinator: AnyObject { // conforming classes inherit the isolation func start()} final class AppCoordinator: Coordinator { private var children: [Coordinator] = []…Every finished checkout leaves its
CheckoutCoordinatoralive in memory. What is wrong with this code?@MainActorfinal class AppCoordinator { private var children: [AnyObject] = [] private let nav: UINavigationController init(nav: UINavigationController) { self.nav = nav } func showCheckout() { let checkout = CheckoutCoordinator(nav: nav) checkout.onFinish = { self.nav.popToRootViewController(animated: true) } children.append(checkout) checkout.start() }}A push notification must open a specific comment on a specific post. Where does that routing state live?
enum Route: Hashable { case post(id: String) case comment(postID: String, commentID: String)} @Observable…What is the Repository pattern, and why keep DTOs, domain models and view state as separate types?
// The wire format: pessimistic, matches the server exactlystruct PostDTO: Decodable { let id: Int let author_name: String? let created_at: String let status: String // never an enum here…When is a DI container such as Swinject or Factory worth it over plain constructor injection?
// wrong: a runtime registry; a missing line fails only when the code runslet container = Container()container.register(AuthService.self) { _ in LiveAuthService() }let auth = container.resolve(AuthService.self)! // crash in production // right: the initialiser states the requirement; the compiler enforces it…Find the bug:
testFreshInstallShowsOnboardingpasses alone on a clean simulator, but fails once the whole class has run, and keeps failing.@MainActor final class OnboardingViewModel { private let defaults: UserDefaults init(defaults: UserDefaults = .standard) { self.defaults = defaults } var showsOnboarding: Bool { !defaults.bool(forKey: "onboardingDone") } func finish() { defaults.set(true, forKey: "onboardingDone") }}@MainActor final class OnboardingTests: XCTestCase { func testFreshInstallShowsOnboarding() { XCTAssertTrue(OnboardingViewModel().showsOnboarding) } func testFinishHidesOnboarding() { let model = OnboardingViewModel() model.finish() XCTAssertFalse(model.showsOnboarding) }}How do VIPER and Clean Swift (VIP) split a screen, and what does that ceremony buy you?
// Clean Swift scene: VC -> Interactor -> Presenter -> VC, one direction only@MainActor protocol OrderBusinessLogic { func fetchOrder(request: Order.Fetch.Request) }protocol OrderPresentationLogic { func presentOrder(response: Order.Fetch.Response) }protocol OrderDisplayLogic: AnyObject { func displayOrder(viewModel: Order.Fetch.ViewModel) }@MainActor protocol OrderDataStore { var order: OrderEntity? { get } }protocol OrderLoading { func load(id: String) async throws -> OrderEntity }…What are feature flags, and how do you use them to ship a risky change safely?
Compare MVC, MVP, MVVM, VIPER and TCA: how do you choose an architecture for a feature or a team?
What is unidirectional data flow, and what problems does TCA solve with it?
import ComposableArchitecture struct NumberFactClient { var fetch: @Sendable (Int) async throws -> String }extension NumberFactClient: DependencyKey { static let liveValue = NumberFactClient { "\($0) is a number" }}…What is Clean Architecture on iOS, and when does a use-case layer pay for itself?
// Domain: plain Swift, no framework importsstruct Article: Identifiable, Equatable, Sendable { let id: String let title: String}…How do you structure a large iOS app, by feature or by layer, and where do the module boundaries go?
// swift-tools-version: 6.0import PackageDescription let package = Package( name: "Profile", platforms: [.iOS(.v17)],…A three-step checkout shares one draft order, and users report a new checkout opening with the previous order's address. Where should that draft live?
// wrong: app-wide lifetime, so the next checkout inherits the last onefinal class CheckoutDraft { static let shared = CheckoutDraft() } // right: the draft's lifetime is the flow's lifetime@Observablefinal class CheckoutDraft {…You are adding SwiftUI to a ten-year-old UIKit app. Where do you draw the boundary?
// UIKit pushes a SwiftUI screen; the coordinator still owns navigation@MainActorfinal class SettingsCoordinator { private let nav: UINavigationController private let session: SessionStore // @Observable, shared by both worlds…The app is 300k lines of view controllers and feature work cannot stop. How do you reach a testable architecture?
// before: untestable: the dependency is fetched, not receivedfinal class FeedViewController: UIViewController { private var posts: [Post] = [] func load() { Task { posts = (try? await NetworkManager.shared.feed()) ?? [] }…How would you design an image loading and caching pipeline for a scrolling feed?
How would you design offline-first sync for a notes app, including conflict resolution?