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

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

Creational, Structural ve Behavioral pattern'ler ile iOS uygulamalarınızı daha sürdürülebilir yapın. Coordinator, Repository ve SOLID prensipleri dahil.

iOS Design Patterns: Pratik Örneklerle 15 Tasarım Deseni

Tasarım desenleri, yazılım geliştirmede tekrarlayan sorunlara kanıtlanmış çözümlerdir. Gang of Four'un 1994'te tanımladığı 23 desen, bugün hala iOS geliştirmede her gün karşımıza çıkıyor. Ama hangi desen nerede kullanılır? Bu rehberde iOS'a en çok uyan 15 deseni gerçek kod örnekleriyle inceleyeceğiz.

💡 Hızlı Not: Bu rehber GoF Design Patterns, Martin Fowler'ın Patterns of Enterprise Application Architecture ve Apple'ın kendi framework tasarım kalıplarından derlendi.

İçindekiler

  1. Creational Patterns
  2. Singleton ve Anti-Pattern Riskleri
  3. Factory Method ve Abstract Factory
  4. Builder Pattern
  5. Structural Patterns
  6. Adapter ve Facade
  7. Decorator Pattern
  8. Behavioral Patterns
  9. Observer ve Strategy
  10. Coordinator Pattern
  11. Repository Pattern

