āļŠāļąāļĄāļ āļēāļĐāļāđ iOS Senior 2026: āļāļģāļāļēāļĄāđāļĢāļ·āđāļāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāđāļĨāļ° Design Pattern
āđāļāļĢāļĩāļĒāļĄāļāļąāļ§āļŠāļąāļĄāļ āļēāļĐāļāđ iOS senior āļāđāļ§āļĒāļāļģāļāļēāļĄāļŠāļģāļāļąāļāđāļāļĩāđāļĒāļ§āļāļąāļ MVVM, VIPER, Clean Architecture āđāļĨāļ° design pattern āļāļđāđāļĄāļ·āļāļāļĢāļāļāđāļ§āļāļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļ Swift

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS senior āđāļŦāđāļāļ§āļēāļĄāļŠāļģāļāļąāļāļāļĒāđāļēāļāļĄāļēāļāļāļąāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāđāļĨāļ° design pattern āļāļāļāļāļēāļāđāļ§āļĒāļēāļāļĢāļāđāļāļāļ Swift āđāļĨāđāļ§ āļāļđāđāļŠāļąāļĄāļ āļēāļĐāļāđāļĒāļąāļāļāļĢāļ°āđāļĄāļīāļāļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļāļāđāļāļāđāļāļāļāļĨāļīāđāļāļāļąāļāļāļĩāđāļāļģāļĢāļļāļāļĢāļąāļāļĐāļēāļāđāļēāļĒ āļāļāļŠāļāļāđāļāđ āđāļĨāļ°āļāļĒāļēāļĒāļāļąāļ§āđāļāđ
āļāļđāđāļĄāļ·āļāļāļĩāđāļāļĢāļāļāļāļĨāļļāļĄāļāļģāļāļēāļĄāļāļĩāđāļāļāļāđāļāļĒāļāļĩāđāļŠāļļāļāđāļāļĩāđāļĒāļ§āļāļąāļ MVVM, VIPER, Clean Architecture āđāļĨāļ° pattern āļāļĩāđāļāļģāđāļāđāļ āļāļĢāđāļāļĄāļāļģāļāļāļāđāļāļĒāļĨāļ°āđāļāļĩāļĒāļāđāļĨāļ°āļāļąāļ§āļāļĒāđāļēāļāđāļāđāļāļāļĩāđāļāļĢāđāļāļĄāđāļāđāļāļēāļāđāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ
āđāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđāļĢāļ°āļāļąāļ senior āļāļģāļāļāļāļāļēāļāđāļāļāļāļīāļāļŠāļģāļāļąāļāļāđāļāļĒāļāļ§āđāļēāļāļēāļĢāđāļŦāđāđāļŦāļāļļāļāļĨ āļāļ§āļĢāļāļāļīāļāļēāļĒāđāļŠāļĄāļāļ§āđāļēāļāļģāđāļĄāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāļŦāļāļķāđāļāļāļķāļāđāļŦāļĄāļēāļ°āļāļąāļāļāļĢāļīāļāļāļŦāļāļķāđāļ āđāļĄāđāđāļāđāđāļāļĩāļĒāļāļ§āļīāļāļĩāļāļēāļĢāļāļģāđāļāđāļāđ
āļāļģāļāļ§āļēāļĄāđāļāđāļēāđāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ iOS: āļ āļēāļāļĢāļ§āļĄ
āļāđāļāļāđāļāđāļēāļŠāļđāđāļāļģāļāļēāļĄāđāļāļāļēāļ° āļāļēāļĢāđāļāđāļēāđāļāļ āļđāļĄāļīāļāļąāļĻāļāđāļāļāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ iOS āđāļāđāļāļŠāļīāđāļāļāļģāđāļāđāļ āđāļāđāļĨāļ° pattern āđāļāđāļāļąāļāļŦāļēāļāļĩāđāđāļāļāļāđāļēāļāļāļąāļāđāļĨāļ°āđāļŦāļĄāļēāļ°āļāļąāļāļāļĢāļīāļāļāļāļĩāđāļāđāļēāļāļāļąāļ
MVC: pattern āļāļąāđāļāđāļāļīāļĄāļāļāļ Apple
MVC (Model-View-Controller) āļĒāļąāļāļāļāđāļāđāļ pattern āđāļĢāļīāđāļĄāļāđāļāļāļāļ Apple āđāļāđāļāļĢāļ°āļŠāļāļāļąāļāļŦāļē "Massive View Controllers" āđāļāđāļāļāļāļĩāđāļāļąāļāļāđāļāļ
// Typical MVC example showing its limitations
class UserViewController: UIViewController {
// The ViewController accumulates too many responsibilities
private var users: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
// View logic
setupUI()
// Business logic
fetchUsers()
// Navigation logic
setupNavigationBar()
}
private func fetchUsers() {
// Networking in the VC: anti-pattern
URLSession.shared.dataTask(with: URL(string: "api/users")!) { data, _, _ in
// JSON parsing here too...
}.resume()
}
}pattern āļāļĩāđāļāļĨāļēāļĒāđāļāđāļāļāļąāļāļŦāļēāđāļĄāļ·āđāļ ViewController āđāļāļīāļ 500 āļāļĢāļĢāļāļąāļ āļāļģāđāļŦāđāļāļēāļĢāļāļģ unit test āđāļāļāđāļāđāļāđāļāđāļĄāđāđāļāđ
āļāļģāļāļēāļĄāļāļĩāđ 1: āļāļāļīāļāļēāļĒ MVVM āđāļĨāļ°āļāļēāļĢāļāļģāđāļāđāļāđāđāļ Swift
MVVM (Model-View-ViewModel) āđāļĒāļ presentation logic āļāļāļāđāļāļĒāļąāļ ViewModel āļāļģāđāļŦāđāļāļāļŠāļāļāđāļāđāļāđāļēāļĒāđāļĨāļ°āļĨāļāļāļāļēāļāļāļāļ ViewController
MVVM āđāļāļāđāļāđāļāđāļĄāļ·āđāļāđāļāđāļāļąāļ SwiftUI āļāđāļ§āļĒ @Observable āđāļĨāļ° data binding āđāļāļāļąāļ§ āļŠāđāļ§āļ UIKit āļāđāļāļāđāļāđāļāļĨāđāļ binding (Combine, closure)
āļāļēāļĢāđāļāđāļāļēāļ MVVM āļāđāļ§āļĒ Combine
// ViewModel separated from any UIKit dependency
import Combine
@MainActor
final class UserViewModel: ObservableObject {
// Published states for binding
@Published private(set) var users: [User] = []
@Published private(set) var isLoading = false
@Published private(set) var errorMessage: String?
// Injected dependency for testability
private let userRepository: UserRepositoryProtocol
private var cancellables = Set<AnyCancellable>()
init(userRepository: UserRepositoryProtocol = UserRepository()) {
self.userRepository = userRepository
}
func loadUsers() {
isLoading = true
errorMessage = nil
userRepository.fetchUsers()
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.errorMessage = error.localizedDescription
}
} receiveValue: { [weak self] users in
self?.users = users
}
.store(in: &cancellables)
}
}View āļāļĩāđāđāļāđ binding āļāļāļ Combine
// SwiftUI View consuming the ViewModel
import SwiftUI
struct UserListView: View {
// StateObject for lifecycle management
@StateObject private var viewModel = UserViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView("Loading...")
} else if let error = viewModel.errorMessage {
ErrorView(message: error, retry: viewModel.loadUsers)
} else {
List(viewModel.users) { user in
UserRowView(user: user)
}
}
}
.onAppear { viewModel.loadUsers() }
}
}ViewModel āđāļĄāđāļĢāļđāđāļāļąāļ UIKit āļŦāļĢāļ·āļ SwiftUI āļāļķāļāļŠāļēāļĄāļēāļĢāļāļāļģ unit test āđāļāđāļāļĒāđāļēāļāđāļāđāļĄāļāļĩāđ
āļāļģāļāļēāļĄāļāļĩāđ 2: āđāļĄāļ·āđāļāđāļāļāļ§āļĢāđāļĨāļ·āļāļ VIPER āđāļāļ MVVM?
VIPER (View-Interactor-Presenter-Entity-Router) āđāļŦāļĄāļēāļ°āļāļąāļāđāļāļāļāļĩāđāļāļąāļāļāđāļāļāļāļķāđāļāļāđāļāļāļāļēāļĢāļāļēāļĢāđāļĒāļāļŦāļāđāļēāļāļĩāđāļāļĒāđāļēāļāđāļāđāļĄāļāļ§āļāđāļĨāļ°āļāļēāļĢāļāļģāļāļēāļāļāļąāđāļāļŠāļđāļ
āđāļāļĢāļāļŠāļĢāđāļēāļ VIPER āđāļāļāđāļāđāļĄ
// Contract definitions between VIPER components
protocol UserListViewProtocol: AnyObject {
var presenter: UserListPresenterProtocol? { get set }
func showUsers(_ users: [UserViewModel])
func showError(_ message: String)
func showLoading()
}
protocol UserListPresenterProtocol: AnyObject {
var view: UserListViewProtocol? { get set }
var interactor: UserListInteractorInputProtocol? { get set }
var router: UserListRouterProtocol? { get set }
func viewDidLoad()
func didSelectUser(_ user: UserViewModel)
}
protocol UserListInteractorInputProtocol: AnyObject {
var presenter: UserListInteractorOutputProtocol? { get set }
func fetchUsers()
}
protocol UserListInteractorOutputProtocol: AnyObject {
func didFetchUsers(_ users: [User])
func didFailWithError(_ error: Error)
}
protocol UserListRouterProtocol: AnyObject {
func navigateToUserDetail(with userId: String)
}Presenter āļāļģāļŦāļāđāļēāļāļĩāđāļāļĢāļ°āļŠāļēāļāļāļēāļāļĨāļāļāļīāļ
// The Presenter bridges View and Interactor
final class UserListPresenter: UserListPresenterProtocol {
weak var view: UserListViewProtocol?
var interactor: UserListInteractorInputProtocol?
var router: UserListRouterProtocol?
func viewDidLoad() {
view?.showLoading()
interactor?.fetchUsers()
}
func didSelectUser(_ user: UserViewModel) {
router?.navigateToUserDetail(with: user.id)
}
}
// Extension for Interactor callbacks
extension UserListPresenter: UserListInteractorOutputProtocol {
func didFetchUsers(_ users: [User]) {
// Model -> ViewModel transformation
let viewModels = users.map { UserViewModel(from: $0) }
view?.showUsers(viewModels)
}
func didFailWithError(_ error: Error) {
view?.showError(error.localizedDescription)
}
}VIPER āđāļāļīāđāļĄ boilerplate āļāļģāļāļ§āļāļĄāļēāļ āļāļ§āļĢāļāļāļīāļāļēāļĒāđāļŦāļāļļāļāļĨāļāļāļāļāļēāļĢāđāļāđāļāđāļ§āļĒāļāļāļēāļāļāļĩāļĄ (āļĄāļĩāļāļąāļāļāļąāļāļāļēāļŦāļĨāļēāļĒāļāļāļāļģāļāļēāļāđāļāđāļĄāļāļđāļĨāđāļāļĩāļĒāļ§) āļŦāļĢāļ·āļāļāļ§āļēāļĄāļāļąāļāļāđāļāļāļāļāļāđāļāđāļĄāļāļāļļāļĢāļāļīāļ
āļāļģāļāļēāļĄāļāļĩāđ 3: āļāļģ Clean Architecture āđāļāđāļāđāļāļ iOS āļāļĒāđāļēāļāđāļĢ?
Clean Architecture āļāļąāļāđāļĢāļĩāļĒāļāđāļāđāļāđāļāđāļāļ§āļāļāļĨāļĄāļāđāļāļāļāļąāļ āđāļāļĒāļĄāļĩāļāļāļāļēāļāļāļļāļĢāļāļīāļāļāļĒāļđāđāļāļĢāļāļāļĨāļēāļ āđāļāđāļāļāļīāļŠāļĢāļ°āļāļēāļāđāļāļĢāļĄāđāļ§āļīāļĢāđāļ
āđāļāļĢāļāļŠāļĢāđāļēāļāđāļāđāļāļāļąāđāļ
// Pure business entity, no framework dependencies
struct User: Identifiable, Equatable {
let id: String
let email: String
let fullName: String
let subscriptionLevel: SubscriptionLevel
enum SubscriptionLevel: String {
case free, premium, enterprise
}
}
// Domain/UseCases/GetUsersUseCase.swift
// Use Case encapsulating a business rule
protocol GetUsersUseCaseProtocol {
func execute() async throws -> [User]
}
final class GetUsersUseCase: GetUsersUseCaseProtocol {
// Dependency on abstraction, not implementation
private let repository: UserRepositoryProtocol
init(repository: UserRepositoryProtocol) {
self.repository = repository
}
func execute() async throws -> [User] {
let users = try await repository.fetchAll()
// Business rule: sort by subscription level
return users.sorted { $0.subscriptionLevel.rawValue > $1.subscriptionLevel.rawValue }
}
}āļāļąāđāļāļāđāļāļĄāļđāļĨāļāđāļ§āļĒ pattern Repository
// Concrete repository implementation
final class UserRepository: UserRepositoryProtocol {
private let remoteDataSource: UserRemoteDataSourceProtocol
private let localDataSource: UserLocalDataSourceProtocol
init(
remoteDataSource: UserRemoteDataSourceProtocol = UserRemoteDataSource(),
localDataSource: UserLocalDataSourceProtocol = UserLocalDataSource()
) {
self.remoteDataSource = remoteDataSource
self.localDataSource = localDataSource
}
func fetchAll() async throws -> [User] {
do {
// Cache-first strategy with fallback
let remoteUsers = try await remoteDataSource.fetchUsers()
await localDataSource.save(remoteUsers)
return remoteUsers
} catch {
// Fallback to local cache
return try await localDataSource.fetchUsers()
}
}
}āļāļēāļĢāļāļąāļāļĢāļ°āđāļāļĩāļĒāļāļāļĩāđāļāļģāđāļŦāđāļŠāļēāļĄāļēāļĢāļāļāļāļŠāļāļāđāļāđāļĨāļ°āļāļąāđāļāđāļāđāļāļĒāđāļēāļāļāļīāļŠāļĢāļ° āđāļĨāļ°āđāļāļĨāļĩāđāļĒāļāļāļēāļĢāļāļģāđāļāđāļāđ (āđāļāđāļ āļĒāđāļēāļĒāļāļēāļ CoreData āđāļāļĒāļąāļ SwiftData) āđāļāļĒāđāļĄāđāļāļĢāļ°āļāļāđāļāđāļĄāļ
āļāļĢāđāļāļĄāļāļĩāđāļāļ°āļāļīāļāļīāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļĨāđāļ§āļŦāļĢāļ·āļāļĒāļąāļāļāļĢāļąāļ?
āļāļķāļāļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāđāļāļāđāļāđāļāļāļ, flashcards āđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āļāļģāļāļēāļĄāļāļĩāđ 4: āđāļāđ design pattern āđāļāđāļāđāļāļāļĢāļ°āļāļģ?
āļāļđāđāļŠāļąāļĄāļ āļēāļĐāļāđāļāļēāļāļŦāļ§āļąāļāļāļ§āļēāļĄāđāļāļĩāđāļĒāļ§āļāļēāļāļāļēāļāļāļēāļĢāđāļāđāļāļēāļāļāļĢāļīāļ āđāļĄāđāđāļāđāļāļēāļĢāļāđāļāļāļāļģāļāļĪāļĐāļāļĩ āļāđāļāđāļāļāļĩāđāļāļ·āļ pattern āļāļĩāđāļāļāļāđāļāļĒāļāļĩāđāļŠāļļāļāļāļ iOS
Dependency Injection āļāđāļ§āļĒ Property Wrappers
// Simple and effective injection container
final class DIContainer {
static let shared = DIContainer()
private var factories: [String: () -> Any] = [:]
func register<T>(_ type: T.Type, factory: @escaping () -> T) {
let key = String(describing: type)
factories[key] = factory
}
func resolve<T>(_ type: T.Type) -> T {
let key = String(describing: type)
guard let factory = factories[key], let instance = factory() as? T else {
fatalError("No registration for \(key)")
}
return instance
}
}
// Property Wrapper for elegant injection
@propertyWrapper
struct Injected<T> {
private var value: T
init() {
self.value = DIContainer.shared.resolve(T.self)
}
var wrappedValue: T {
get { value }
mutating set { value = newValue }
}
}
// Usage in a ViewModel
final class PaymentViewModel {
@Injected private var paymentService: PaymentServiceProtocol
@Injected private var analyticsService: AnalyticsServiceProtocol
func processPayment(_ amount: Decimal) async throws {
analyticsService.track(.paymentInitiated(amount: amount))
try await paymentService.charge(amount)
}
}Coordinator Pattern āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļģāļāļēāļ
// Coordinator managing navigation flow
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get }
func start()
}
final class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
let navigationController: UINavigationController
private let window: UIWindow
init(window: UIWindow) {
self.window = window
self.navigationController = UINavigationController()
}
func start() {
window.rootViewController = navigationController
window.makeKeyAndVisible()
// Flow decision based on state
if AuthManager.shared.isAuthenticated {
showMainFlow()
} else {
showAuthFlow()
}
}
private func showAuthFlow() {
let authCoordinator = AuthCoordinator(navigationController: navigationController)
authCoordinator.delegate = self
childCoordinators.append(authCoordinator)
authCoordinator.start()
}
private func showMainFlow() {
let mainCoordinator = MainCoordinator(navigationController: navigationController)
childCoordinators.append(mainCoordinator)
mainCoordinator.start()
}
}pattern āļāļĩāđāļāļģāļĨāļāļāļīāļāļāļēāļĢāļāļģāļāļēāļāļāļāļāļāļēāļ ViewController āļāļģāđāļŦāđāđāļāļēāļāļķāđāļāđāļĨāļ°āļāļģāļāļĨāļąāļāļĄāļēāđāļāđāđāļŦāļĄāđāđāļāđāļāđāļēāļĒāļāļķāđāļ
āļāļģāļāļēāļĄāļāļĩāđ 5: āļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļ·āđāļāļŠāļēāļĢāļĢāļ°āļŦāļ§āđāļēāļāđāļĄāļāļđāļĨāļāļĒāđāļēāļāđāļĢ?
āļāļēāļĢāļŠāļ·āđāļāļŠāļēāļĢāļĢāļ°āļŦāļ§āđāļēāļāđāļĄāļāļđāļĨāđāļāđāļāđāļĢāļ·āđāļāļāļŠāļģāļāļąāļāđāļāđāļāļāļāļāļēāļāđāļŦāļāđ āļĄāļĩāļŦāļĨāļēāļĒāđāļāļ§āļāļēāļāļāļķāđāļāļāļąāļāļĢāļ°āļāļąāļāļāļēāļĢāļāļąāļāļāļĨāļīāđāļāļāļĩāđāļāđāļāļāļāļēāļĢ
āļāļēāļĢāļŠāļ·āđāļāļŠāļēāļĢāļāđāļēāļ protocol
// Public contracts exposed by each module
protocol PaymentModuleProtocol {
func startPaymentFlow(for productId: String, completion: @escaping (Result<Receipt, PaymentError>) -> Void)
}
protocol UserModuleProtocol {
func getCurrentUser() -> User?
func updateProfile(_ profile: ProfileUpdate) async throws
}
// Modules/Payment/PaymentModule.swift
// Internal module implementation
final class PaymentModule: PaymentModuleProtocol {
static let shared: PaymentModuleProtocol = PaymentModule()
private let paymentService: PaymentService
private init() {
self.paymentService = PaymentService()
}
func startPaymentFlow(for productId: String, completion: @escaping (Result<Receipt, PaymentError>) -> Void) {
// Module-internal logic
paymentService.process(productId: productId, completion: completion)
}
}āļāļēāļĢāļŠāļ·āđāļāļŠāļēāļĢāđāļāļ event-driven āļāđāļ§āļĒ Combine
// Decoupled event bus for async communication
enum AppEvent {
case userDidLogin(User)
case userDidLogout
case purchaseCompleted(Receipt)
case subscriptionChanged(SubscriptionLevel)
}
final class AppEventBus {
static let shared = AppEventBus()
// Private Subject, public Publisher
private let eventSubject = PassthroughSubject<AppEvent, Never>()
var events: AnyPublisher<AppEvent, Never> {
eventSubject.eraseToAnyPublisher()
}
func send(_ event: AppEvent) {
eventSubject.send(event)
}
}
// Listening in any module
final class AnalyticsModule {
private var cancellables = Set<AnyCancellable>()
init() {
AppEventBus.shared.events
.sink { [weak self] event in
self?.handleEvent(event)
}
.store(in: &cancellables)
}
private func handleEvent(_ event: AppEvent) {
switch event {
case .purchaseCompleted(let receipt):
trackPurchase(receipt)
case .userDidLogin(let user):
identifyUser(user)
default:
break
}
}
}āļāļ§āļĢāļāļĨāđāļēāļ§āļāļķāļāļ§āđāļēāļāļēāļĢāđāļĨāļ·āļāļāļĢāļ°āļŦāļ§āđāļēāļāļāļąāļāļāļĨāļīāđāļāđāļāđāļ (protocol) āļāļąāļāļāļąāļāļāļĨāļīāđāļāļŦāļĨāļ§āļĄ (event) āļāļķāđāļāļāļĒāļđāđāļāļąāļāļāļĢāļīāļāļ: event āđāļŦāļĄāļēāļ°āļāļąāļāļāļēāļĢāđāļāđāļāđāļāļ·āļāļāļāļąāđāļ§āļĢāļ°āļāļ āļŠāđāļ§āļ protocol āđāļŦāļĄāļēāļ°āļāļąāļāļāļēāļĢāļĄāļĩāļāļāļīāļŠāļąāļĄāļāļąāļāļāđāđāļāļĒāļāļĢāļ
āļāļģāļāļēāļĄāļāļĩāđ 6: āļāļąāļāđāļāļĢāļāļŠāļĢāđāļēāļāļāļēāļĢāļāļāļŠāļāļāđāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāđāļāļāđāļĄāļāļđāļĨāļāļĒāđāļēāļāđāļĢ?
āļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļāļŠāļāļāđāļāđāļāđāļāļāļāđāļŠāļģāļāļąāļāļŠāļģāļŦāļĢāļąāļāļāļģāđāļŦāļāđāļāļĢāļ°āļāļąāļ senior āļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāļāļĩāđāļāļĩāļāđāļ§āļĒāđāļŦāđāļāļāļŠāļāļāđāļāđāļāđāļēāļĒāđāļāļāļļāļāļĢāļ°āļāļąāļ
Unit test āļāļāļ ViewModel
// Unit tests with injected mocks
import XCTest
@testable import MyApp
final class UserViewModelTests: XCTestCase {
private var sut: UserViewModel!
private var mockRepository: MockUserRepository!
override func setUp() {
super.setUp()
mockRepository = MockUserRepository()
sut = UserViewModel(userRepository: mockRepository)
}
override func tearDown() {
sut = nil
mockRepository = nil
super.tearDown()
}
func test_loadUsers_success_updatesUsersArray() async {
// Given
let expectedUsers = [User.mock(), User.mock()]
mockRepository.stubbedUsers = expectedUsers
// When
await sut.loadUsers()
// Then
XCTAssertEqual(sut.users.count, 2)
XCTAssertFalse(sut.isLoading)
XCTAssertNil(sut.errorMessage)
}
func test_loadUsers_failure_setsErrorMessage() async {
// Given
mockRepository.stubbedError = NetworkError.noConnection
// When
await sut.loadUsers()
// Then
XCTAssertTrue(sut.users.isEmpty)
XCTAssertNotNil(sut.errorMessage)
}
}
// Mocks/MockUserRepository.swift
final class MockUserRepository: UserRepositoryProtocol {
var stubbedUsers: [User] = []
var stubbedError: Error?
var fetchUsersCalled = false
func fetchUsers() -> AnyPublisher<[User], Error> {
fetchUsersCalled = true
if let error = stubbedError {
return Fail(error: error).eraseToAnyPublisher()
}
return Just(stubbedUsers)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}Integration test āļāļāļ Use Case
// Integration test verifying business logic
final class GetUsersUseCaseTests: XCTestCase {
func test_execute_sortsUsersBySubscriptionLevel() async throws {
// Given
let freeUser = User(id: "1", email: "free@test.com", fullName: "Free", subscriptionLevel: .free)
let premiumUser = User(id: "2", email: "premium@test.com", fullName: "Premium", subscriptionLevel: .premium)
let mockRepo = MockUserRepository()
mockRepo.stubbedUsers = [freeUser, premiumUser]
let sut = GetUsersUseCase(repository: mockRepo)
// When
let result = try await sut.execute()
// Then - Premium should be first
XCTAssertEqual(result.first?.subscriptionLevel, .premium)
XCTAssertEqual(result.last?.subscriptionLevel, .free)
}
}āļāļģāļāļēāļĄāļāļĩāđ 7: āļāļąāļāļāļēāļĢāļŠāļāļēāļāļ° UI āļāļĩāđāļāļąāļāļāđāļāļāļāļĒāđāļēāļāđāļĢ?
āļāļēāļĢāļāļąāļāļāļēāļĢāļŠāļāļēāļāļ°āļĄāļĩāļāļ§āļēāļĄāļŠāļģāļāļąāļāļŠāļđāļāđāļāđāļāļāļĢāļ°āļāļąāļ senior āđāļāļ§āļāļēāļāļāļĩāđāļĄāļĩāđāļāļĢāļāļŠāļĢāđāļēāļāļāļ°āļāđāļāļāļāļąāļāļāļąāđāļāđāļĨāļ°āļāļģāđāļŦāđāļāļēāļĢ debug āļāđāļēāļĒāļāļķāđāļ
State machine āļāđāļ§āļĒ Enum
// State machine for payment flow
enum CheckoutState: Equatable {
case idle
case loadingCart
case cartLoaded(CartSummary)
case processingPayment
case paymentSucceeded(Receipt)
case paymentFailed(PaymentError)
var isLoading: Bool {
switch self {
case .loadingCart, .processingPayment: return true
default: return false
}
}
}
@MainActor
final class CheckoutViewModel: ObservableObject {
@Published private(set) var state: CheckoutState = .idle
private let cartService: CartServiceProtocol
private let paymentService: PaymentServiceProtocol
init(cartService: CartServiceProtocol, paymentService: PaymentServiceProtocol) {
self.cartService = cartService
self.paymentService = paymentService
}
func loadCart() async {
state = .loadingCart
do {
let summary = try await cartService.getSummary()
state = .cartLoaded(summary)
} catch {
state = .paymentFailed(.cartLoadFailed)
}
}
func confirmPayment() async {
guard case .cartLoaded(let summary) = state else { return }
state = .processingPayment
do {
let receipt = try await paymentService.charge(summary.total)
state = .paymentSucceeded(receipt)
} catch let error as PaymentError {
state = .paymentFailed(error)
} catch {
state = .paymentFailed(.unknown)
}
}
}āđāļāļ§āļāļēāļāļāļĩāđāļāļģāđāļŦāđāļŠāļāļēāļāļ°āļāļĩāđāļāļąāļāđāļĒāđāļāļāļąāļāđāļāđāļāđāļāđāļĄāđāđāļāđ (āđāļāđāļ isLoading = true āļāļĢāđāļāļĄāļāļąāļāđāļŠāļāļāļāđāļāļāļīāļāļāļĨāļēāļ)
āđāļĢāļīāđāļĄāļāļķāļāļāđāļāļĄāđāļĨāļĒ!
āļāļāļŠāļāļāļāļ§āļēāļĄāļĢāļđāđāļāļāļāļāļļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāļŠāļąāļĄāļ āļēāļĐāļāđāđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āļŠāļĢāļļāļ
āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS senior āļāļĢāļ°āđāļĄāļīāļāļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāđāļĨāļ·āļāļāđāļĨāļ°āđāļŦāđāđāļŦāļāļļāļāļĨāļāļąāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāļāļĩāđāđāļŦāļĄāļēāļ°āļāļąāļāļāļĢāļīāļāļ āļāļĢāļ°āđāļāđāļāļŠāļģāļāļąāļ:
Checklist āļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ iOS Senior:
â MVVM āļŠāļģāļŦāļĢāļąāļāđāļāļāļāļāļēāļāļāļĨāļēāļāļāļĩāđāđāļāđ SwiftUI āļŦāļĢāļ·āļ UIKit + Combine â VIPER āļŠāļģāļŦāļĢāļąāļāļāļĩāļĄāļāļāļēāļāđāļŦāļāđāđāļĨāļ°āđāļāđāļĄāļāļāļļāļĢāļāļīāļāļāļĩāđāļāļąāļāļāđāļāļ â Clean Architecture āđāļāļ·āđāļāļāļ§āļēāļĄāđāļāđāļāļāļīāļŠāļĢāļ°āļāļēāļāđāļāļĢāļĄāđāļ§āļīāļĢāđāļ â Dependency Injection āļāļĒāđāļēāļāđāļāđāļāļĢāļ°āļāļāđāļāļ·āđāļāļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļāļŠāļāļ â Coordinator Pattern āđāļāļ·āđāļāđāļĒāļāļāļēāļĢāļāļģāļāļēāļāļāļāļāļāļēāļāđāļĨāđāļĒāļāļĢāđāļāļ·āđāļ â State machine āļŠāļģāļŦāļĢāļąāļāđāļāļĨāļ§āđāļāļĩāđāļāļąāļāļāđāļāļ â āļāļēāļĢāļāļāļŠāļāļāđāļāļāļļāļāļĢāļ°āļāļąāļ: unit, integration, UI
āđāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ āļāđāļāļāđāļŠāļāļāđāļŦāđāđāļŦāđāļ:
- āļāļ§āļēāļĄāđāļāđāļēāđāļāđāļāđāļĢāļ·āđāļāļ trade-off (MVVM āđāļāļāđāļĢāļĩāļĒāļāļāđāļēāļĒ vs VIPER āļāļĩāđāļĄāļĩāđāļāļĢāļāļŠāļĢāđāļēāļ)
- āļāļĢāļ°āļŠāļāļāļēāļĢāļāđāđāļāļīāļāļāļāļīāļāļąāļāļīāļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāļāļēāļāđāļāļĢāđāļāļāļāđāļāļĢāļīāļ
- āļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļĢāļąāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāđāļŦāđāđāļāđāļēāļāļąāļāļāļĢāļīāļāļ (āļāļāļēāļāļāļĩāļĄ, āļāļ§āļēāļĄāļāļąāļāļāđāļāļ)
- āļāļ§āļēāļĄāđāļāļĩāđāļĒāļ§āļāļēāļāđāļāļāļēāļĢāļāļāļŠāļāļāđāļāļāļēāļāļ°āđāļāļāļāđāļāļļāļāļ āļēāļāļāļāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ
āđāļĢāļīāđāļĄāļāļķāļāļāđāļāļĄāđāļĨāļĒ!
āļāļāļŠāļāļāļāļ§āļēāļĄāļĢāļđāđāļāļāļāļāļļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāļŠāļąāļĄāļ āļēāļĐāļāđāđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āđāļāđāļ
āđāļāļĢāđ
āļāļāļāļ§āļēāļĄāļāļĩāđāđāļāļĩāđāļĒāļ§āļāđāļāļ

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ StoreKit 2: āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļāđāļĨāļ°āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ
āđāļāļĩāđāļĒāļ§āļāļēāļāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļāļĩāđāļĒāļ§āļāļąāļ StoreKit 2 āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļ āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ āđāļĨāļ°āļāļēāļĢāļāļģāļāļēāļĢāļāļ·āđāļāđāļāđāļāļāđāļāđāļāđ āļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļ Swift āļāļĩāđāđāļāđāļāļēāļāđāļāđāļāļĢāļīāļ

Swift Testing Framework āļŠāļąāļĄāļ āļēāļĐāļāđ 2026: āļĄāļēāđāļāļĢ #expect āđāļĨāļ° #require āđāļāļĩāļĒāļāļāļąāļ XCTest
āđāļĢāļĩāļĒāļāļĢāļđāđ Swift Testing Framework āđāļŦāļĄāđāļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS: āļĄāļēāđāļāļĢ #expect āđāļĨāļ° #require āļāļēāļĢāļĒāđāļēāļĒāļāļēāļ XCTest āđāļāļāđāļāļīāļĢāđāļāļāļąāđāļāļŠāļđāļāđāļĨāļ°āļāđāļāļāļīāļāļāļĨāļēāļāļāļĩāđāļāļāļāđāļāļĒ

āļŠāļąāļĄāļ āļēāļĐāļāđ iOS Push Notifications 2026: APNs, āđāļāđāļāļ āđāļĨāļ° troubleshooting
āļāļđāđāļĄāļ·āļāđāļāļĢāļĩāļĒāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāļĒāđāļēāļāļāļĢāļāļāđāļ§āļāđāļāļĩāđāļĒāļ§āļāļąāļ Push Notifications, APNs, āļāļēāļĢāļāļąāļāļāļēāļĢāđāļāđāļāļ āđāļĨāļ° troubleshooting āļāļĢāđāļāļĄāļāļģāļāļēāļĄāļĒāļāļāļāļīāļĒāļĄāđāļĨāļ°āļāļģāļāļāļāđāļāļĒāļĨāļ°āđāļāļĩāļĒāļ