Combine — temel kavramlar, Publisher ve reaktif programlama

Yazar: IT Sectr Yayınlanma: 2026-05-03 Okuma süresi: 11 dk

Combine, iOS 13, macOS Catalina, tvOS 13 ve watchOS 6'da tanıtılan Apple'ın yerel reaktif programlama framework'üdür. Publisher ve Subscriber deseni aracılığıyla asenkron olayları işlemek için bildirimsel bir Swift API sağlar ve delegeleri, closure'ları ve NotificationCenter'ı birleşik bir zincirle değiştirir. Apple, 2025'e göre Combine, SwiftUI ve modern iOS mimarilerinin temelidir ve async/await ile Structured Concurrency ile yakın çalışır. Framework, iş parçacığı güvenliği garantileriyle asenkron işlemlerin kompozisyonu için tasarlanmıştır.

Ana Noktalar

  • Combine, Publisher, Subscriber, Subject türleri ve Failure aracılığıyla hata türleriyle Apple'ın yerel reaktif framework'üdür
  • Publisher zaman içinde olaylar yayar: Output değerleri, başarı veya Failure hatasıyla tamamlanabilir
  • Subscriber, Publisher'dan olayları alır ve Demand aracılığıyla belirli sayıda öğe talep edebilir
  • Operatörler map, flatMap, filter, combineLatest, zip, debounce yarış koşulları olmadan akışları dönüştürür
  • SwiftUI, reaktif arayüz güncellemeleri için @Published, ObservableObject ve @StateObject aracılığıyla Combine'ı kullanır

Combine Nedir?

Combine, Apple SDK'sına entegre edilmiş bildirimsel bir reaktif programlama framework'üdür. Reactive Streams desenini uygular: Publisher değerler üretir, Subscriber bunları tüketir ve operatörler aralarındaki akışı dönüştürür. Combine, geri çağırma ve delege sorununu, ağ yanıtlarından UI değişikliklerine kadar herhangi bir asenkron olay için birleşik bir kompozisyon modeli sağlayarak çözer.

Combine'dan önce, iOS geliştiricileri üçüncü taraf kütüphaneleri, özellikle RxSwift'i kullanıyordu. Apple, Combine'ı ekosisteme derin entegrasyon sağlayan yerel bir alternatif olarak oluşturdu: framework, @objc köprüleri aracılığıyla Objective-C'yi destekler, KVO (NSObject.keyValuePublisher aracılığıyla Key-Value Observing) ve NotificationCenter ile çalışır ve ayrıca SwiftUI'nin temelini oluşturur. SwiftUI'de yayınlanan tüm UIKit bileşenleri, görünüm güncellemeleri için dahili olarak Combine'ı kullanır.

Combine, Swift Concurrency dikkate alınarak tasarlanmıştır: iOS 15'ten itibaren, bir Publisher .values aracılığıyla AsyncSequence'e dönüştürülebilir ve for-await-in döngülerinde kullanılabilir. Async işlevlerinden Publisher'a ters dönüşüm Future aracılığıyla yapılır. Apple WWDC 2024'e göre, tek seferlik asenkron çağrılar için async/await'in tanıtılmasına rağmen, Combine UIKit uygulamalarında akış verilerini işlemek için önerilen framework olmaya devam etmektedir.

Combine Temel Kavramları

