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

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

SPM ile modular iOS mimarisi kurun. Multi-module app yapısı, binary targets, plugins, build performance ve CI/CD entegrasyonu.

Swift Package Manager İleri Seviye: Modular Architecture

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

  1. Neden Modular Architecture?
  2. Package.swift Deep Dive
  3. Modül Tipleri ve Katmanlar
  4. Feature Module Oluşturma
  5. Binary Targets ve XCFramework
  6. SPM Plugins
  7. Build Performance Optimizasyonu
  8. SPM vs CocoaPods vs Carthage
  9. CI/CD Entegrasyonu
  10. 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.9
2import PackageDescription
3 
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üller
10 .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, extensions
22 .target(
23 name: "Core",
24 dependencies: [],
25 path: "Sources/Core"
26 ),
27 // Networking - API client, endpoints
28 .target(
29 name: "Networking",
30 dependencies: ["Core"],
31 path: "Sources/Networking"
32 ),
33 // Design System - UI components, colors, fonts
34 .target(
35 name: "DesignSystem",
36 dependencies: ["Core"],
37 path: "Sources/DesignSystem",
38 resources: [.process("Resources")]
39 ),
40 // Feature modules
41 .target(
42 name: "FeatureHome",
43 dependencies: [
44 "Core", "Networking", "DesignSystem",
45 .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
46 ]
47 ),
48 // Test targets
49 .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├─────────────────────────────────────────┤
20Core(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.swift
2import Core
3import Networking
4import DesignSystem
5import ComposableArchitecture
6 
7@Reducer
8public struct HomeFeature {
9 public struct State: Equatable {
10 public var products: [Product] = []
11 public var isLoading = false
12 public init() {}
13 }
14 
15 public enum Action {
16 case onAppear
17 case productsLoaded([Product])
18 }
19 
20 @Dependency(\.productClient) var productClient
21 
22 public init() {}
23 
24 public var body: some ReducerOf<Self> {
25 Reduce { state, action in
26 switch action {
27 case .onAppear:
28 state.isLoading = true
29 return .run { send in
30 let products = try await productClient.fetchAll()
31 await send(.productsLoaded(products))
32 }
33 case .productsLoaded(let products):
34 state.isLoading = false
35 state.products = products
36 return .none
37 }
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 target
2.binaryTarget(
3 name: "HeavySDK",
4 url: "https://releases.example.com/HeavySDK-1.0.0.xcframework.zip",
5 checksum: "abc123..."
6)
7 
8// veya local path
9.binaryTarget(
10 name: "InternalSDK",
11 path: "Binaries/InternalSDK.xcframework"
12)

SPM Plugins {#plugins}

swift
1// Build tool plugin - SwiftGen, SwiftLint
2.plugin(
3 name: "SwiftLintPlugin",
4 capability: .buildTool(),
5 dependencies: ["SwiftLintBinary"]
6)
7 
8// Command plugin - code generation
9.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}

  1. Incremental builds: Modüler yapı, sadece değişen modülü rebuild eder
  2. Explicit imports: @_exported import kullanma — bağımlılık grafiğini bozar
  3. Access control: internal yerine public sadece gerekli API'ler için
  4. 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 test
2name: Module Tests
3on: [pull_request]
4jobs:
5 test:
6 runs-on: macos-14
7 strategy:
8 matrix:
9 module: [Core, Networking, FeatureHome, FeatureProfile]
10 steps:
11 - uses: actions/checkout@v4
12 - name: Test ${{ matrix.module }}
13 run: swift test --filter ${{ matrix.module }}Tests

Migration Stratejisi {#migration}

  1. Core modülü oluştur — models, protocols, extensions taşı
  2. Networking modülü — API client'ı bağımsızlaştır
  3. DesignSystem — UI components, themes, assets
  4. Feature modülleri — her ekranı bağımsız modüle taşı
  5. 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 dependency
2// ModuleA imports ModuleB, ModuleB imports ModuleA
3// ÇÖZÜM: Ortak bağımlılığı Core modülüne taşı
4 
5// ❌ HATA 2: @_exported import ile bulaşıcı bağımlılık
6@_exported import Networking // TÜM import edenler Networking'i de alır
7// ÇÖZÜM: Explicit import kullan, @_exported kullanma
8 
9// ❌ HATA 3: Internal access level ile modüller arası erişim
10struct UserModel { // internal by default - diğer modüllerden erişilemez
11 let name: String
12}
13// ÇÖZÜM: Public API'leri public yap
14public struct UserModel: Sendable {
15 public let name: String
16 public init(name: String) { self.name = name }
17}
18 
19// ❌ HATA 4: Test target'ında @testable import eksik
20import FeatureHome // Sadece public API erişimi
21// ÇÖZÜM: @testable ile internal API'lere de eriş
22@testable import FeatureHome

Modü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/)

Etiketler

#spm#swift-package-manager#modular#architecture#dependency-management#monorepo
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