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 Karsilastirmasi
- Neden Modular Architecture?
- Modul Yapisi
- Package.swift Yapilandirmasi
- Dependency Injection
- Feature Module Ornegi
- Build Time Optimization
- Modul Iletisim Stratejileri
- Sonuç
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?
- Build time azalması: Sadece değişen modüller yeniden derlenir
- Team collaboration: Farklı ekipler farklı modüllerde çalışabilir
- Code reuse: Modüller farklı projelerde kullanılabilir
- Test isolation: Her modül bağımsız test edilebilir
- Clear boundaries: Sorumluluklar net ayrılmış olur
Modül Yapısı
swift
1MyApp/2├── App/ # Ana uygulama target3├── Packages/4│ ├── Core/ # Temel utilities5│ │ ├── Sources/6│ │ └── Tests/7│ ├── Networking/ # API katmanı8│ │ ├── Sources/9│ │ └── Tests/10│ ├── DesignSystem/ # UI components11│ │ ├── Sources/12│ │ └── Tests/13│ ├── Features/14│ │ ├── Home/15│ │ ├── Profile/16│ │ ├── Search/17│ │ └── Settings/18│ └── Domain/ # Business logic19│ ├── Sources/20│ └── Tests/21└── Package.swiftPackage.swift Yapılandırması
swift
1// swift-tools-version: 5.92import PackageDescription3 4let package = Package(5 name: "MyAppModules",6 platforms: [.iOS(.v17)],7 products: [8 // Core modules9 .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 modules15 .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 // Core25 .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 // Networking37 .target(38 name: "Networking",39 dependencies: ["Core", "Alamofire"],40 path: "Packages/Networking/Sources"41 ),42 43 // DesignSystem44 .target(45 name: "DesignSystem",46 dependencies: ["Core"],47 path: "Packages/DesignSystem/Sources",48 resources: [.process("Resources")]49 ),50 51 // Domain52 .target(53 name: "Domain",54 dependencies: ["Core", "Networking"],55 path: "Packages/Domain/Sources"56 ),57 58 // Features59 .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.swift2public protocol DependencyContainer {3 func resolve<T>(_ type: T.Type) -> T4 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] = factory16 }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 singleton28 }29 30 guard let factory = factories[key],31 let instance = factory() as? T else {32 fatalError("No registration for \(key)")33 }34 35 return instance36 }37}38 39// Usage40extension AppContainer {41 public static func bootstrap() {42 let container = AppContainer.shared43 44 // Register services45 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.swift2import SwiftUI3import Core4import Domain5import DesignSystem6 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.swift18struct HomeView: View {19 @StateObject var viewModel: HomeViewModel20 21 var body: some View {22 ScrollView {23 LazyVStack(spacing: 16) {24 // Featured section25 FeaturedCarousel(items: viewModel.featured)26 27 // Products grid28 ProductGrid(products: viewModel.products)29 }30 }31 .task {32 await viewModel.loadData()33 }34 }35}Build Time Optimization
swift
1// Package.swift - Parallel builds2targets: [3 .target(4 name: "FeatureA",5 dependencies: ["Core"], // Minimal dependencies6 swiftSettings: [7 .unsafeFlags(["-Xfrontend", "-warn-long-function-bodies=100"])8 ]9 )10]11 12// Xcode Build Settings13// SWIFT_WHOLE_MODULE_OPTIMIZATION = YES (Release)14// SWIFT_COMPILATION_MODE = wholemoduleModul 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.swift2// Bu modul sadece protokolleri icerir - implementasyon yok3public protocol NetworkServiceProtocol: Sendable {4 func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T5 func upload(data: Data, to endpoint: Endpoint) async throws -> UploadResult6}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.swift15// Concrete implementasyon ayri bir modulde16import NetworkInterface17 18public final class NetworkService: NetworkServiceProtocol {19 public func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T {20 // Gercek implementasyon21 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 implementasyonu28 fatalError("Implement upload logic")29 }30}Event Bus ile Loose Coupling
swift
1// Core/Sources/EventBus.swift2public 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 in14 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 in25 handler(event)26 }27 }28 }29}30 31// Kullanim - Feature modulleri birbirini bilmeden iletisim kurabilir32struct UserLoggedInEvent: AppEvent {33 let userId: String34}35 36// ProfileFeature'da37EventBus.shared.publish(UserLoggedInEvent(userId: "123"))38 39// HomeFeature'da40EventBus.shared.subscribe(to: UserLoggedInEvent.self) { event in41 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.