Combine üç protokole dayanır: Publisher (Output türünde değerler yayar, Failure türünde bir hatayla başarısız olabilir), Subscriber (değerleri alır, Demand'i yönetir — talep edilen öğe sayısı), Subscription (iptal etme yeteneğiyle Publisher-Subscriber bağlantısını temsil eder). Veri kanalı abone olunduğunda başlatılır ve iptal, tamamlama veya hatayla sona erer. Demand, Combine'a özgü bir kavramdır: Subscriber, Publisher'a işlemeye hazır olduğu öğe sayısını söyler ve protokol düzeyinde geri basınç (backpressure) uygular.

Publisher ve Subscriber: Reaktif Akış Mimarisi

Publisher, iki ilişkili türe sahip bir protokoldür: Output (yayılan değerlerin türü) ve Failure (Error'ı uygulayan hata türü). Akış başarısız olamıyorsa, Failure Never olarak belirtilir — bu, Subscriber'a onReceive'ın yalnızca Output ile çağrılacağını garanti eder. Yerleşik Publisher'lar arasında Just (tek değer), Sequence (dizi), URLSession.DataTaskPublisher (ağ isteği), NotificationCenter.Publisher ve @Published özellik sarmalayıcısı bulunur.

swift
import Combine

// Bir diziden Publisher oluşturma
let publisher = [1, 2, 3, 4, 5].publisher

// Değer işleme ile Subscriber oluşturma
class PrintSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {
        subscription.request(.unlimited)
    }

    func receive(_ input: Int) -> Subscribers.Demand {
        print("Received: \(input)")
        return .unlimited
    }

    func receive(completion: Subscribers.Completion<Never>) {
        print("Tamamlandı")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription ve Demand

Subscription, Publisher ve Subscriber arasındaki aktif bağlantıyı temsil eden bir protokoldür. Subscriber, receive(subscription:) yönteminde Subscription'ı alır ve Demand'i belirtmek için request(_:) çağrısı yapar: .unlimited (tüm değerler), .max(N) (sınırlı sayı) veya .none (duraklama). Demand dinamik olarak değişebilir — Subscriber, veri alımı sırasında talep edilen öğe sayısını artırabilir veya azaltabilir. Bu, Publisher tarafında tamponlama olmadan geri basınç sağlar.

Subject ve CurrentValueSubject

Subject, Publisher ve Subscriber'ı birleştiren bir türdür. Bir Subject, Publisher (aboneler ona abone olur) ve aynı anda Subscriber (ona değerler gönderilir) olarak kullanılabilir. Combine iki tür Subject sağlar: PassthroughSubject (durum depolamaz, yalnızca yeni değerleri iletir) ve CurrentValueSubject (mevcut değeri depolar ve yeni abonelere iletir). Subject, zorunlu kodları Combine reaktif zincirlerine entegre etmek için gereklidir.

swift
let subject = PassthroughSubject<String, Never>()

// Publisher olarak abone olma
let cancellable = subject
    .map { $0.uppercased() }
    .sink { print($0) }

// Subscriber olarak değer gönderme
subject.send("hello")  // "HELLO" yazdırır
subject.send("world")  // "WORLD" yazdırır

CurrentValueSubject, bir başlangıç değerine ve value özelliğine sahip olmasıyla PassthroughSubject'ten ayrılır: bir abone, abonelik sırasında hemen mevcut değeri alır ve ardından sonraki tüm güncellemeleri alır. CurrentValueSubject.value okunabilir ve yazılabilir — value'yu değiştirmek otomatik olarak tüm abonelere yeni değeri gönderir. Bu, CurrentValueSubject'i MVVM mimarisinde durumu temsil etmek için ideal bir seçim haline getirir: ViewModel CurrentValueSubject yayınlar, View sink aracılığıyla değişikliklere abone olur.

Her iki Subject, send(completion: .finished) veya send(completion: .failure(error)) çağrısı yaparak akışı sonlandırabilir. Sonlandırmadan sonra Subject, olayları kabul etmeyi ve yaymayı durdurur. Sonlandırılmaması gereken uzun ömürlü akışlar (örneğin, UI olayları) için, yanlışlıkla send(completion:) çağrısı yapmaktan kaçınmak için Never Failure ile PassthroughSubject kullanılması önerilir.

Dönüşüm ve Birleştirme Operatörleri

Combine Operatörleri, yeni bir Publisher döndüren Publisher yöntemleridir. Her operatör, yukarı akış Publisher'ına abone olan ve dönüştürülmüş değerleri aşağı akışa yayan yeni bir nesne oluşturur. Publisher genel bir tür olduğundan, operatörler sıkı tür belirlemesini korur: map Output<A>'yı Output<B>'ye dönüştürür, tryMap hata olasılığı ekler. Combine yaklaşık 100 yerleşik operatör içerir.

KategoriOperatörAmaç
Dönüşümmap / tryMap / flatMapDeğerleri veya akışları dönüştürme
Filtrelemefilter / compactMap / removeDuplicatesDeğerleri seçme veya temizleme
BirleştirmecombineLatest / zip / mergeBirden çok Publisher'ı birleştirme
Zaman Kontrolüdebounce / throttle / delayOlayları geciktirme ve seyreltme
Hata Yönetimicatch / retry / replaceErrorFailure sonrası kurtarma
Demand Yönetimibuffer / collectGruplama veya tamponlama

flatMap Combine'da RxSwift sürümünden önemli bir farka sahiptir: aynı Failure türüne sahip bir Publisher döndüren bir closure alır ve iç içe Publisher'ı ana akışa düzleştirir. maxPublishers: .max(1) ile flatMap, switchMap gibi davranır — yeni bir değer geldiğinde önceki iç içe Publisher'ı iptal eder. Bu, arama senaryoları için kritiktir: yeni bir karakter yazıldığında önceki HTTP isteği otomatik olarak iptal edilir.

swift
// Önceki isteği iptal eden debounce arama
searchTextField.textPublisher
    .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
    .removeDuplicates()
    .flatMap(maxPublishers: .max(1)) { query in
        apiService.searchPublisher(query)
            .catch { _ in Just([]) }
    }
    .receive(on: DispatchQueue.main)
    .sink { results in
        self.tableView.reloadData()
    }
    .store(in: &cancellables)

Birleştirme operatörleri — combineLatest ve zip — RxSwift'e benzer şekilde çalışır: combineLatest, herhangi biri değiştiğinde tüm Publisher'ların en son değerlerinin bir demetini yayar; zip değerleri dizine göre eşleştirir. merge, aynı türdeki Publisher'ları tek bir akışta birleştirir, sıra koruması garanti edilmez. Combine ayrıca select'e sahiptir — birden çok arasından ilk tamamlanan Publisher'ı seçen nadir bir operatör — ve share — yeniden yürütme olmadan bir akışı birden çok aboneye çoklu yayınlama.

Scheduler'lar ve İş Parçacığı Yönetimi

Scheduler Combine'da, operatörler için yürütme bağlamını tanımlayan bir protokoldür. 5'ten fazla yerleşik Scheduler'a sahip RxSwift'in aksine, Combine mevcut Apple mekanizmalarını kullanır: DispatchQueue, RunLoop ve OperationQueue. Bu türlerin her biri Scheduler protokolüne uygundur ve ek adaptörler olmadan doğrudan receive(on:) ve subscribe(on:) öğelerine aktarılmalarına olanak tanır.

receive(on:) aşağı akışı belirtilen Scheduler'a geçirir — RxSwift'teki observeOn'e eşdeğerdir. receive(on:) sonrasındaki tüm operatörler belirtilen Scheduler'da yürütülür. subscribe(on:) yukarı akışı geçirir — Publisher yürütmesini etkiler. Tipik bir desen: arka plan çalışması için subscribe(on: DispatchQueue.global()) ve UI güncellemeleri için receive(on: DispatchQueue.main). SwiftUI'de .onReceive kullanıldığında, ana iş parçacığına yerleşik bağlama gerekli değildir, ancak sink için açık receive(on:).main önerilir.

swift
// Arka plan yükleme + ana iş parçacığında UI
URLSession.shared.dataTaskPublisher(for: url)
    .subscribe(on: DispatchQueue.global(qos: .background))
    .tryMap { data, response -> Data in
        guard let http = response as? HTTPURLResponse,
              http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
    .receive(on: DispatchQueue.main)
    .decode(type: User.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print($0) },
         receiveValue: { self.nameLabel.text = $0.name })
    .store(in: &cancellables)

RunLoop.main, UI işlemleri için DispatchQueue.main'e bir alternatiftir. Fark, RunLoop.main'in geçerli uygulama olay döngüsüne bağlı olması, DispatchQueue.main'in ise genel ana iş parçacığı kuyruğuna bağlı olmasıdır. UIKit için DispatchQueue.main, SwiftUI için RunLoop.main önerilir. ImmediateWhenScheduler işlemleri geçerli iş parçacığında eşzamanlı olarak yürütür — testler ve basit Publisher'lar için varsayılan olarak kullanılır.

Combine ve SwiftUI: ObservableObject ile Entegrasyon

ObservableObject, değişiklikleri yayınlayan nesneler için bir SwiftUI protokolüdür. ObservableObject'ı uygulayan bir sınıf, değişiklikleri SwiftUI'ye yeniden çizim gerektiğini otomatik olarak bildiren özellikler için @Published özellik sarmalayıcısını kullanabilir. Perde arkasında, @Published bir Publisher oluşturur ve wrappedValue değiştiğinde objectWillChange Publisher'ına bildirir. SwiftUI, @StateObject, @ObservedObject veya @EnvironmentObject aracılığıyla objectWillChange'e abone olur.

@Published ve @StateObject

@Published, Combine'ı SwiftUI'ye entegre etmenin en yaygın yoludur. Bir @Published özelliğinin değeri değiştiğinde, SwiftUI bu nesneyi kullanan tüm Views'leri günceller. @StateObject bir ObservableObject örneği oluşturur ve değişikliklerine abone olur. @StateObject ile oluşturulan bir View, @Published özellikleri değiştiğinde otomatik olarak yeniden çizilir. Nesnenin birden çok View arasında paylaşılması gerekiyorsa, @ObservedObject veya @EnvironmentObject kullanılır.

swift
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var age: Int = 0
    private var cancellables = Set<AnyCancellable>()

    init() {
        $name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { [weak self] newName in
                AnalyticsService.logNameChange(newName)
            }
            .store(in: &cancellables)
    }
}

struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        TextField("Name", text: $viewModel.name)
    }
}

AnyCancellable, bir abonelik iptal belirteci depolayan Cancellable için tür silme sarmalayıcısıdır. Set<AnyCancellable> aboneliklerin yaşam döngüsünü yönetir: sahip devre dışı bırakıldığında, tüm Cancellable'lar otomatik olarak iptal edilir. SwiftUI projelerinde, Set<AnyCancellable> ObservableObject sınıfında bildirilir ve abonelikler .store(in: &cancellables) aracılığıyla eklenir. UIKit için, UIViewController'da &cancellables veya manuel cancel() çağrıları aracılığıyla depolama ile aynı mekanizmalar kullanılır.

Combine vs RxSwift

Combine ve RxSwift aynı reaktif programlama görevlerini çözer ancak temel mimari farklılıklara sahiptir. Combine, iOS 13'e kadar geriye dönük uyumluluğa sahip Apple SDK'sının bir parçasıdır, RxSwift ise iOS 8+'ı destekleyen üçüncü taraf bir kütüphanedir. Combine, Failure genel tipi aracılığıyla sıkı hata türü belirlemesi kullanırken, RxSwift tek bir Error türü kullanır. Combine, platform düzeyinde SwiftUI ile entegre edilmiştir, RxSwift ise UI uzantıları için RxCocoa gerektirir.

Combine ve RxSwift arasındaki seçim proje gereksinimlerine bağlıdır. Minimum iOS sürümü >= 13 ise ve proje SwiftUI kullanıyorsa — Combine, yerleşik entegrasyon ve ek bağımlılık olmaması sayesinde doğal seçimdir. Proje iOS 11-12'yi destekliyorsa, mevcut bir RxSwift kod tabanı içeriyorsa veya yalnızca RxSwift'te bulunan belirli operatörler (örneğin, Observable.from(path:)) gerekiyorsa, RxSwift geçerli bir çözüm olmaya devam eder.

ÖzellikCombineRxSwift
GeliştiriciApple (SDK'ya entegre)ReactiveX (topluluk)
iOS SürümüiOS 13+iOS 8+
Hata TürüGenel Failure (UI için Never)Error (herhangi)
UI Entegrasyonu@Published + SwiftUIRxCocoa + UIKit
Operatörler~100 yerleşik400+ operatör
Swift Concurrency.values aracılığıyla (async dizi)Köprü kütüphanesi aracılığıyla

Sıkça Sorulan Sorular

PassthroughSubject ve CurrentValueSubject arasındaki fark nedir?

PassthroughSubject durum depolamaz — bir abone yalnızca abonelikten sonra gönderilen olayları alır. CurrentValueSubject mevcut değeri depolar ve abonelik sırasında her yeni aboneye hemen iletir. CurrentValueSubject, durumu temsil etmek için uygundur (örneğin, isLoggedIn).

Combine'da abonelik nasıl iptal edilir?

Bir abonelik AnyCancellable döndürür ve cancel() çağrıldığında veya devre dışı bırakıldığında iptal edilir. Grup yönetimi için Set<AnyCancellable> kullanın — set temizlendiğinde tüm abonelikler iptal edilir. Bu, RxSwift'teki DisposeBag'e benzer.

async/await'ten sonra Combine'ı öğrenmeli miyim?

Evet, Combine akış verileri için hala geçerlidir: UI olayları, debounce, combineLatest, WebSocket. async/await tek seferlik istekler için uygundur, Combine ise sürekli veya birden çok akış için uygundur. Her iki framework birbirini tamamlar — Publisher, AsyncSequence'e dönüştürülebilir.

UIKit ile Combine nasıl kullanılır?

UIKit'in yerleşik Publisher'ları yoktur, ancak Apple uzantılar sağlar: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Özel UI olayları için, Future veya Subject aracılığıyla bir Publisher'a sarılmış PassthroughSubject veya @IBAction kullanılır.

Combine'da geri basınç (backpressure) nedir?

Geri basınç (backpressure), akış hızını kontrol eden bir mekanizmadır: Subscriber, Demand aracılığıyla Publisher'a işlemeye hazır olduğu öğe sayısını bildirir. Demand = .max(1) ise, Publisher bir sonraki değeri göndermeden önce bir istek bekler. Bu, üretici ve tüketici hızları uyuşmadığında arabellek taşmasını önler.

Özet

  • Combine, iOS 13+ için Apple'ın yerel reaktif framework'üdür, Publisher-Subscriber deseni ve sıkı hata türü belirlemesiyle
  • Publisher ve Subscriber, geri basınç için Demand yönetimiyle bir iletişim kanalı oluşturur
  • Subject (Passthrough ve CurrentValue) zorunlu kodu reaktif zincirlere entegre eder
  • Operatörler map, flatMap, combineLatest, debounce, catch bildirimsel akış işleme sağlar
  • Scheduler'lar DispatchQueue ve RunLoop aracılığıyla UI'yı engellemeden yürütme iş parçacıklarını yönetir
  • SwiftUI, reaktif Views için @Published, ObservableObject ve @StateObject aracılığıyla Combine'ı kullanır
  • RxSwift iOS 8+ projeleri için bir alternatif olmaya devam eder, Combine iOS 13+ yeni projeler için seçimdir

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun