Modern iOS geliştirme son yıllarda önemli ölçüde değişti. Bir iOS uygulaması artık yalnızca UIViewController sınıfları, storyboard’lar, delegate’ler ve callback tabanlı network isteklerinden oluşmuyor.
Production uygulamaları asynchronous API’ler, concurrency ve thread safety, karmaşık navigation, offline davranışı, persistence, test, modularization, design system, analytics, gözlemlenebilirlik, App Store release’leri ve legacy UIKit koduyla birlikte yaşamak zorunda.
Production bir uygulama için hedef yalnızca ekranı çalıştırmak olmamalıdır. Hedef; ürün büyürken sürdürülebilir, test edilebilir, gözlemlenebilir, ölçeklenebilir ve anlaşılabilir kalan bir yazılım üretmektir.
Kullanıcı
│
▼
SwiftUI View
│ User Actions
▼
State / ViewModel
│ async/await
▼
Use Case / Service
▼
Repository
┌───────────────┐
▼ ▼
Remote API Local Data
URLSession SwiftData / Core Data
Bu tek doğru mimari değildir. Teknolojilerin kendisi de mimari değildir. Asıl soru şudur: Hangi sorumluluk nerede yaşamalıdır?
1. Temel Taş Olarak Swift
Native iOS geliştirmede yeni kod için varsayılan dil Swift olmalıdır. Swift; value type’lar, optionals, associated value içeren enum’lar, protocol’ler, generic’ler, async/await, actor’lar, Sendable ve pattern matching gibi özelliklerle state ve domain model’lerini daha güvenli tasarlamanıza yardımcı olur.
Örneğin ekran durumunu açıkça modelleyebilirsiniz:
enum DeviceState {
case loading
case loaded([Device])
case error(String)
}
Bunun yerine isLoading, hasError ve cihaz listesi gibi birbirinden bağımsız değişkenler tutmak çelişkili durumlar üretebilir. Enum kullanıldığında yalnızca geçerli state’ler ifade edilir ve UI kararları daha anlaşılır hâle gelir.
2. Yeni UI İçin Genellikle SwiftUI Varsayılan Olmalı
SwiftUI, yeni Apple-platformu arayüzleri için doğal başlangıç noktası hâline geldi. Imperative yaklaşımda label bulup değiştirmek, spinner’ı gizlemek, tabloyu yenilemek ve butonu etkinleştirmek gerekir. SwiftUI ise declarative bir model önerir:
State
↓
UI
struct DeviceScreen: View {
let state: DeviceState
var body: some View {
switch state {
case .loading:
ProgressView()
case .loaded(let devices):
DeviceList(devices: devices)
case .error(let message):
Text(message)
}
}
}
Arayüz, mevcut state için neyin gösterilmesi gerektiğini tanımlar. State değiştiğinde SwiftUI ilgili view hiyerarşisini günceller. Bu, uygulama state’i ile arayüz arasındaki manuel senkronizasyonu azaltır.
3. UIKit Ölmedi
Modern iOS projesi demek SwiftUI her yerde, UIKit hiçbir yerde demek değildir. Olgun production uygulamalarında çoğu zaman SwiftUI, UIKit, legacy bileşenler ve üçüncü parti SDK’lar birlikte bulunur.
SwiftUI Feature
│
▼
UIViewControllerRepresentable
│
▼
Existing UIKit Component
Senior bir iOS mühendisi iki framework’ü de anlamalıdır. UIKit bilgisi mevcut production uygulamalarında, özel collection layout’larında, gelişmiş text işleme ihtiyaçlarında, navigation stack’lerinde, eski SDK entegrasyonlarında ve kademeli SwiftUI migration’ında hâlâ çok değerlidir.
Sağlıklı migration stratejisi genellikle mevcut UIKit feature’larını stabil tutmak, yeni ekranlarda SwiftUI kullanmak ve değer kattığı yerleri adım adım dönüştürmektir. Tüm uygulamayı bir gecede yeniden yazmak değildir.
4. Business Logic SwiftUI View’larının Dışında Kalmalı
SwiftUI view’ları öncelikle state’i render etmeli ve kullanıcı aksiyonlarını göndermelidir. HTTP istekleri, database erişimi, authentication, business rules, analytics kararları ve büyük state machine’ler view içine doldurulmamalıdır.
SwiftUI View
│ Action
▼
ViewModel / State Holder
▼
Use Case / Service
▼
Repository / API
struct CheckoutView: View {
let viewModel: CheckoutViewModel
var body: some View {
Button("Pay") {
Task { await viewModel.pay() }
}
}
}
View sunumla sınırlı kaldığında preview, test, yeniden kullanım ve bağımsız değişiklik yapmak kolaylaşır.
5. State Yönetimi Mimari İsminden Daha Önemlidir
MVVM, Clean Architecture, TCA veya başka bir yaklaşım seçmeden önce şu soruları cevaplayın: State nerede yaşar? Kim değiştirebilir? UI state’i nasıl gözlemler? Side effect’ler nerede çalışır? Veri uygulama içinde nasıl akar?
View
│ Action
▼
ViewModel
│ calls
▼
Service / Use Case
▼
Repository
@Observable
@MainActor
final class DeviceViewModel {
private(set) var state: DeviceState = .loading
private let repository: DeviceRepository
init(repository: DeviceRepository) {
self.repository = repository
}
func load() async {
state = .loading
do {
state = .loaded(try await repository.devices())
} catch {
state = .error(error.localizedDescription)
}
}
}
Architecture’ın amacı state ve side effect’leri öngörülebilir hâle getirmektir.
6. Observation ve Modern SwiftUI State
Modern SwiftUI içinde @State, @Binding, @Environment ve @Observable farklı problemleri çözer. View’ın doğrudan sahip olduğu küçük state için @State, parent tarafından sahiplenilen state için @Binding, paylaşılan dependency’ler için @Environment, ekran modelinin gözlemlenmesi için @Observable kullanılabilir.
@State private var isPresented = false
@Binding var isEnabled: Bool
@Observable
final class ProfileModel {
var name = ""
var isLoading = false
}
Yararlı kural şudur: State’i mümkün olduğunca yerel, gerektiği kadar ortak tutun. Her property’yi global observable state’e taşımak coupling’i artırır.
7. Swift Concurrency Varsayılan Zihinsel Model Olmalı
Networking, database erişimi, dosya işlemleri, image loading, authentication, media processing, background work, Bluetooth, location ve cloud synchronization asynchronous olabilir. Main thread’i bloklamak kabul edilemez.
func devices() async throws -> [Device] {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Device].self, from: data)
}
Caller tarafında let devices = try await repository.devices() gibi anlaşılır bir akış oluşur. Swift concurrency; async/await’in yanında actor isolation ve Sendable kontrolleriyle concurrency domain’leri arasında güvenli veri paylaşımını destekler.
8. @MainActor Kullanımını Anlayın
UI state’i çoğunlukla main actor’a aittir.
@MainActor
final class DeviceViewModel {
var devices: [Device] = []
func load() async throws {
devices = try await repository.devices()
}
}
Bu, mutable UI state’in main actor üzerinde tutulduğu garantisini verir. Ancak @MainActor her yere düşünmeden eklenmemelidir. CPU yoğun hesaplama, image processing, audio processing veya büyük parsing işleri farklı isolation gerektirebilir. Amaç her şeyi main actor’a koymak değil, concurrency sahipliğini açıkça ifade etmektir.
9. Paylaşılan Mutable State İçin Actor’lar
Aynı token store’a birden fazla task erişiyorsa yarış durumu oluşabilir. Actor, paylaşılan state’i izole eder:
actor TokenStore {
private var token: String?
func update(_ token: String) {
self.token = token
}
func currentToken() -> String? {
token
}
}
Actor’lar authentication state’i, cache’ler, synchronization engine’leri, websocket state’i ve diğer paylaşılan mutable kaynaklar için yararlıdır. Swift’in daha sıkı concurrency kontrolleri bu sınırları production kodunda daha da önemli hâle getiriyor.
10. Networking İçin Ayrı Bir Client Katmanı Kullanın
View HTTP’nin nasıl çalıştığını bilmemeli; ViewModel da mümkün olduğunca az bilmeli.
View
↓
ViewModel
↓
Repository
↓
API Client
↓
URLSession
protocol APIClient {
func send<T: Decodable>(_ request: URLRequest) async throws -> T
}
final class URLSessionAPIClient: APIClient {
func send<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse,
200..<300 ~= http.statusCode else {
throw APIError.invalidResponse
}
return try JSONDecoder().decode(T.self, from: data)
}
}
Bu yapı test, authentication, retry, logging, mocking ve API migration süreçlerini kolaylaştırır.
11. REST ve GraphQL İkisi de Yaygındır
Production iOS uygulamaları REST, GraphQL, WebSocket ve streaming API’lerini kullanabilir. Ekranları doğrudan transport teknolojisine bağlamak yerine domain ve repository sınırlarını koruyun.
UI
↓
Domain
↓
Repository
↓
REST / GraphQL implementation
Transport değiştiğinde SwiftUI ekranlarını değiştirmek zorunda kalmamak, bu ayrımın önemli faydalarından biridir.
12. Repository Pattern
UI verinin REST API’den mi, GraphQL’den mi, Core Data’dan mı, SwiftData’dan mı, cache’ten mi, CloudKit’ten mi veya Keychain’den mi geldiğini bilmemelidir.
protocol DeviceRepository {
func devices() async throws -> [Device]
func device(id: Device.ID) async throws -> Device
}
Repository, remote ve local kaynakları koordine eden sınırdır. Offline-capable bir uygulamada server’dan gelen veriyi yerel database’e yazar, UI ise yerel state’i gözlemler.
13. SwiftData, Core Data, Keychain ve Dosyalar Farklı Problemleri Çözer
Her veriyi aynı storage sistemine koymayın.
- SwiftData: deployment ve feature kısıtları uygunsa modern model persistence için kullanışlıdır.
- Core Data: mevcut production uygulamaları, karmaşık migration’lar ve uzun ömürlü codebase’ler için hâlâ önemlidir.
- UserDefaults: tema, sıralama, onboarding flag’i ve küçük ayarlar içindir; büyük database değildir.
- Keychain: authentication token’ları ve gizli credential’lar için kullanılır.
- Dosyalar: audio, video, image, download ve büyük binary kaynaklar içindir.
Storage seçimini framework alışkanlığına göre değil, verinin doğasına göre yapın.
14. Offline-First Bir Mimari Karardır
Mobil bağlantı güvenilir değildir. Sağlam bir tasarım yalnızca internet var ve request başarılı varsayımına dayanmaz; offline, yavaş bağlantı, timeout, stale cache, conflict, retry ve expired session durumlarını da tasarlar.
Server
▲
│ Sync
▼
UI ─► Repository ─► Local Database
UI çoğunlukla local state’i gözlemler, repository remote state ile senkronizasyonu yürütür. Conflict resolution’ın server mı yoksa client mı authoritative olacak sorusuyla başlaması gerekir.
15. Navigation da Uygulama State’idir
Navigation başlangıçta NavigationStack ile basit görünebilir. Ancak büyük uygulamalarda deep link, authentication, tab’ler, sheet’ler, push notification’lar, external URL’ler, nested flow’lar ve state restoration devreye girer.
enum Route: Hashable {
case profile
case device(UUID)
case settings
}
@State private var path: [Route] = []
Daha büyük uygulamalarda Coordinator, Router veya Navigation Store kullanılabilir. Önemli olan isim değil, navigation hiyerarşisinin büyüdükçe anlaşılır kalmasıdır.
16. Dependency Injection Büyük Bir Framework Gerektirmez
ViewModel’ın kendi repository’sini oluşturması test ve replacement işlemlerini zorlaştırır. Constructor injection ile dependency dışarıdan verilebilir.
final class DeviceViewModel {
private let repository: DeviceRepository
init(repository: DeviceRepository) {
self.repository = repository
}
}
Production ortamında gerçek repository, testlerde ise mock repository sağlanabilir. Dependency injection loose coupling, test edilebilirlik ve net dependency’ler sağlar. Constructor injection çoğu zaman büyük bir DI framework’ünden önce değerlendirilmelidir.
17. Clean Architecture Her Fonksiyonu Katmana Çevirmek Değildir
Büyük bir iOS uygulamasında Presentation, Domain, Data ve Infrastructure ayrımı yararlı olabilir.
Presentation
├── SwiftUI
├── ViewModel
└── Navigation
Domain
├── Models
├── Use Cases
└── Repository Protocols
Data
├── Repository Implementations
├── DTOs
└── Persistence
Ancak basit bir işlem için View → ViewModel → UseCase → Interactor → Manager → Repository → DataSource → Service → APIClient zinciri oluşturmayın. Mimari karmaşıklığı kontrol etmek içindir; yeni karmaşıklık üretmek için değil.
18. MVVM Yaygındır, Fakat Bir Din Değildir
MVVM iOS ilanlarında sık görülen bir terimdir. Basit modelde ViewModel presentation state’i hazırlar ve View bu state’i render eder.
Anti-pattern ise networking, navigation, analytics, persistence, validation, payment ve business rule’ları tek bir 2.000 satırlık ViewModel’a doldurmaktır. ViewModel presentation’ı koordine etmeli; karmaşık yetenekler ayrı service veya use case’lere taşınmalıdır.
19. TCA Gerçek Bir Problemi Çözdüğünde Değerlidir
The Composable Architecture; State, Action, Reducer, Effect ve Dependency kavramlarını açık biçimde yönetmek isteyen ekipler için değerli olabilir.
Action
↓
Reducer
↓
State
↓
View
Büyük uygulamalarda öngörülebilirlik sağlayabilir. Küçük bir uygulamaya yalnızca sofistike göründüğü için eklenirse gereksiz karmaşıklık yaratır. TCA’yı ekip modelinden fayda gördüğü için kullanın; senior projelerde geçiyor diye otomatik olarak seçmeyin.
20. Swift Package Manager ile Modularization
Tek bir target içinde her şeyi tutmak uygulama büyüdükçe zorlaşır.
App
├── FeatureHome
├── FeatureProfile
├── DesignSystem
├── Networking
└── Persistence
Local Swift Package’lar daha net dependency sınırları, ekip ownership’i, tekrar kullanılabilir bileşenler, izole testler ve daha iyi build organizasyonu sağlayabilir.
Ancak 60 package otomatik olarak iyi mimari demek değildir. Modularization gerçek organizasyonel veya teknik problemleri çözmelidir.
21. Design System Gerçek Bir Engineering Component Olmalı
Büyük ürünlerde her feature’ın button, typography, spacing, color, text field ve loading indicator’ı yeniden üretmesi engellenmelidir.
DesignSystem
├── Colors
├── Typography
├── Spacing
├── Buttons
├── Inputs
├── Cards
└── Components
Feature’lar ortak design system’a bağlandığında consistency, accessibility, brand değişiklikleri ve developer velocity iyileşir.
22. Hata Yönetimi Sonradan Eklenmemeli
Production sistemi yalnızca happy path üzerinde yaşamaz. Network yokluğu, timeout, 401, 403, 500, malformed JSON, expired token, disk doluluğu ve iptal edilmiş Task normal durumlar olarak düşünülmelidir.
enum APIError: Error {
case unauthorized
case unavailable
case invalidResponse
case decoding
case network(Error)
}
Unauthorized login’e, network problemi retry’a, server unavailable geçici hata ekranına ve decoding failure diagnostic raporlamaya yönlenebilir. Hata yönetimi mimarinin bir parçasıdır.
23. Cancellation Async Mimarinin Parçasıdır
Mobil kullanıcılar ekranı açar, hemen çıkar, aramayı başlatır, tekrar yazar, filter değiştirir veya uygulamayı background’a alır. Uzun süren Task’lar çoğu zaman iptal edilebilir olmalıdır.
.task(id: query) {
await search(query)
}
try Task.checkCancellation()
Cancellation’ı yok saymak gereksiz network isteği, eski UI, race condition ve CPU tüketimi üretebilir.
24. Testler Birden Fazla Seviyede Olmalı
UI / E2E
▲
/ \nIntegration
▲
/ \n Unit Tests
- Unit test: business rule, reducer, ViewModel, formatting, validation ve use case.
- Integration test: repository, API, database, authentication ve persistence birlikte çalışırken.
- UI test: login, checkout, navigation ve onboarding gibi kritik kullanıcı yolculukları.
- Snapshot test: design system bileşenleri, görsel regression’lar ve farklı cihaz boyutları.
Test piramidinin amacı her şeyi en üst seviyede test etmek değil, her davranışı uygun maliyetli seviyede korumaktır.
25. Swift Testing ve XCTest
Modern Swift projeleri Swift Testing ve XCTest’i birlikte kullanabilir. Swift Testing; concurrency, parameterized test ve paralel çalıştırma için modern API’ler sunar. XCTest ise mevcut test suite’lerinde, UI automation’da ve olgun projelerde hâlâ önemlidir.
@Test
func totalCalculation() {
let result = calculateTotal(items: [10, 20])
#expect(result == 30)
}
Her XCTest’i hemen yeniden yazmak gerekmez. Migration kademeli yapılabilir.
26. Test Edilebilirlik İçin Tasarlayın
ViewModel’ın gerçek API ve storage nesnelerini kendi içinde oluşturması testleri altyapıya bağımlı hâle getirir. Bunun yerine protocol’leri constructor’dan alın.
final class ProfileViewModel {
private let api: ProfileAPI
private let storage: ProfileStorage
init(api: ProfileAPI, storage: ProfileStorage) {
self.api = api
self.storage = storage
}
}
Testler böylece fake API, fake storage, kontrollü response ve kontrollü failure kullanabilir. Test edilebilirlik architecture tamamlandıktan sonra eklenen bir özellik değil, iyi mimarinin doğal sonucudur.
27. Optimize Etmeden Önce Ölçün
Measure
↓
Bottleneck’i bul
↓
Hypothesis kur
↓
Değiştir
↓
Tekrar ölç
iOS mühendisleri main-thread hang, memory growth, leak, excessive SwiftUI update, rendering hitch, CPU, launch time, network, disk I/O ve pil tüketimini araştırabilmelidir. “SwiftUI yavaş görünüyor” yerine “Bunu ölçtüm, darboğaz burada” diyebilmek gerekir.
28. Memory Management Hâlâ Önemli
Swift Automatic Reference Counting kullanır; bu memory problemlerinin ortadan kalktığı anlamına gelmez.
player.onFinish = { [weak self] in
self?.showNextTrack()
}
Strong reference, weak reference, unowned reference, retain cycle ve object lifetime kavramlarını özellikle delegate, callback, media, Combine, Objective-C interoperability ve uzun ömürlü service’lerde bilmek gerekir.
29. Production Gözlemlenebilirliği Önemlidir
Testler production’daki her koşulu kapsayamaz. Farklı cihazlar, OS sürümleri, zayıf network, düşük storage, memory pressure ve beklenmedik kullanıcı akışları gerçek dünyada ortaya çıkar.
Ship
↓
Observe
↓
Measure
↓
Improve
Crash report, performance metric, analytics, log ve feature metric’leri toplanmalıdır. MetricKit, Crashlytics, Sentry, Datadog veya New Relic gibi araçlar organizasyonun ihtiyaçlarına göre kullanılabilir.
30. CI/CD Mimarinin Parçasıdır
Modern iOS repository’si “benim Mac’imde çalışıyor” yaklaşımına bağlı kalmamalıdır.
Pull Request
│
▼
Resolve Packages
▼
Build → Lint → Tests
▼
Archive
Release akışı merge, build, sign, upload, TestFlight, QA ve App Store adımlarından oluşabilir. GitHub Actions, Bitrise, Fastlane, Xcode Cloud ve gerektiğinde Tuist kullanılabilir. Amaç çok sayıda araç biriktirmek değil, yanlış değişiklikleri yayınlamayı zorlaştırıp doğru değişiklikleri release etmeyi kolaylaştırmaktır.
31. App Store Delivery iOS Engineering’in Parçasıdır
Production sorumluluğu kod merge edildiğinde bitmez. Versioning, signing, provisioning, TestFlight, App Store Connect, App Review, entitlements, privacy manifest, rollout, crash monitoring ve rollback stratejisi anlaşılmalıdır.
Consumer ürünlerde bir feature’ı production’a kadar sahiplenmek, yalnızca implementasyon yapmak değil; release sonrasındaki davranışı izlemek ve gerektiğinde müdahale etmektir.
32. Objective-C Legacy’dir, Fakat Değerlidir
Yeni kod genellikle Swift ile yazılır; ancak olgun iOS uygulamalarında Objective-C, Objective-C++, C veya C++ bulunabilir. Deneyimli bir iOS mühendisi Objective-C okuyabilmeli, mixed project debug edebilmeli, bridging header’ları ve Objective-C API’lerini anlayabilmelidir.
Yeni feature’ları Objective-C ile yazmanız gerekmez. Fakat mevcut codebase içinde çalışamamak senior roller için sınırlayıcı olabilir.
33. C++ ve Objective-C++ Ne Zaman Anlamlıdır?
Çoğu iOS uygulamasının C++’a ihtiyacı yoktur. Audio/DSP, computer vision, game engine, mevcut C++ SDK’ları, performans hassas algoritmalar ve cross-platform native library’ler geçerli kullanım alanlarıdır.
Swift
│
▼
Objective-C++
│
▼
C++
Sınırı küçük tutun ve karmaşık C++ ownership kurallarını Swift codebase’inin her yerine yaymayın.
34. AI iOS Engineering Workflow’ünün Parçası Oluyor
AI-assisted development artık mühendislik akışının bir parçası hâline geliyor. Claude Code, Codex, Copilot, AI code review ve agentic workflow’ler üretkenliği artırabilir.
Mühendis problemi tanımlar
↓
AI implementasyona yardım eder
↓
Mühendis review yapar
↓
Testler doğrular
↓
Production metric’leri sonucu gösterir
Sağlıklı model AI’ın kod yazması ve doğrudan merge edilmesi değildir. AI hızlandırıcı olabilir; mühendislik yargısını, test sorumluluğunu ve production sahipliğini devralmamalıdır.
35. Pratik Bir Modern iOS Stack’i
Dil
└── Swift
UI
├── SwiftUI
└── Gerektiğinde UIKit
State
├── Observation / @Observable
├── @State
└── @Environment
Concurrency
├── async / await
├── Task
├── Actors
└── Sendable
Architecture
├── Gerektiğinde MVVM
├── Unidirectional Data Flow
├── Repository Pattern
├── Gerektiğinde Clean Architecture
└── Fayda sağlayan ekiplerde TCA
Networking
├── URLSession
├── Codable
├── REST
└── Gerektiğinde GraphQL
Persistence
├── SwiftData
├── Core Data
├── Keychain
├── UserDefaults
└── File storage
Modularization
├── Local Swift Packages
├── Feature modules
└── Core modules
Testing
├── Swift Testing
├── XCTest
├── UI / Integration Tests
└── Snapshot Tests
Build ve dağıtım
├── Xcode
├── GitHub Actions / Bitrise / Xcode Cloud
├── Fastlane
└── TestFlight / App Store Connect
Tekrar hatırlatalım: Stack mimarinin kendisi değildir. Framework’ler değişir; sistemi sürdürülebilir yapan sorumluluk sınırlarıdır.
36. Büyük Resim
USER
│
▼
┌─────────────────┐
│ SwiftUI │
│ View │
└────────┬────────┘
│ Actions
▼
┌─────────────────┐
│ State / │
│ ViewModel │
└────────┬────────┘
│ async/await
▼
┌─────────────────┐
│ Use Case / │
│ Domain Service │
└────────┬────────┘
▼
┌─────────────────┐
│ Repository │
└────────┬────────┘
┌───────┴───────┐
▼ ▼
Remote API Local Data
URLSession SwiftData / Core Data
Bu çekirdeğin çevresinde Dependency Injection, Testing, Navigation, Design System, Logging, Analytics, Performance Monitoring, CI/CD ve App Store Delivery bulunur.
İyi bir proje şu sorulara kolay cevap verebilmelidir: UI state nerede yaşar? Network çağrısının sahibi kimdir? Request başarısız olunca ne olur? Bu bileşen network olmadan test edilebilir mi? Task iptal edilirse ne olur? Mutable state’in sahibi hangi actor’dır? Feature offline çalışabilir mi? Navigation’ın sahibi kimdir? Bu modül bağımsız değiştirilebilir mi? Production regression’ı nasıl fark ederiz? Kod TestFlight’a nasıl ulaşır?
Sonuç
İyi iOS mimarisinin amacı en etkileyici folder yapısını kurmak değildir. Amaç değişikliği güvenli hâle getirmektir.
İyi yapılandırılmış bir iOS projesi ekibin feature eklemesini, altyapıyı değiştirmesini, davranışları test etmesini, production problemlerini araştırmasını ve yeni Apple framework’lerini tüm uygulamayı yeniden yazmadan benimsemesini sağlar.
Özetle modern iOS geliştirme Swift veya SwiftUI kullanmaktan ibaret değildir. UIKit ile yaşamayı, concurrency sahipliğini, state akışını, navigation’ı, persistence’ı, testleri, modularization’ı, performansı, observability’yi ve App Store delivery’yi birlikte tasarlamaktır.