# Combine Framework: Swift'te Reaktif Programlama > Swift'te asenkron veri akışlarını yönetmek için Combine'ı ustalaşın: Publishers, Subscribers, Operators ve iOS uygulamaları için ileri düzey desenler. - Published: 2026-02-18 - Updated: 2026-04-28 - Author: SharpSkill - Tags: combine, swift, ios, reactive, async - Reading time: 14 min --- Reaktif programlama, iOS uygulamalarında asenkron olayların ve veri akışlarının ele alınma biçimini kökten değiştirir. Apple'ın yerel framework'ü olan Combine, karmaşık veri pipeline'larını orkestre etmek için bildirimsel ve tip güvenli bir yaklaşım sunar. Bu rehber, temel kavramlardan üretime hazır desenlere kadar uzanır. > **Neden RxSwift yerine Combine?** > > Combine, iOS 13+ ile birlikte gelir, Apple'ın optimizasyonları sayesinde daha iyi performans sunar ve SwiftUI ile sorunsuz bütünleşir. Yönetilecek dış bağımlılık yoktur. ## Combine'ın temel kavramları Combine üç temel kavram üzerine kuruludur: değer yayan **Publishers**, bunları alan **Subscribers** ve veriyi aralarında dönüştüren **Operators**. Bu mimari, reaktif ve birleştirilebilir veri pipeline'ları kurmaya olanak tanır. ### Publisher: veri kaynağı Publisher, zamana yayılan bir değer dizisini yayınlayabilen bir tiptir. Her Publisher iki ilişkili tip bildirir: yayınlanan değerin tipi (`Output`) ve olası hata tipi (`Failure`). Farklı Publisher türleri şöyle oluşturulur: ```swift // PublisherBasics.swift import Combine // Just: emits a single value then completes // Useful for converting a simple value to a Publisher let singleValue = Just("Hello Combine") // CurrentValueSubject: stores and emits the current value // Perfect for representing state that changes over time let counter = CurrentValueSubject(0) // PassthroughSubject: emits values without storing them // Ideal for one-time events (taps, notifications) let buttonTaps = PassthroughSubject() // Future: emits a single value asynchronously // Wraps an async operation that returns a result let asyncOperation = Future { promise in // Simulate a network call DispatchQueue.global().asyncAfter(deadline: .now() + 1) { promise(.success("Data loaded")) } } ``` Hatalar için `Never` tipi, Publisher'ın asla başarısız olamayacağı anlamına gelir. Bu, derleme zamanında verilen ve hata yönetim kodunu basitleştiren bir garantidir. ### Subscriber: değerleri almak Subscriber, değerlerini almak için bir Publisher'a abone olur. `sink` metodu Subscriber oluşturmanın en yaygın yoludur. İki closure alır: biri hata veya tamamlanma için, diğeri alınan her değer için: ```swift // SubscriberBasics.swift import Combine // Variable to store subscriptions // Without this reference, the subscription would be immediately cancelled var cancellables = Set() let publisher = ["Swift", "Combine", "iOS"].publisher // sink() creates a Subscriber that receives values publisher .sink( // Called when the Publisher completes or fails receiveCompletion: { completion in switch completion { case .finished: print("✅ Completed successfully") case .failure(let error): print("❌ Error: \(error)") } }, // Called for each emitted value receiveValue: { value in print("Received: \(value)") } ) // store() keeps a reference to the subscription .store(in: &cancellables) // Output: // Received: Swift // Received: Combine // Received: iOS // ✅ Completed successfully ``` > **Bellek sızıntılarına dikkat** > > `sink()` tarafından döndürülen `AnyCancellable`'ı her zaman saklayın. Referans olmadan abonelik otomatik olarak iptal edilir ve hiçbir değer alınmaz. ## Operators ile veriyi dönüştürmek Operators, Combine'ın kalbidir. Veri akışlarını bildirimsel olarak dönüştürmeye, filtrelemeye ve birleştirmeye olanak tanır. Her Operator yeni bir Publisher döndürür ve böylece zincirlenebilir. ### Temel dönüşüm Operators'leri Dönüşüm Operators'leri yayınlanan her değeri değiştirir. `map` değerleri dönüştürür, `flatMap` iç içe Publishers'ı düzleştirir ve `compactMap` nil değerleri filtreler: ```swift // TransformOperators.swift import Combine var cancellables = Set() // map: transforms each value // Equivalent to map on arrays [1, 2, 3, 4, 5].publisher .map { $0 * 2 } // Multiply each number by 2 .sink { print("Doubled: \($0)") } .store(in: &cancellables) // Output: 2, 4, 6, 8, 10 // compactMap: transforms AND filters out nil // Useful for optional conversions ["1", "two", "3", "four", "5"].publisher .compactMap { Int($0) } // Convert to Int, ignore failures .sink { print("Valid number: \($0)") } .store(in: &cancellables) // Output: 1, 3, 5 // flatMap: flattens nested Publishers // Essential for chaining async operations struct User { let id: Int; let name: String } func fetchUser(id: Int) -> AnyPublisher { // Simulate an API call Just(User(id: id, name: "User \(id)")) .delay(for: .milliseconds(100), scheduler: RunLoop.main) .eraseToAnyPublisher() } [1, 2, 3].publisher .flatMap { id in fetchUser(id: id) } // Each ID becomes an API call .sink { user in print("User: \(user.name)") } .store(in: &cancellables) ``` ### Filtreleme Operators'leri Filtreleme Operators'leri pipeline'dan hangi değerlerin geçeceğini denetler. Gereksiz işlemleri önlemek ve performansı optimize etmek için zorunludur: ```swift // FilterOperators.swift import Combine var cancellables = Set() let numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5].publisher // filter: keeps only values that satisfy the condition numbers .filter { $0 > 2 } // Keep only numbers > 2 .sink { print("Filtered: \($0)") } .store(in: &cancellables) // Output: 3, 3, 3, 4, 5, 5 // removeDuplicates: removes consecutive identical values numbers .removeDuplicates() // Eliminate consecutive duplicates .sink { print("Without duplicates: \($0)") } .store(in: &cancellables) // Output: 1, 2, 3, 4, 5 // debounce: waits for a pause before emitting // Perfect for real-time search let searchText = PassthroughSubject() searchText .debounce(for: .milliseconds(300), scheduler: RunLoop.main) .removeDuplicates() // Ignore if text hasn't changed .sink { query in print("Search: \(query)") // Launch API call here } .store(in: &cancellables) // Simulate rapid typing searchText.send("S") searchText.send("Sw") searchText.send("Swi") searchText.send("Swift") // Only "Swift" is emitted after 300ms ``` ## Birden fazla Publisher'ı birleştirmek Gerçek dünya uygulamaları sıklıkla birden fazla veri kaynağını birleştirmek zorundadır. Combine, bu çoklu akışları orkestre etmek için çeşitli Operators sunar. ### CombineLatest ve Zip `combineLatest` herhangi bir Publisher yayınladığında, diğerlerinin son değerleriyle birleştirerek yayın yapar. `zip` ise birleştirmeden önce tüm Publisher'ların yayın yapmasını bekler: ```swift // CombiningPublishers.swift import Combine var cancellables = Set() // Simulate a form with validation let email = CurrentValueSubject("") let password = CurrentValueSubject("") // combineLatest: combines the latest values from each Publisher // Emits on every change from either source Publishers.CombineLatest(email, password) .map { email, password in // Validate that email contains @ and password > 6 chars let isEmailValid = email.contains("@") let isPasswordValid = password.count >= 6 return isEmailValid && isPasswordValid } .sink { isFormValid in print("Form valid: \(isFormValid)") } .store(in: &cancellables) email.send("user@example.com") // false (password empty) password.send("123456") // true (both are valid) // zip: waits for one value from each Publisher before emitting // Useful for synchronizing parallel operations let firstAPI = PassthroughSubject() let secondAPI = PassthroughSubject() Publishers.Zip(firstAPI, secondAPI) .sink { stringValue, intValue in print("Received pair: \(stringValue), \(intValue)") } .store(in: &cancellables) firstAPI.send("Hello") // No emission, waiting for secondAPI secondAPI.send(42) // Emits: ("Hello", 42) firstAPI.send("World") // No emission, waiting for secondAPI secondAPI.send(100) // Emits: ("World", 100) ``` ### Akışları birleştiren Merge `merge`, aynı tipteki birden fazla Publisher'ı tek bir akışta birleştirir. Değerler, hangi Publisher gönderdiğinden bağımsız olarak yayın sırasına göre gelir: ```swift // MergePublishers.swift import Combine var cancellables = Set() // Multiple user notification sources let pushNotifications = PassthroughSubject() let localNotifications = PassthroughSubject() let inAppMessages = PassthroughSubject() // Merge unifies all streams into one Publishers.Merge3(pushNotifications, localNotifications, inAppMessages) .sink { message in // Handle all notifications the same way print("📬 Notification: \(message)") } .store(in: &cancellables) pushNotifications.send("New message") // 📬 Notification: New message localNotifications.send("Reminder: meeting") // 📬 Notification: Reminder: meeting inAppMessages.send("Welcome!") // 📬 Notification: Welcome! ``` ## Combine'da hata yönetimi Hata yönetimi, Combine'ın çekirdeğine yerleştirilmiştir. Publisher'ların `Failure` tipi, tüm hataların ele alındığını derleyicinin doğrulamasına izin verir. ### Toparlama stratejileri Combine, hatalarla başa çıkmak için çeşitli Operators sunar: başka bir Publisher ile değiştirmek için `catch`, yeniden denemek için `retry` ve varsayılan değer için `replaceError`: ```swift // ErrorHandling.swift import Combine var cancellables = Set() enum APIError: Error { case networkError case invalidResponse case serverError(Int) } // Simulate an API call that can fail func fetchData() -> AnyPublisher { Fail(error: APIError.networkError) .eraseToAnyPublisher() } // retry: retries N times before propagating the error fetchData() .retry(3) // Try up to 3 times .catch { error -> Just in // catch: replaces the error with a fallback Publisher print("Error after 3 attempts: \(error)") return Just("Cached data") // Fallback value } .sink( receiveCompletion: { _ in }, receiveValue: { print("Result: \($0)") } ) .store(in: &cancellables) // replaceError: replaces any error with a fixed value // Simpler than catch when only a default value is needed fetchData() .replaceError(with: "Error - default value") .sink { print("With fallback: \($0)") } .store(in: &cancellables) ``` > **Never ile Error karşılaştırması** > > `Never` tipinde bir Publisher'ı başarısız olabilen bir Publisher'a dönüştürmek için `setFailureType(to:)`, ters yönde dönüştürmek için ise `replaceError(with:)` veya `catch` kullanılır. ## Combine ile MVVM deseni Combine, MVVM (Model-View-ViewModel) desenine doğal olarak entegre olur. ViewModel, View'in gözlemlediği Publisher'ları sunar ve veri ile arayüz arasında reaktif bir bağ kurar. ### Tam reaktif ViewModel Arama, yükleme ve hata yönetimi içeren bir kullanıcı listesi için örnek bir ViewModel: ```swift // UserListViewModel.swift import Combine import Foundation // Data model struct User: Codable, Identifiable { let id: Int let name: String let email: String } // ViewModel with reactive state final class UserListViewModel: ObservableObject { // MARK: - Published Properties (observed by SwiftUI) @Published var users: [User] = [] // User list @Published var searchQuery: String = "" // Search text @Published var isLoading: Bool = false // Loading state @Published var errorMessage: String? // Optional error message // MARK: - Private Properties private var cancellables = Set() private let userService: UserServiceProtocol // MARK: - Computed Properties // Filters users based on search query var filteredUsers: [User] { guard !searchQuery.isEmpty else { return users } return users.filter { $0.name.localizedCaseInsensitiveContains(searchQuery) } } // MARK: - Initialization init(userService: UserServiceProtocol = UserService()) { self.userService = userService setupBindings() } // MARK: - Private Methods private func setupBindings() { // Observe searchQuery changes // debounce prevents too frequent calls $searchQuery .debounce(for: .milliseconds(300), scheduler: RunLoop.main) .removeDuplicates() .sink { [weak self] query in // Server-side search logic if needed print("Search updated: \(query)") } .store(in: &cancellables) } // MARK: - Public Methods func loadUsers() { isLoading = true errorMessage = nil userService.fetchUsers() .receive(on: DispatchQueue.main) // Ensure UI updates on main thread .sink( receiveCompletion: { [weak self] completion in self?.isLoading = false if case .failure(let error) = completion { self?.errorMessage = error.localizedDescription } }, receiveValue: { [weak self] users in self?.users = users } ) .store(in: &cancellables) } } ``` ### Combine ve URLSession ile servis URLSession, `dataTaskPublisher` aracılığıyla Combine'ı yerel olarak destekler. Yeniden kullanılabilir bir ağ servisi şöyle oluşturulur: ```swift // UserService.swift import Combine import Foundation protocol UserServiceProtocol { func fetchUsers() -> AnyPublisher<[User], Error> } final class UserService: UserServiceProtocol { private let baseURL = URL(string: "https://api.example.com")! private let session: URLSession private let decoder: JSONDecoder init(session: URLSession = .shared) { self.session = session self.decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase } func fetchUsers() -> AnyPublisher<[User], Error> { let url = baseURL.appendingPathComponent("users") return session.dataTaskPublisher(for: url) // Check HTTP status code .tryMap { data, response in guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) } guard 200..<300 ~= httpResponse.statusCode else { throw URLError(.badServerResponse) } return data } // Decode JSON to Swift model .decode(type: [User].self, decoder: decoder) // Erase concrete type to return AnyPublisher .eraseToAnyPublisher() } } ``` ## SwiftUI entegrasyonu Combine ve SwiftUI güçlü bir ikili oluşturur. Bir `ObservableObject`'in `@Published` özellikleri view güncellemelerini otomatik olarak tetikler. ### Combine ViewModel ile SwiftUI View ViewModel'i bir SwiftUI view'ine bu şekilde bağlanır: ```swift // UserListView.swift import SwiftUI struct UserListView: View { // StateObject: creates and owns the ViewModel @StateObject private var viewModel = UserListViewModel() var body: some View { NavigationStack { Group { if viewModel.isLoading { // Centered loading indicator ProgressView("Loading...") } else if let error = viewModel.errorMessage { // Error view with retry button VStack(spacing: 16) { Text("Error: \(error)") .foregroundStyle(.red) Button("Retry") { viewModel.loadUsers() } } } else { // User list List(viewModel.filteredUsers) { user in UserRowView(user: user) } } } .navigationTitle("Users") .searchable(text: $viewModel.searchQuery) // Direct binding .onAppear { viewModel.loadUsers() // Load on first appearance } } } } struct UserRowView: View { let user: User var body: some View { VStack(alignment: .leading, spacing: 4) { Text(user.name) .font(.headline) Text(user.email) .font(.subheadline) .foregroundStyle(.secondary) } .padding(.vertical, 4) } } ``` ## İleri düzey desenler ### İptal ve otomatik temizleme Abonelik yaşam döngüsünü yönetmek bellek sızıntılarını önlemek için kritiktir. `AnyCancellable` ile birlikte kullanılan `cancellables` deseni otomatik temizliği garanti eder: ```swift // CancellationPatterns.swift import Combine final class DataManager { // Set of cancellables: automatically cancelled on destruction private var cancellables = Set() // Individual cancellable for fine-grained control private var currentRequest: AnyCancellable? func startPolling() { // Timer that emits every 5 seconds Timer.publish(every: 5, on: .main, in: .common) .autoconnect() // Starts automatically .sink { [weak self] _ in self?.fetchLatestData() } .store(in: &cancellables) } func fetchLatestData() { // Cancel the previous request if it exists currentRequest?.cancel() currentRequest = URLSession.shared .dataTaskPublisher(for: URL(string: "https://api.example.com/data")!) .map(\.data) .decode(type: [String].self, decoder: JSONDecoder()) .replaceError(with: []) .receive(on: DispatchQueue.main) .sink { data in print("Data received: \(data)") } } deinit { // All cancellables are automatically cancelled print("DataManager destroyed, subscriptions cancelled") } } ``` ### Threading için Schedulers Schedulers, işlemlerin hangi thread'te yürütüleceğini kontrol eder. Arka plan işleri için `subscribe(on:)`, UI güncellemeleri için ise `receive(on:)` kullanın: ```swift // SchedulerPatterns.swift import Combine import Foundation var cancellables = Set() func loadAndProcessData() -> AnyPublisher { URLSession.shared.dataTaskPublisher(for: apiURL) // Perform parsing on a background thread .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .map(\.data) .decode(type: RawData.self, decoder: JSONDecoder()) // Heavy processing on background thread .map { rawData in // This expensive operation runs in the background processData(rawData) } // Return to main thread for UI .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } ``` ## Sonuç Combine, iOS uygulamalarında asenkron veri akışlarını yönetmek için güçlü ve bildirimsel bir yaklaşım sunar. Öne çıkan noktalar: ✅ **Publishers** zamana yayılan değerler yayınlar ✅ **Subscribers** bu değerleri alır ve işler ✅ **Operators** akışları dönüştürür ve birleştirir ✅ `AnyCancellable` abonelik yaşam döngüsünü yönetir ✅ SwiftUI ile `@Published`, otomatik reaktif bağlar oluşturur ✅ **Schedulers** optimum performans için thread'leri yönetir Combine'da ustalaşmak; sağlam, sürdürülebilir ve reaktif iOS uygulamaları kurmaya olanak tanır. SwiftUI ile yerel entegrasyonu, onu modern iOS geliştirmenin vazgeçilmez bir aracı yapar. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/ios/combine-reactive-programming-swift