Combine — یک فریمورک بومی برنامهنویسی واکنشی از اپل است که در iOS 13، macOS Catalina، tvOS 13 و watchOS 6 معرفی شد. این فریمورک API اعلانی Swift را برای پردازش رویدادهای ناهمگام از طریق الگوی Publisher و Subscriber ارائه میدهد و نمایندگان، بلاکها و NotificationCenter را با یک زنجیره یکپارچه جایگزین میکند. به گفته Apple, 2025، Combine اساس SwiftUI و معماریهای مدرن iOS است و در ارتباط نزدیک با async/await و Structured Concurrency کار میکند. این فریمورک برای ترکیب عملیاتهای ناهمگام با تضمین امنیت نخ طراحی شده است.
نکات اصلی
Combine — یک فریمورک برنامهنویسی واکنشی اعلانی است که در SDK اپل تعبیه شده است. این فریمورک الگوی Reactive Streams را پیادهسازی میکند: Publisher مقادیر را تولید میکند، Subscriber آنها را مصرف میکند و عملگرها جریان را بین آنها تبدیل میکنند. Combine مشکل callbackها و نمایندگان را حل میکند و یک مدل ترکیب یکپارچه برای هر رویداد ناهمگام — از پاسخهای شبکه تا تغییرات UI — ارائه میدهد.
قبل از ظهور Combine، توسعهدهندگان iOS از کتابخانههای شخص ثالث، عمدتاً RxSwift استفاده میکردند. اپل Combine را به عنوان یک جایگزین بومی با یکپارچگی عمیق در اکوسیستم ایجاد کرد: این فریمورک از Objective-C از طریق پلهای @objc پشتیبانی میکند، با KVO (Key-Value Observing از طریق NSObject.keyValuePublisher) و NotificationCenter کار میکند و همچنین اساس SwiftUI است. تمام کامپوننتهای UIKit منتشر شده در SwiftUI از Combine در پسزمینه برای بهروزرسانی نماها استفاده میکنند.
Combine با در نظر گرفتن Swift Concurrency طراحی شده است: از iOS 15، Publisher را میتوان از طریق .values به AsyncSequence تبدیل کرد و در حلقههای for-await-in استفاده کرد. تبدیل معکوس توابع async به Publisher از طریق Future انجام میشود. به گفته Apple WWDC 2024، Combine با وجود ظهور async/await برای فراخوانیهای ناهمگام تکی، فریمورک توصیه شده برای پردازش دادههای جریانی در برنامههای UIKit باقی میماند.
Combine بر سه پروتکل استوار است: Publisher (مقادیری از نوع Output منتشر میکند، میتواند با خطایی از نوع Failure پایان یابد)، Subscriber (مقادیر را دریافت میکند، Demand — تعداد عناصر درخواستی را مدیریت میکند)، Subscription (ارتباط Publisher-Subscriber را با قابلیت لغو نشان میدهد). کانال انتقال داده در subscribe مقداردهی میشود و در cancel, completion یا error پایان مییابد. Demand — مفهوم منحصربهفرد Combine: Subscriber به Publisher اطلاع میدهد که چند عنصر را آماده پردازش است و backpressure را در سطح پروتکل پیادهسازی میکند.
Publisher — پروتکلی با دو نوع مرتبط: Output (نوع مقادیر منتشر شده) و Failure (نوع خطا که Error را پیادهسازی میکند). اگر جریان نتواند با خطا پایان یابد، Failure به صورت Never مشخص میشود — این به Subscriber تضمین میدهد که onReceive فقط با Output فراخوانی میشود. Publisherهای داخلی شامل Just (یک مقدار)، Sequence (آرایه)، URLSession.DataTaskPublisher (درخواست شبکه)، NotificationCenter.Publisher و @Published property wrapper هستند.
import Combine
// ایجاد Publisher از دنباله
let publisher = [1, 2, 3, 4, 5].publisher
// ایجاد Subscriber با پردازش مقادیر
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("تکمیل شد")
}
}
publisher.subscribe(PrintSubscriber())
Subscription — پروتکلی است که ارتباط فعال بین Publisher و Subscriber را نشان میدهد. Subscriber Subscription را در متد receive(subscription:) دریافت میکند و request(_:) را برای مشخص کردن Demand فراخوانی میکند: .unlimited (همه مقادیر)، .max(N) (تعداد محدود) یا .none (مکث). Demand میتواند به صورت پویا تغییر کند — Subscriber میتواند تعداد عناصر درخواستی را در فرآیند دریافت داده افزایش یا کاهش دهد. این امر backpressure را بدون بافر کردن در سمت Publisher تضمین میکند.
Subject — نوعی است که Publisher و Subscriber را ترکیب میکند. Subject میتواند به عنوان Publisher (میتوان روی آن مشترک شد) و همزمان به عنوان Subscriber (میتوان مقادیر را به آن ارسال کرد) استفاده شود. Combine دو نوع Subject ارائه میدهد: PassthroughSubject (وضعیت را ذخیره نمیکند، فقط مقادیر جدید را منتقل میکند) و CurrentValueSubject (مقدار جاری را ذخیره میکند و آن را به مشترکین جدید منتقل میکند). Subject برای یکپارچهسازی کد امری در زنجیرههای واکنشی Combine ضروری است.
let subject = PassthroughSubject<String, Never>()
// اشتراک به عنوان Publisher
let cancellable = subject
.map { $0.uppercased() }
.sink { print($0) }
// ارسال مقادیر به عنوان Subscriber
subject.send("hello") // "HELLO" چاپ میکند
subject.send("جهان") // "WORLD" چاپ میکند
CurrentValueSubject با PassthroughSubject در داشتن مقدار اولیه و ویژگی value متفاوت است: مشترک بلافاصله مقدار جاری را هنگام اشتراک دریافت میکند و سپس تمام بهروزرسانیهای بعدی را. CurrentValueSubject.value برای خواندن و نوشتن قابل دسترسی است — تغییر value به طور خودکار مقدار جدید را به همه مشترکین ارسال میکند. این باعث میشود CurrentValueSubject انتخاب ایدهآلی برای نمایش وضعیت در معماری MVVM باشد: ViewModel CurrentValueSubject را منتشر میکند، View با تغییرات از طریق sink مشترک میشود.
هر دو Subject امکان پایان دادن به جریان را با فراخوانی send(completion: .finished) یا send(completion: .failure(error)) فراهم میکنند. پس از پایان، Subject دریافت و ارسال رویدادها را متوقف میکند. برای جریانهای طولانیمدت که نباید پایان یابند (مثلاً رویدادهای UI)، توصیه میشود از PassthroughSubject با Never Failure استفاده شود تا فراخوانی تصادفی send(completion:) حذف شود.
عملگرها Combine — متدهای Publisher هستند که Publisher جدیدی بازمیگردانند. هر عملگر یک شیء جدید ایجاد میکند که در Publisher بالادستی مشترک میشود و مقادیر تبدیل شده را به پاییندست منتشر میکند. از آنجا که Publisher یک نوع جنریک است، عملگرها تایپسازی سختگیرانه را حفظ میکنند: map Output<A> را به Output<B> تبدیل میکند، tryMap امکان خطا را اضافه میکند. Combine حدود 100 عملگر داخلی دارد.
| دستهبندی | عملگر | هدف |
|---|---|---|
| تبدیل | map / tryMap / flatMap | تبدیل مقادیر یا جریانها |
| فیلتر | filter / compactMap / removeDuplicates | انتخاب یا پاکسازی مقادیر |
| ترکیب | combineLatest / zip / merge | ترکیب چند Publisher |
| مدیریت زمان | debounce / throttle / delay | تأخیر و کاهش تعداد رویدادها |
| مدیریت خطا | catch / retry / replaceError | بازیابی پس از Failure |
| مدیریت Demand | buffer / collect | گروهبندی یا بافر کردن |
flatMap در Combine تفاوت مهمی با نسخه RxSwift دارد: یک closure میپذیرد که Publisher با همان نوع Failure برمیگرداند و Publisher تو در تو را در جریان اصلی باز میکند. flatMap با maxPublishers: .max(1) مانند switchMap رفتار میکند — با دریافت مقدار جدید، Publisher تو در توی قبلی را لغو میکند. این برای سناریوهای جستجو حیاتی است: با وارد کردن کاراکتر جدید، درخواست HTTP قبلی به طور خودکار لغو میشود.
// جستجوی debounce با لغو درخواست قبلی
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)
عملگرهای ترکیب — combineLatest و zip — مشابه RxSwift کار میکنند: combineLatest هنگام تغییر هر یک از Publisherها، چندتایی از آخرین مقادیر همه Publisherها را منتشر میکند؛ zip مقادیر را به صورت زوج بر اساس ایندکس ترکیب میکند. merge Publisherهای هم نوع را در یک جریان ادغام میکند، preserveOrder تضمین نمیشود. در Combine همچنین select — عملگر نادری که اولین Publisher تکمیل شده از بین چندین مورد را انتخاب میکند، و share — multicast جریان به چندین مشترک بدون اجرای مجدد وجود دارد.
Scheduler در Combine — پروتکلی است که زمینه اجرا را برای عملگرها تعیین میکند. برخلاف RxSwift با 5+ Scheduler داخلی، Combine از مکانیسمهای موجود Apple استفاده میکند: DispatchQueue، RunLoop و OperationQueue. هر یک از این انواع با پروتکل Scheduler مطابقت دارد که امکان ارسال مستقیم آنها به receive(on:) و subscribe(on:) بدون آداپتورهای اضافی را فراهم میکند.
receive(on:) downstream را به Scheduler مشخص شده تغییر میدهد — معادل observeOn در RxSwift. همه عملگرهای بعد از receive(on:) در Scheduler مشخص شده اجرا میشوند. subscribe(on:) upstream را تغییر میدهد — بر اجرای Publisher تأثیر میگذارد. الگوی معمول: subscribe(on: DispatchQueue.global()) برای کار در پسزمینه و receive(on: DispatchQueue.main) برای بهروزرسانی UI. در SwiftUI هنگام استفاده از .onReceive اتصال داخلی به نخ اصلی لازم نیست، اما برای sink توصیه میشود receive(on:).main به صراحت ذکر شود.
// بارگیری پسزمینه + 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 — جایگزینی برای DispatchQueue.main برای عملیاتهای UI. تفاوت در این است که RunLoop.main به حلقه رویداد جاری برنامه متصل است، در حالی که DispatchQueue.main به صف جهانی نخ اصلی متصل است. برای UIKit، DispatchQueue.main توصیه میشود، برای SwiftUI — RunLoop.main. ImmediateWhenScheduler عملیاتها را به صورت همزمان در نخ جاری اجرا میکند — به طور پیشفرض برای تستها و Publisherهای ساده استفاده میشود.
ObservableObject — پروتکل SwiftUI برای اشیایی که تغییرات را منتشر میکنند. کلاسی که ObservableObject را پیادهسازی میکند میتواند از property wrapper @Published برای ویژگیهایی استفاده کند که تغییرات آنها به طور خودکار SwiftUI را از نیاز به بازترسیم مطلع میکند. در پسزمینه، @Published یک Publisher ایجاد میکند که هنگام تغییر wrappedValue به Publisher objectWillChange اطلاع میدهد. SwiftUI از طریق @StateObject، @ObservedObject یا @EnvironmentObject در objectWillChange مشترک میشود.
@Published — رایجترین روش یکپارچهسازی Combine در SwiftUI. هنگامی که مقدار ویژگی @Published تغییر میکند، SwiftUI تمام Viewهای استفادهکننده از این شیء را بهروزرسانی میکند. @StateObject یک نمونه ObservableObject ایجاد میکند و در تغییرات آن مشترک میشود. View ایجاد شده با @StateObject به طور خودکار هنگام تغییر ویژگیهای @Published بازترسیم میشود. اگر شیء باید بین چندین View منتقل شود، از @ObservedObject یا @EnvironmentObject استفاده میشود.
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 — نوع پاککننده برای Cancellable که توکن لغو اشتراک را ذخیره میکند. Set<AnyCancellable> چرخه حیات اشتراکها را مدیریت میکند: هنگام مقدارزدایی مالک، همه Cancellableها به طور خودکار لغو میشوند. در پروژههای SwiftUI، Set<AnyCancellable> در کلاس ObservableObject اعلام میشود و اشتراکها از طریق .store(in: &cancellables) اضافه میشوند. برای UIKit از همان مکانیسمها با ذخیرهسازی در UIViewController از طریق &cancellables یا فراخوانی دستی cancel() استفاده میشود.
Combine و RxSwift وظایف یکسان برنامهنویسی واکنشی را حل میکنند، اما تفاوتهای معماری بنیادی دارند. Combine — بخشی از Apple SDK با سازگاری معکوس تا iOS 13، RxSwift — کتابخانه شخص ثالث با پشتیبانی از iOS 8+. Combine از تایپسازی سختگیرانه خطا از طریق Failure generic استفاده میکند، RxSwift — از نوع یکپارچه Error. Combine در سطح پلتفرم با SwiftUI یکپارچه شده است، RxSwift برای توسعههای UI نیاز به RxCocoa دارد.
انتخاب بین Combine و RxSwift به الزامات پروژه بستگی دارد. اگر حداقل نسخه iOS >= 13 و پروژه از SwiftUI استفاده میکند — Combine انتخاب طبیعی به دلیل یکپارچگی داخلی و عدم وجود وابستگیهای اضافی است. اگر پروژه از iOS 11-12 پشتیبانی میکند، دارای پایگاه کد RxSwift موجود است یا به عملگرهای خاصی که فقط در RxSwift موجود است نیاز دارد (مثلاً Observable.from(path:))، — RxSwift راهحل موجهی باقی میماند.
| ویژگی | Combine | RxSwift |
|---|---|---|
| توسعهدهنده | Apple (تعبیه شده در SDK) | ReactiveX (جامعه) |
| نسخه iOS | iOS 13+ | iOS 8+ |
| نوع خطا | Generic Failure (Never برای UI) | Error (هر نوع) |
| یکپارچگی UI | @Published + SwiftUI | RxCocoa + UIKit |
| عملگرها | ~100 عملگر داخلی | 400+ عملگر |
| Swift Concurrency | از طریق .values (async sequence) | از طریق کتابخانه پل |
سوالات متداول
PassthroughSubject وضعیت را ذخیره نمیکند — مشترک فقط رویدادهای ارسال شده پس از اشتراک را دریافت میکند. CurrentValueSubject مقدار جاری را ذخیره میکند و آن را به هر مشترک جدید بلافاصله هنگام اشتراک منتقل میکند. CurrentValueSubject برای نمایش وضعیت مناسب است (مثلاً isLoggedIn).
اشتراک AnyCancellable برمیگرداند که با فراخوانی cancel() یا هنگام مقدارزدایی لغو میشود. برای مدیریت گروهی از Set<AnyCancellable> استفاده کنید — همه اشتراکها هنگام پاک شدن set لغو میشوند. این معادل DisposeBag در RxSwift است.
بله، Combine برای دادههای جریانی همچنان مرتبط است: رویدادهای UI، debounce، combineLatest، WebSocket. async/await برای درخواستهای تکی مناسب است، Combine — برای جریانهای پیوسته یا چندگانه. هر دو فریمورک مکمل یکدیگر هستند — Publisher میتواند به AsyncSequence تبدیل شود.
UIKit Publisherهای داخلی ندارد، اما اپل توسعههایی ارائه میدهد: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. برای رویدادهای UI سفارشی از PassthroughSubject یا @IBAction استفاده میشود که از طریق Future یا Subject در Publisher پیچیده شده است.
Backpressure — مکانیزم کنترل سرعت جریان: Subscriber از طریق Demand به Publisher اطلاع میدهد که چند عنصر را آماده پردازش است. اگر Demand = .max(1) باشد، Publisher قبل از ارسال مقدار بعدی منتظر درخواست میماند. این کار از سرریز بافر در صورت ناهماهنگی سرعت تولیدکننده و مصرفکننده جلوگیری میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید