Projen büyüdükçe build süreleri uzar, merge conflict'ler artar ve yeni özellik eklemek zorlaşır. Çözüm: modular architecture. SPM ile uygulamayı bağımsız modüllere bölerek, ekiplerin paralel çalışmasını, incremental compilation'ı ve yeniden kullanılabilirliği sağlarsın.
💡 Hızlı Not: Bu rehber Apple'ın Swift Package Manager dokümantasyonu ve Airbnb, Uber gibi şirketlerin modularization tecrübelerinden derlendi.
İçindekiler
- Neden Modular Architecture?
- Package.swift Deep Dive
- Modül Tipleri ve Katmanlar
- Feature Module Oluşturma
- Binary Targets ve XCFramework
- SPM Plugins
- Build Performance Optimizasyonu
- SPM vs CocoaPods vs Carthage
- CI/CD Entegrasyonu
- Migration Stratejisi
Neden Modular Architecture? {#neden-modular}
Metrik | Monolitik | Modular |
|---|---|---|
Build süresi (clean) | 5+ dakika | 1-2 dakika (incremental) |
Merge conflict | Çok sık | Nadir |
Code review | Tüm proje | Sadece ilgili modül |
Test süresi | Tüm test suite | Modül bazlı test |
Ekip bağımsızlığı | Düşük | Yüksek |
Yeniden kullanılabilirlik | Zor | Kolay |
Package.swift Deep Dive {#package-swift}
swift
1// swift-tools-version: 5.92import PackageDescription3 4let package = Package(5 name: "MyAppModules",6 defaultLocalization: "tr",7 platforms: [.iOS(.v17), .macOS(.v14)],8 products: [9 // Public API - dışarıya açılan modüller10 .library(name: "Core", targets: ["Core"]),11 .library(name: "Networking", targets: ["Networking"]),12 .library(name: "DesignSystem", targets: ["DesignSystem"]),13 .library(name: "FeatureHome", targets: ["FeatureHome"]),14 .library(name: "FeatureProfile", targets: ["FeatureProfile"]),15 ],16 dependencies: [17 .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.0.0"),18 .package(url: "https://github.com/onevcat/Kingfisher", from: "7.0.0"),19 ],20 targets: [21 // Core - temel tipler, protocol'ler, extensions22 .target(23 name: "Core",24 dependencies: [],25 path: "Sources/Core"26 ),27 // Networking - API client, endpoints28 .target(29 name: "Networking",30 dependencies: ["Core"],31 path: "Sources/Networking"32 ),33 // Design System - UI components, colors, fonts34 .target(35 name: "DesignSystem",36 dependencies: ["Core"],37 path: "Sources/DesignSystem",38 resources: [.process("Resources")]39 ),40 // Feature modules41 .target(42 name: "FeatureHome",43 dependencies: [44 "Core", "Networking", "DesignSystem",45 .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),46 ]47 ),48 // Test targets49 .testTarget(name: "CoreTests", dependencies: ["Core"]),50 .testTarget(name: "NetworkingTests", dependencies: ["Networking"]),51 .testTarget(name: "FeatureHomeTests", dependencies: ["FeatureHome"]),52 ]53)Modül Tipleri ve Katmanlar {#modul-tipleri}
swift
1┌─────────────────────────────────────────┐2│ App │3│ (Composition Root - DI, Navigation) │4├─────────────────────────────────────────┤5│ Feature Modules │6│ ┌──────────┐ ┌──────────┐ ┌──────────┐│7│ │ Home │ │ Profile │ │ Settings ││8│ └──────────┘ └──────────┘ └──────────┘│9├─────────────────────────────────────────┤10│ Shared Modules │11│ ┌──────────┐ ┌──────────┐ │12│ │DesignSys │ │AnalyticsKit│ │13│ └──────────┘ └──────────┘ │14├─────────────────────────────────────────┤15│ Infrastructure │16│ ┌──────────┐ ┌──────────┐ ┌──────────┐│17│ │Networking│ │ Storage │ │ Auth ││18│ └──────────┘ └──────────┘ └──────────┘│19├─────────────────────────────────────────┤20│ Core(Models, Protocols, Extensions) │21└─────────────────────────────────────────┘Kural: Yukarıdaki katmanlar aşağıdakilere bağımlı olabilir, AMA aşağıdakiler yukarıdakilere ASLA bağımlı olamaz.
Feature Module Oluşturma {#feature-module}
swift
1// Sources/FeatureHome/HomeFeature.swift2import Core3import Networking4import DesignSystem5import ComposableArchitecture6 7@Reducer8public struct HomeFeature {9 public struct State: Equatable {10 public var products: [Product] = []11 public var isLoading = false12 public init() {}13 }14 15 public enum Action {16 case onAppear17 case productsLoaded([Product])18 }19 20 @Dependency(\.productClient) var productClient21 22 public init() {}23 24 public var body: some ReducerOf<Self> {25 Reduce { state, action in26 switch action {27 case .onAppear:28 state.isLoading = true29 return .run { send in30 let products = try await productClient.fetchAll()31 await send(.productsLoaded(products))32 }33 case .productsLoaded(let products):34 state.isLoading = false35 state.products = products36 return .none37 }38 }39 }40}Binary Targets ve XCFramework {#binary-targets}
Compile süresini azaltmak için pre-built binary kullan:
swift
1// Package.swift'te binary target2.binaryTarget(3 name: "HeavySDK",4 url: "https://releases.example.com/HeavySDK-1.0.0.xcframework.zip",5 checksum: "abc123..."6)7 8// veya local path9.binaryTarget(10 name: "InternalSDK",11 path: "Binaries/InternalSDK.xcframework"12)SPM Plugins {#plugins}
swift
1// Build tool plugin - SwiftGen, SwiftLint2.plugin(3 name: "SwiftLintPlugin",4 capability: .buildTool(),5 dependencies: ["SwiftLintBinary"]6)7 8// Command plugin - code generation9.plugin(10 name: "GenerateAPI",11 capability: .command(12 intent: .custom(verb: "generate-api", description: "Generate API client"),13 permissions: [.writeToPackageDirectory(reason: "Generate source files")]14 )15)Build Performance Optimizasyonu {#build-perf}
- Incremental builds: Modüler yapı, sadece değişen modülü rebuild eder
- Explicit imports:
@_exported importkullanma — bağımlılık grafiğini bozar - Access control:
internalyerinepublicsadece gerekli API'ler için - Paralel build: Bağımsız modüller paralel derlenir
SPM vs CocoaPods vs Carthage {#karsilastirma}
Özellik | SPM | CocoaPods | Carthage |
|---|---|---|---|
Apple desteği | ✅ Native | ❌ 3rd party | ❌ 3rd party |
Xcode entegrasyonu | ✅ Built-in | ⚠️ xcworkspace | ⚠️ Manual |
Binary cache | ✅ Var | ✅ Var | ✅ Var |
Plugins | ✅ Var | ❌ Yok | ❌ Yok |
Resources | ✅ Var | ✅ Var | ⚠️ Kısıtlı |
ObjC desteği | ✅ Var | ✅ Var | ✅ Var |
Geleceği | 🔝 Büyüyor | ↘️ Azalıyor | ⬇️ Archived |
CI/CD Entegrasyonu {#ci-cd}
yaml
1# GitHub Actions ile modüler test2name: Module Tests3on: [pull_request]4jobs:5 test:6 runs-on: macos-147 strategy:8 matrix:9 module: [Core, Networking, FeatureHome, FeatureProfile]10 steps:11 - uses: actions/checkout@v412 - name: Test ${{ matrix.module }}13 run: swift test --filter ${{ matrix.module }}TestsMigration Stratejisi {#migration}
- Core modülü oluştur — models, protocols, extensions taşı
- Networking modülü — API client'ı bağımsızlaştır
- DesignSystem — UI components, themes, assets
- Feature modülleri — her ekranı bağımsız modüle taşı
- App target — sadece composition root kalsın
Yaygın Hatalar ve Çözümleri
Modular mimariye geçişte en sık yapılan hataları ve çözümlerini bilmek, seni saatler sürecek debug session'larından kurtarır:
swift
1// ❌ HATA 1: Circular dependency2// ModuleA imports ModuleB, ModuleB imports ModuleA3// ÇÖZÜM: Ortak bağımlılığı Core modülüne taşı4 5// ❌ HATA 2: @_exported import ile bulaşıcı bağımlılık6@_exported import Networking // TÜM import edenler Networking'i de alır7// ÇÖZÜM: Explicit import kullan, @_exported kullanma8 9// ❌ HATA 3: Internal access level ile modüller arası erişim10struct UserModel { // internal by default - diğer modüllerden erişilemez11 let name: String12}13// ÇÖZÜM: Public API'leri public yap14public struct UserModel: Sendable {15 public let name: String16 public init(name: String) { self.name = name }17}18 19// ❌ HATA 4: Test target'ında @testable import eksik20import FeatureHome // Sadece public API erişimi21// ÇÖZÜM: @testable ile internal API'lere de eriş22@testable import FeatureHomeModül Boyut Rehberi
Modül Büyüklüğü | Dosya Sayısı | Uygun Olan | Örnek |
|---|---|---|---|
**Mikro** | 1-5 dosya | Utility, extension'lar | Logger, DateFormatter |
**Küçük** | 5-15 dosya | Tek feature, tek servis | NetworkClient, AuthService |
**Orta** | 15-40 dosya | Feature module | FeatureHome, FeatureProfile |
**Büyük** | 40+ dosya | Muhtemelen bölünmeli | Daha küçük modüllere refactor et |
SPM Conditional Dependencies
Platform'a göre farklı bağımlılıklar tanımlayabilirsin:
swift
1targets: [2 .target(3 name: "SharedModule",4 dependencies: [5 .target(name: "Core"),6 .product(name: "Kingfisher", package: "Kingfisher",7 condition: .when(platforms: [.iOS, .macOS])),8 ],9 swiftSettings: [10 .define("DEBUG", .when(configuration: .debug)),11 .define("ENABLE_LOGGING", .when(configuration: .debug)),12 ]13 ),14]Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Apple: Swift Package Manager](https://www.swift.org/package-manager/) - [Apple: Creating Swift Packages](https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode) - [Uber's Modular Architecture](https://www.uber.com/en-TR/blog/uber-mobile-platform-architecture/)