Creational Patterns {#creational}

Creational pattern'ler nesne oluşturma mekanizmalarını soyutlar. iOS'ta en sık kullanılan üçünü inceleyelim.

Singleton ve Anti-Pattern Riskleri {#singleton}

Singleton, tüm uygulama boyunca tek bir instance garantisi sağlar. Apple framework'lerinde yaygın:

swift
1// Apple'ın singleton'ları
2UIApplication.shared
3FileManager.default
4UserDefaults.standard
5NotificationCenter.default
6 
7// Kendi singleton'ımız - Thread-safe
8final class AnalyticsManager {
9 static let shared = AnalyticsManager()
10 
11 private let queue = DispatchQueue(label: "analytics", qos: .utility)
12 private var events: [AnalyticsEvent] = []
13 
14 private init() {} // Dışarıdan instance oluşturmayı engelle
15 
16 func track(_ event: AnalyticsEvent) {
17 queue.async { [weak self] in
18 self?.events.append(event)
19 if self?.events.count ?? 0 >= 10 {
20 self?.flush()
21 }
22 }
23 }
24 
25 private func flush() {
26 let batch = events
27 events.removeAll()
28 // API'ye gönder...
29 }
30}

Singleton Anti-Pattern Tablosu

Sorun
Açıklama
Çözüm
Test zorluğu
Global state mock'lanamaz
Protocol + DI kullan
Gizli bağımlılık
Hangi class singleton kullanıyor belirsiz
Constructor injection
Thread safety
Concurrent erişim race condition
Actor veya serial queue
Memory
Uygulama ömrü boyunca bellekte
Gerçekten gerekli mi düşün

Factory Method ve Abstract Factory {#factory}

Factory pattern, nesne oluşturma mantığını soyutlar:

swift
1// Factory Method
2protocol ViewControllerFactory {
3 func makeListVC() -> UIViewController
4 func makeDetailVC(item: Item) -> UIViewController
5}
6 
7class ProductViewControllerFactory: ViewControllerFactory {
8 func makeListVC() -> UIViewController {
9 let vm = ProductListViewModel(repository: ProductRepository())
10 return ProductListViewController(viewModel: vm)
11 }
12 
13 func makeDetailVC(item: Item) -> UIViewController {
14 let vm = ProductDetailViewModel(product: item as! Product)
15 return ProductDetailViewController(viewModel: vm)
16 }
17}
18 
19// Abstract Factory - Platform-specific UI
20protocol UIComponentFactory {
21 func makeButton(title: String) -> any ButtonComponent
22 func makeTextField(placeholder: String) -> any TextFieldComponent
23 func makeAlert(title: String, message: String) -> any AlertComponent
24}
25 
26class iOSComponentFactory: UIComponentFactory {
27 func makeButton(title: String) -> any ButtonComponent { iOSButton(title: title) }
28 func makeTextField(placeholder: String) -> any TextFieldComponent { iOSTextField(placeholder: placeholder) }
29 func makeAlert(title: String, message: String) -> any AlertComponent { iOSAlert(title: title, message: message) }
30}

Builder Pattern {#builder}

Karmaşık nesneleri adım adım oluşturmak için:

swift
1class URLRequestBuilder {
2 private var url: URL
3 private var method: String = "GET"
4 private var headers: [String: String] = [:]
5 private var body: Data?
6 private var timeout: TimeInterval = 30
7 
8 init(url: URL) { self.url = url }
9 
10 @discardableResult
11 func method(_ method: String) -> Self { self.method = method; return self }
12 
13 @discardableResult
14 func header(_ key: String, _ value: String) -> Self { headers[key] = value; return self }
15 
16 @discardableResult
17 func jsonBody<T: Encodable>(_ body: T) -> Self {
18 self.body = try? JSONEncoder().encode(body)
19 headers["Content-Type"] = "application/json"
20 return self
21 }
22 
23 @discardableResult
24 func timeout(_ seconds: TimeInterval) -> Self { self.timeout = seconds; return self }
25 
26 func build() -> URLRequest {
27 var request = URLRequest(url: url, timeoutInterval: timeout)
28 request.httpMethod = method
29 request.httpBody = body
30 headers.forEach { request.setValue($1, forHTTPHeaderField: $0) }
31 return request
32 }
33}
34 
35// Kullanım - fluent API
36let request = URLRequestBuilder(url: apiURL)
37 .method("POST")
38 .header("Authorization", "Bearer \(token)")
39 .jsonBody(CreateUserRequest(name: "John", email: "[email protected]"))
40 .timeout(15)
41 .build()

Structural Patterns {#structural}

Structural pattern'ler, nesneler arasındaki ilişkileri organize eder.

Adapter ve Facade {#adapter-facade}

swift
1// Adapter - 3rd party kütüphaneyi kendi interface'imize uyarla
2protocol AnalyticsService {
3 func logEvent(_ name: String, parameters: [String: Any])
4}
5 
6// Firebase adapter
7class FirebaseAnalyticsAdapter: AnalyticsService {
8 func logEvent(_ name: String, parameters: [String: Any]) {
9 Analytics.logEvent(name, parameters: parameters)
10 }
11}
12 
13// Mixpanel adapter
14class MixpanelAnalyticsAdapter: AnalyticsService {
15 func logEvent(_ name: String, parameters: [String: Any]) {
16 Mixpanel.mainInstance().track(event: name, properties: parameters as? Properties)
17 }
18}
19 
20// Facade - Karmaşık alt sistemi basit interface ile sar
21class AppStartupFacade {
22 private let analytics: AnalyticsService
23 private let crashReporter: CrashReporter
24 private let featureFlags: FeatureFlagService
25 private let pushManager: PushNotificationManager
26 
27 init(analytics: AnalyticsService, crashReporter: CrashReporter,
28 featureFlags: FeatureFlagService, pushManager: PushNotificationManager) {
29 self.analytics = analytics
30 self.crashReporter = crashReporter
31 self.featureFlags = featureFlags
32 self.pushManager = pushManager
33 }
34 
35 func start() async {
36 crashReporter.initialize()
37 await featureFlags.fetch()
38 pushManager.requestPermission()
39 analytics.logEvent("app_launched", parameters: [:])
40 }
41}

Decorator Pattern {#decorator}

swift
1// Protocol
2protocol DataService {
3 func fetchData() async throws -> [Item]
4}
5 
6// Base implementation
7class RemoteDataService: DataService {
8 func fetchData() async throws -> [Item] {
9 // Network request...
10 return try await apiClient.fetch()
11 }
12}
13 
14// Caching decorator
15class CachingDataService: DataService {
16 private let wrapped: DataService
17 private let cache: NSCache<NSString, CacheEntry>
18 
19 init(wrapping service: DataService) {
20 self.wrapped = service
21 self.cache = NSCache()
22 }
23 
24 func fetchData() async throws -> [Item] {
25 if let cached = cache.object(forKey: "items"), !cached.isExpired {
26 return cached.items
27 }
28 let items = try await wrapped.fetchData()
29 cache.setObject(CacheEntry(items: items), forKey: "items")
30 return items
31 }
32}
33 
34// Logging decorator
35class LoggingDataService: DataService {
36 private let wrapped: DataService
37 
38 init(wrapping service: DataService) { self.wrapped = service }
39 
40 func fetchData() async throws -> [Item] {
41 let start = CFAbsoluteTimeGetCurrent()
42 let items = try await wrapped.fetchData()
43 let elapsed = CFAbsoluteTimeGetCurrent() - start
44 print("📊 Fetched \(items.count) items in \(elapsed)s")
45 return items
46 }
47}
48 
49// Compose decorators
50let service: DataService = LoggingDataService(
51 wrapping: CachingDataService(
52 wrapping: RemoteDataService()
53 )
54)

Behavioral Patterns {#behavioral}

Observer ve Strategy {#observer-strategy}

swift
1// Observer - Combine ile modern iOS
2class ShoppingCart: ObservableObject {
3 @Published var items: [CartItem] = []
4 @Published var totalPrice: Decimal = 0
5 
6 func addItem(_ item: CartItem) {
7 items.append(item)
8 recalculateTotal()
9 }
10}
11 
12// Strategy Pattern - Farklı sıralama algoritmaları
13protocol SortStrategy {
14 func sort<T: Comparable>(_ array: [T]) -> [T]
15}
16 
17struct QuickSortStrategy: SortStrategy {
18 func sort<T: Comparable>(_ array: [T]) -> [T] { array.sorted() }
19}
20 
21struct RelevanceSortStrategy: SortStrategy {
22 func sort<T: Comparable>(_ array: [T]) -> [T] { array.reversed() }
23}
24 
25class ProductListViewModel {
26 var sortStrategy: SortStrategy = QuickSortStrategy()
27 
28 func sortProducts(_ products: [Product]) -> [Product] {
29 // Strategy pattern ile runtime'da algoritma değiştir
30 sortStrategy.sort(products)
31 }
32}

Coordinator Pattern {#coordinator}

iOS navigation'ı merkezi bir noktadan yönetmek için:

swift
1protocol Coordinator: AnyObject {
2 var childCoordinators: [Coordinator] { get set }
3 var navigationController: UINavigationController { get }
4 func start()
5}
6 
7class AppCoordinator: Coordinator {
8 var childCoordinators: [Coordinator] = []
9 let navigationController: UINavigationController
10 
11 init(navigationController: UINavigationController) {
12 self.navigationController = navigationController
13 }
14 
15 func start() {
16 if UserDefaults.standard.bool(forKey: "isLoggedIn") {
17 showMain()
18 } else {
19 showAuth()
20 }
21 }
22 
23 private func showAuth() {
24 let authCoordinator = AuthCoordinator(nav: navigationController)
25 authCoordinator.onLoginSuccess = { [weak self] in
26 self?.removeChild(authCoordinator)
27 self?.showMain()
28 }
29 childCoordinators.append(authCoordinator)
30 authCoordinator.start()
31 }
32 
33 private func showMain() {
34 let mainCoordinator = MainTabCoordinator(nav: navigationController)
35 childCoordinators.append(mainCoordinator)
36 mainCoordinator.start()
37 }
38 
39 private func removeChild(_ coordinator: Coordinator) {
40 childCoordinators.removeAll { $0 === coordinator }
41 }
42}

Repository Pattern {#repository}

Data katmanını soyutlayarak farklı data source'lar arasında geçiş yapabilirsin:

swift
1protocol ProductRepository {
2 func getAll() async throws -> [Product]
3 func getById(_ id: UUID) async throws -> Product
4 func save(_ product: Product) async throws
5 func delete(_ id: UUID) async throws
6}
7 
8class RemoteProductRepository: ProductRepository {
9 private let apiClient: APIClient
10 
11 func getAll() async throws -> [Product] {
12 try await apiClient.get("/products")
13 }
14 
15 func getById(_ id: UUID) async throws -> Product {
16 try await apiClient.get("/products/\(id)")
17 }
18 
19 func save(_ product: Product) async throws {
20 try await apiClient.post("/products", body: product)
21 }
22 
23 func delete(_ id: UUID) async throws {
24 try await apiClient.delete("/products/\(id)")
25 }
26}

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:** - [Design Patterns: Elements of Reusable Object-Oriented Software (GoF)](https://en.wikipedia.org/wiki/Design_Patterns) - [Apple: Cocoa Design Patterns](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html) - [Refactoring.Guru: Design Patterns](https://refactoring.guru/design-patterns)

Etiketler

#design-patterns#architecture#swift#ios#solid#creational#structural#behavioral
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