Tüm Yazılar
KategoriArchitecture
Okuma Süresi
21 dk
Yayın Tarihi
...
Kelime Sayısı
1.417kelime

Kahveni hazırla - bu içerikli bir makale!

Büyük iOS projelerini modüllere ayırın. SPM ile dependency management, build time optimization ve team collaboration.

Modular iOS Architecture ve Swift Package Manager

Büyük iOS projeleri zamanla yönetilemez hale gelebilir. Modular architecture, projenizi bağımsız, test edilebilir ve yeniden kullanılabilir modüllere ayırarak bu sorunu çözer.


İçindekiler


Monolith vs Modular Mimari Karsilastirmasi

Ozellik
Monolith
Modular (SPM)
Modular (CocoaPods)
Micro-Feature
**Build Suresi (ilk)**
Uzun
Orta
Uzun
Kisa
**Incremental Build**
Yavas
Hizli
Orta
Cok hizli
**Ekip Olceklenmesi**
Zor
Iyi
Iyi
Mukemmel
**Code Sharing**
Zor
Kolay
Kolay
Kolay
**Test Izolasyonu**
Zor
Iyi
Iyi
Mukemmel
**Dependency Yonetimi**
Basit
Orta
Karmasik
Karmasik
**CI/CD Suresi**
Uzun
Orta
Uzun
Kisa
**Setup Zorlugu**
Kolay
Orta
Kolay
Zor
**IDE Performansi**
Dusuk
Iyi
Orta
Iyi
50.000+ satir kod iceren projelerde modular mimariye gecis build surelerini %40-60 azaltabilir. Ozellikle 5+ kisilik ekiplerde modular yaklasim neredeyse zorunludur.

Neden Modular Architecture?

  1. Build time azalması: Sadece değişen modüller yeniden derlenir
  2. Team collaboration: Farklı ekipler farklı modüllerde çalışabilir
  3. Code reuse: Modüller farklı projelerde kullanılabilir
  4. Test isolation: Her modül bağımsız test edilebilir
  5. Clear boundaries: Sorumluluklar net ayrılmış olur

Modül Yapısı

swift
1MyApp/
2├── App/ # Ana uygulama target
3├── Packages/
4│ ├── Core/ # Temel utilities
5│ │ ├── Sources/
6│ │ └── Tests/
7│ ├── Networking/ # API katmanı
8│ │ ├── Sources/
9│ │ └── Tests/
10│ ├── DesignSystem/ # UI components
11│ │ ├── Sources/
12│ │ └── Tests/
13│ ├── Features/
14│ │ ├── Home/
15│ │ ├── Profile/
16│ │ ├── Search/
17│ │ └── Settings/
18│ └── Domain/ # Business logic
19│ ├── Sources/
20│ └── Tests/
21└── Package.swift

Package.swift Yapılandırması

swift
1// swift-tools-version: 5.9
2import PackageDescription
3 
4let package = Package(
5 name: "MyAppModules",
6 platforms: [.iOS(.v17)],
7 products: [
8 // Core modules
9 .library(name: "Core", targets: ["Core"]),
10 .library(name: "Networking", targets: ["Networking"]),
11 .library(name: "DesignSystem", targets: ["DesignSystem"]),
12 .library(name: "Domain", targets: ["Domain"]),
13
14 // Feature modules
15 .library(name: "HomeFeature", targets: ["HomeFeature"]),
16 .library(name: "ProfileFeature", targets: ["ProfileFeature"]),
17 .library(name: "SearchFeature", targets: ["SearchFeature"]),
18 ],
19 dependencies: [
20 .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
21 .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.10.0"),
22 ],
23 targets: [
24 // Core
25 .target(
26 name: "Core",
27 dependencies: [],
28 path: "Packages/Core/Sources"
29 ),
30 .testTarget(
31 name: "CoreTests",
32 dependencies: ["Core"],
33 path: "Packages/Core/Tests"
34 ),
35
36 // Networking
37 .target(
38 name: "Networking",
39 dependencies: ["Core", "Alamofire"],
40 path: "Packages/Networking/Sources"
41 ),
42
43 // DesignSystem
44 .target(
45 name: "DesignSystem",
46 dependencies: ["Core"],
47 path: "Packages/DesignSystem/Sources",
48 resources: [.process("Resources")]
49 ),
50
51 // Domain
52 .target(
53 name: "Domain",
54 dependencies: ["Core", "Networking"],
55 path: "Packages/Domain/Sources"
56 ),
57
58 // Features
59 .target(
60 name: "HomeFeature",
61 dependencies: ["Core", "Domain", "DesignSystem", "Kingfisher"],
62 path: "Packages/Features/Home/Sources"
63 ),
64 .target(
65 name: "ProfileFeature",
66 dependencies: ["Core", "Domain", "DesignSystem"],
67 path: "Packages/Features/Profile/Sources"
68 ),
69 ]
70)

Dependency Injection

swift
1// Core/Sources/DI/Container.swift
2public protocol DependencyContainer {
3 func resolve<T>(_ type: T.Type) -> T
4 func register<T>(_ type: T.Type, factory: @escaping() -> T)
5}
6 
7public final class AppContainer: DependencyContainer {
8 public static let shared = AppContainer()
9
10 private var factories: [String: () -> Any] = [:]
11 private var singletons: [String: Any] = [:]
12
13 public func register<T>(_ type: T.Type, factory: @escaping() -> T) {
14 let key = String(describing: type)
15 factories[key] = factory
16 }
17
18 public func registerSingleton<T>(_ type: T.Type, factory: @escaping() -> T) {
19 let key = String(describing: type)
20 singletons[key] = factory()
21 }
22
23 public func resolve<T>(_ type: T.Type) -> T {
24 let key = String(describing: type)
25
26 if let singleton = singletons[key] as? T {
27 return singleton
28 }
29
30 guard let factory = factories[key],
31 let instance = factory() as? T else {
32 fatalError("No registration for \(key)")
33 }
34
35 return instance
36 }
37}
38 
39// Usage
40extension AppContainer {
41 public static func bootstrap() {
42 let container = AppContainer.shared
43
44 // Register services
45 container.registerSingleton(NetworkServiceProtocol.self) {
46 NetworkService()
47 }
48
49 container.register(UserRepositoryProtocol.self) {
50 UserRepository(networkService: container.resolve(NetworkServiceProtocol.self))
51 }
52 }
53}

