Tüm Yazılar
KategoriSwift
Okuma Süresi
22 dk
Yayın Tarihi
...
Kelime Sayısı
1.331kelime

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

Apple'ın Combine framework'ü ile reactive programming. Publishers, Subscribers, Operators, error handling ve SwiftUI entegrasyonu.

Combine Framework Mastery: Reactive Programming iOS'ta

Bir kullanıcı arama çubuğuna yazıyor. Her tuşa basışında API çağrısı yapmak istemezsin - debounce lazım. Sonuçlar geldiğinde duplicate'leri filtrelemen gerek. Hata olursa retry etmeli. Ve tüm bunlar main thread'de UI'ı güncellemeli. Bu senaryoyu imperative kodla yazmak kabus, ama Combine ile 5 satır.

İçindekiler

  1. Combine Nedir ve Neden Önemli?
  2. Publisher, Subscriber ve Subscription
  3. Built-in Publishers
  4. Operators: Combine'ın Süper Gücü
  5. Error Handling
  6. Combine + SwiftUI Entegrasyonu
  7. Combine vs async/await: Hangisi Ne Zaman?
  8. Production Patterns

Combine Nedir ve Neden Önemli? {#combine-nedir}

Combine, Apple'ın iOS 13+ ile sunduğu first-party reactive programming framework'ü. RxSwift'in Apple versiyonu diyebilirsin ama daha type-safe ve performanslı.

Temel fikir: zaman içinde değişen değerleri bir pipeline üzerinden işle.

Kavram
Açıklama
Gerçek Hayat Benzetmesi
**Publisher**
Değer üreten kaynak
Su kaynağı
**Operator**
Değeri dönüştüren ara işlem
Su filtresi
**Subscriber**
Değeri alan son nokta
Musluk
**Subscription**
Bağlantı
Su borusu

Dış Kaynaklar:


Publisher, Subscriber ve Subscription {#temel-kavramlar}

swift
1import Combine
2 
3// Publisher - değer yayınlar
4let numbersPublisher = [1, 2, 3, 4, 5].publisher
5 
6// Subscriber - değer alır
7let cancellable = numbersPublisher
8 .sink(
9 receiveCompletion: { completion in
10 switch completion {
11 case .finished:
12 print("Bitti!")
13 case .failure(let error):
14 print("Hata: \(error)")
15 }
16 },
17 receiveValue: { value in
18 print("Değer: \(value)")
19 }
20 )
21 
22// AnyCancellable - subscription'ı hayatta tutar
23// cancellable = nil yapılırsa subscription iptal olur
24 
25// Custom Publisher
26class TemperatureSensor {
27 let temperatureSubject = CurrentValueSubject<Double, Never>(20.0)
28 
29 // Dışarıya sadece Publisher olarak expose et
30 var temperaturePublisher: AnyPublisher<Double, Never> {
31 temperatureSubject.eraseToAnyPublisher()
32 }
33 
34 func updateTemperature(_ temp: Double) {
35 temperatureSubject.send(temp)
36 }
37}

Subject Türleri

Subject
Başlangıç Değeri
Kullanım
**PassthroughSubject**
Yok
Event-driven (buton tıklama, notification)
**CurrentValueSubject**
Var
State yönetimi (mevcut değer önemli)

Built-in Publishers {#built-in-publishers}

swift
1// 1. Just - tek değer yayınlar
2let just = Just(42)
3 
4// 2. Future - async işlem sonucu
5let future = Future<User, Error> { promise in
6 APIService.fetchUser { result in
7 promise(result)
8 }
9}
10 
11// 3. Timer - periyodik değer
12let timer = Timer.publish(every: 1.0, on: .main, in: .common)
13 .autoconnect()
14 
15// 4. NotificationCenter
16let keyboardPublisher = NotificationCenter.default
17 .publisher(for: UIResponder.keyboardWillShowNotification)
18 
19// 5. @Published property
20class UserSettings: ObservableObject {
21 @Published var username = "" // Otomatik publisher!
22}
23 
24// 6. URLSession
25let dataPublisher = URLSession.shared
26 .dataTaskPublisher(for: URL(string: "https://api.example.com/data")!)

Operators: Combine'ın Süper Gücü {#operators}

Operator'lar, publisher pipeline'ında veriyi dönüştüren ara işlemler. Combine'ın gerçek gücü burada.

swift
1// Gerçek dünya örneği: Arama sistemi
2class SearchViewModel: ObservableObject {
3 @Published var searchText = ""
4 @Published private(set) var results: [SearchResult] = []
5 
6 private var cancellables = Set<AnyCancellable>()
7 
8 init() {
9 $searchText // Publisher<String, Never>
10 .debounce(for: .milliseconds(300), // 300ms bekle (her tuşta çağırma)
11 scheduler: RunLoop.main)
12 .removeDuplicates() // Aynı değeri tekrar gönderme
13 .filter { $0.count >= 2 } // Min 2 karakter
14 .flatMap { query in // API çağrısı
15 SearchService.search(query: query)
16 .catch { _ in Just([]) } // Hata durumunda boş dizi
17 }
18 .receive(on: DispatchQueue.main) // UI thread'e geç
19 .assign(to: &$results) // Sonuçları ata
20 }
21}
22 
23// Önemli Operatörler
24let pipeline = publisher
25 .map { $0 * 2 } // Dönüştür
26 .filter { $0 > 10 } // Filtrele
27 .compactMap { Int($0) } // nil'leri at
28 .removeDuplicates() // Tekrarları at
29 .debounce(for: .seconds(1), // Gecikme
30 scheduler: RunLoop.main)
31 .throttle(for: .seconds(2), // Hız sınırla
32 scheduler: RunLoop.main,
33 latest: true)
34 .combineLatest(otherPublisher) // İki publisher'ı birleştir
35 .merge(with: anotherPublisher) // Akışları birleştir
36 .zip(pairedPublisher) // Eşleştir
37 .scan(0) { sum, value in // Kümülatif toplam
38 sum + value
39 }

Error Handling {#error-handling}

swift
1enum APIError: Error {
2 case networkError
3 case decodingError
4 case unauthorized
5}
6 
7// Error handling operators
8let request = URLSession.shared.dataTaskPublisher(for: url)
9 .tryMap { data, response -> Data in
10 guard let httpResponse = response as? HTTPURLResponse else {
11 throw APIError.networkError
12 }
13 guard httpResponse.statusCode == 200 else {
14 if httpResponse.statusCode == 401 {
15 throw APIError.unauthorized
16 }
17 throw APIError.networkError
18 }
19 return data
20 }
21 .decode(type: [Product].self, decoder: JSONDecoder())
22 .mapError { error -> APIError in
23 if error is DecodingError {
24 return .decodingError
25 }
26 return error as? APIError ?? .networkError
27 }
28 .retry(2) // 2 kez tekrar dene
29 .catch { error -> Just<[Product]> in
30 print("Final error: \(error)")
31 return Just([]) // Fallback değer
32 }
33 .receive(on: DispatchQueue.main)
34 .eraseToAnyPublisher()

Combine + SwiftUI Entegrasyonu {#swiftui-entegrasyon}

swift
1struct ProductListView: View {
2 @StateObject private var viewModel = ProductListViewModel()
3 
4 var body: some View {
5 List(viewModel.products) { product in
6 ProductRow(product: product)
7 }
8 .searchable(text: $viewModel.searchText)
9 .overlay {
10 if viewModel.isLoading {
11 ProgressView()
12 }
13 }
14 }
15}
16 
17class ProductListViewModel: ObservableObject {
18 @Published var searchText = ""
19 @Published private(set) var products: [Product] = []
20 @Published private(set) var isLoading = false
21 
22 private var cancellables = Set<AnyCancellable>()
23 
24 init() {
25 setupSearch()
26 }
27 
28 private func setupSearch() {
29 $searchText
30 .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
31 .removeDuplicates()
32 .sink { [weak self] query in
33 Task { await self?.search(query: query) }
34 }
35 .store(in: &cancellables)
36 }
37 
38 @MainActor
39 private func search(query: String) async {
40 isLoading = true
41 defer { isLoading = false }
42 
43 do {
44 if query.isEmpty {
45 products = try await ProductService.fetchAll()
46 } else {
47 products = try await ProductService.search(query: query)
48 }
49 } catch {
50 products = []
51 }
52 }
53}

Combine vs async/await: Hangisi Ne Zaman? {#combine-vs-async}

Senaryo
Combine
async/await
**Tek seferlik API çağrısı**
❌ Overkill
✅ Doğal
**Zaman içinde değişen stream**
✅ Doğal
⚠️ AsyncSequence
**Debounce/throttle**
✅ Built-in
❌ Manuel
**Multiple publishers birleştirme**
✅ combineLatest, merge
⚠️ TaskGroup
**UI binding**
✅ @Published
✅ @Observable
**Error retry**
✅ .retry(3)
❌ Manuel loop
💡 Pro Tip: İkisini birlikte kullan! Combine pipeline'ı, async/await fonksiyonlarını çağırabilir. "Stream" işlemleri için Combine, "one-shot" işlemleri için async/await.

Production Patterns {#production-patterns}

🔑 Bu Yazıdan Çıkarımlar

  1. Publisher/Subscriber modeli zaman içinde değişen veri akışları için ideal
  2. Operator chain ile karmaşık veri transformasyonları temiz kalır
  3. AnyCancellable ile memory leak önle - Set<AnyCancellable> kullan
  4. Combine + async/await birlikte en güçlü kombinasyon
  5. Error handling pipeline'da catch, retry, replaceError ile yönet
  6. @Published SwiftUI ile doğal entegrasyon sağlar

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

#combine#reactive-programming#swift#ios-development#publishers#tutorial#intermediate
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