Feature Module Örneği

swift
1// HomeFeature/Sources/HomeFeature.swift
2import SwiftUI
3import Core
4import Domain
5import DesignSystem
6 
7public struct HomeFeature {
8 public static func makeHomeView() -> some View {
9 let viewModel = HomeViewModel(
10 productRepository: AppContainer.shared.resolve(ProductRepositoryProtocol.self),
11 userRepository: AppContainer.shared.resolve(UserRepositoryProtocol.self)
12 )
13 return HomeView(viewModel: viewModel)
14 }
15}
16 
17// HomeFeature/Sources/HomeView.swift
18struct HomeView: View {
19 @StateObject var viewModel: HomeViewModel
20
21 var body: some View {
22 ScrollView {
23 LazyVStack(spacing: 16) {
24 // Featured section
25 FeaturedCarousel(items: viewModel.featured)
26
27 // Products grid
28 ProductGrid(products: viewModel.products)
29 }
30 }
31 .task {
32 await viewModel.loadData()
33 }
34 }
35}

Build Time Optimization

swift
1// Package.swift - Parallel builds
2targets: [
3 .target(
4 name: "FeatureA",
5 dependencies: ["Core"], // Minimal dependencies
6 swiftSettings: [
7 .unsafeFlags(["-Xfrontend", "-warn-long-function-bodies=100"])
8 ]
9 )
10]
11 
12// Xcode Build Settings
13// SWIFT_WHOLE_MODULE_OPTIMIZATION = YES (Release)
14// SWIFT_COMPILATION_MODE = wholemodule

Modul Iletisim Stratejileri

Modular mimaride en buyuk zorluk, moduller arasi iletisimi dogru kurmaktir. Moduller birbirine dogrudan bagimli olmamali, bunun yerine interface (protokol) uzerinden iletisim kurmalidir.

Interface Module Pattern

swift
1// NetworkInterface/Sources/NetworkServiceProtocol.swift
2// Bu modul sadece protokolleri icerir - implementasyon yok
3public protocol NetworkServiceProtocol: Sendable {
4 func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T
5 func upload(data: Data, to endpoint: Endpoint) async throws -> UploadResult
6}
7 
8public protocol Endpoint: Sendable {
9 var path: String { get }
10 var method: HTTPMethod { get }
11 var headers: [String: String] { get }
12}
13 
14// Networking/Sources/NetworkService.swift
15// Concrete implementasyon ayri bir modulde
16import NetworkInterface
17 
18public final class NetworkService: NetworkServiceProtocol {
19 public func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
20 // Gercek implementasyon
21 let url = baseURL.appendingPathComponent(endpoint.path)
22 let (data, _) = try await URLSession.shared.data(from: url)
23 return try JSONDecoder().decode(T.self, from: data)
24 }
25 
26 public func upload(data: Data, to endpoint: Endpoint) async throws -> UploadResult {
27 // Upload implementasyonu
28 fatalError("Implement upload logic")
29 }
30}

Event Bus ile Loose Coupling

swift
1// Core/Sources/EventBus.swift
2public protocol AppEvent: Sendable {}
3 
4public final class EventBus: @unchecked Sendable {
5 public static let shared = EventBus()
6 
7 private var handlers: [String: [(Any) -> Void]] = [:]
8 private let queue = DispatchQueue(label: "eventbus", attributes: .concurrent)
9 
10 public func subscribe<T: AppEvent>(to eventType: T.Type, handler: @escaping(T) -> Void) {
11 let key = String(describing: eventType)
12 queue.async(flags: .barrier) {
13 self.handlers[key, default: []].append { event in
14 if let typedEvent = event as? T {
15 handler(typedEvent)
16 }
17 }
18 }
19 }
20 
21 public func publish<T: AppEvent>(_ event: T) {
22 let key = String(describing: T.self)
23 queue.sync {
24 self.handlers[key]?.forEach { handler in
25 handler(event)
26 }
27 }
28 }
29}
30 
31// Kullanim - Feature modulleri birbirini bilmeden iletisim kurabilir
32struct UserLoggedInEvent: AppEvent {
33 let userId: String
34}
35 
36// ProfileFeature'da
37EventBus.shared.publish(UserLoggedInEvent(userId: "123"))
38 
39// HomeFeature'da
40EventBus.shared.subscribe(to: UserLoggedInEvent.self) { event in
41 refreshHomeData(for: event.userId)
42}

Bu pattern sayesinde feature modulleri arasinda compile-time bagimliligi sifira iner. Her modul sadece Core moduldeki EventBus'a bagimlidir ve diger modullerin varligindan haberdar olmak zorunda degildir.


Sonuç

Modular architecture, büyük iOS projelerinin ölçeklenebilirliğini ve bakım kolaylığını dramatik şekilde artırır. SPM ile dependency management, feature-based modüller ve proper DI kullanarak profesyonel seviyede bir proje yapısı oluşturabilirsiniz.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Etiketler

#Architecture#SPM#Modular#iOS#Swift Package Manager#Dependency
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz