Combine হল Apple-এর নেটিভ রিঅ্যাকটিভ প্রোগ্রামিং ফ্রেমওয়ার্ক, যা iOS 13, macOS Catalina, tvOS 13 এবং watchOS 6-এ চালু হয়েছে। এটি Publisher এবং Subscriber প্যাটার্নের মাধ্যমে অ্যাসিনক্রোনাস ইভেন্ট হ্যান্ডলিংয়ের জন্য একটি ডিক্লারেটিভ Swift API প্রদান করে, ডেলিগেট, ক্লোজার এবং NotificationCenter-কে একটি ঐক্যবদ্ধ চেইনে প্রতিস্থাপন করে। Apple, 2025 অনুযায়ী, Combine হল SwiftUI এবং আধুনিক iOS আর্কিটেকচারের ভিত্তি, যা async/await এবং Structured Concurrency-এর সাথে ঘনিষ্ঠভাবে কাজ করে। ফ্রেমওয়ার্কটি থ্রেড-নিরাপত্তা গ্যারান্টি সহ অ্যাসিনক্রোনাস অপারেশনগুলির কম্পোজিশনের জন্য ডিজাইন করা হয়েছে।
মূল পয়েন্ট
Combine হল একটি ডিক্লারেটিভ রিঅ্যাকটিভ প্রোগ্রামিং ফ্রেমওয়ার্ক যা Apple SDK-তে নির্মিত। এটি রিঅ্যাকটিভ স্ট্রিম প্যাটার্ন বাস্তবায়ন করে: Publisher মান উৎপন্ন করে, Subscriber সেগুলো গ্রহণ করে এবং অপারেটর তাদের মধ্যে স্ট্রিম রূপান্তর করে। Combine কলব্যাক এবং ডেলিগেটের সমস্যা সমাধান করে যেকোনো অ্যাসিনক্রোনাস ইভেন্ট — নেটওয়ার্ক রেসপন্স থেকে UI পরিবর্তন — এর জন্য একটি ঐক্যবদ্ধ কম্পোজিশন মডেল প্রদান করে।
Combine-এর আগে, iOS ডেভেলপাররা তৃতীয়-পক্ষের লাইব্রেরি, প্রধানত RxSwift ব্যবহার করত। Apple Combine তৈরি করেছে ইকোসিস্টেমে গভীর ইন্টিগ্রেশন সহ একটি নেটিভ বিকল্প হিসেবে: ফ্রেমওয়ার্কটি @objc ব্রিজের মাধ্যমে Objective-C সমর্থন করে, KVO (NSObject.keyValuePublisher-এর মাধ্যমে Key-Value Observing) এবং NotificationCenter-এর সাথে কাজ করে এবং SwiftUI-এর ভিত্তি হিসেবেও কাজ করে। SwiftUI-তে প্রকাশিত সমস্ত UIKit কম্পোনেন্ট ভিউ আপডেটের জন্য আন্ডার দ্য হুড Combine ব্যবহার করে।
Combine Swift Concurrency বিবেচনায় নিয়ে ডিজাইন করা হয়েছে: iOS 15 থেকে শুরু করে, Publisher কে .values-এর মাধ্যমে AsyncSequence-এ রূপান্তর করা যায় এবং for-await-in লুপে ব্যবহার করা যায়। async ফাংশনকে Publisher-এ বিপরীত রূপান্তর Future-এর মাধ্যমে করা হয়। Apple WWDC 2024 অনুযায়ী, একক অ্যাসিনক্রোনাস কলের জন্য async/await চালু হওয়া সত্ত্বেও, UIKit অ্যাপ্লিকেশনে স্ট্রিমিং ডেটা হ্যান্ডলিংয়ের জন্য Combine প্রস্তাবিত ফ্রেমওয়ার্ক রয়ে গেছে।
Combine তিনটি প্রোটোকলের উপর ভিত্তি করে: Publisher (Output টাইপের মান নির্গত করে, Failure টাইপের ত্রুটির সাথে ব্যর্থ হতে পারে), Subscriber (মান গ্রহণ করে, Demand পরিচালনা করে — অনুরোধকৃত উপাদানের সংখ্যা), Subscription (বাতিল করার ক্ষমতা সহ Publisher-Subscriber সংযোগ উপস্থাপন করে)। ডেটা চ্যানেল সাবস্ক্রাইব করার সময় শুরু হয় এবং বাতিল, সম্পূর্ণ বা ত্রুটিতে শেষ হয়। Demand হল একটি অনন্য Combine ধারণা: Subscriber Publisher-কে জানায় যে এটি কতগুলি উপাদান প্রক্রিয়া করতে প্রস্তুত, প্রোটোকল স্তরে ব্যাকপ্রেশার বাস্তবায়ন করে।
Publisher হল দুটি অ্যাসোসিয়েটেড টাইপ সহ একটি প্রোটোকল: Output (নির্গত মানের টাইপ) এবং Failure (ত্রুটির টাইপ যা Error প্রয়োগ করে)। যদি স্ট্রিম ব্যর্থ না হতে পারে, Failure কে Never হিসেবে নির্দিষ্ট করা হয় — এটি Subscriber-কে গ্যারান্টি দেয় যে onReceive শুধুমাত্র Output দিয়ে কল করা হবে। বিল্ট-ইন Publishers-এর মধ্যে রয়েছে Just (একক মান), Sequence (অ্যারে), URLSession.DataTaskPublisher (নেটওয়ার্ক অনুরোধ), NotificationCenter.Publisher এবং @Published প্রপার্টি র্যাপার।
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 receive(subscription:) পদ্ধতিতে Subscription গ্রহণ করে এবং Demand নির্দিষ্ট করতে request(_:) কল করে: .unlimited (সমস্ত মান), .max(N) (সীমিত সংখ্যা) বা .none (বিরাম)। Demand গতিশীলভাবে পরিবর্তিত হতে পারে — Subscriber ডেটা গ্রহণের সময় অনুরোধকৃত উপাদানের সংখ্যা বাড়াতে বা কমাতে পারে। এটি 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") // "WORLD" প্রিন্ট করে
CurrentValueSubject PassthroughSubject থেকে পৃথক কারণ এতে একটি প্রাথমিক মান এবং value প্রপার্টি রয়েছে: একজন সাবস্ক্রাইবার সাবস্ক্রিপশনের সময় অবিলম্বে বর্তমান মান গ্রহণ করে, এবং তারপর সমস্ত পরবর্তী আপডেট। CurrentValueSubject.value পড়া এবং লেখা যায় — value পরিবর্তন করা স্বয়ংক্রিয়ভাবে সমস্ত সাবস্ক্রাইবারকে নতুন মান পাঠায়। এটি CurrentValueSubject-কে MVVM আর্কিটেকচারে অবস্থা উপস্থাপনের জন্য একটি আদর্শ পছন্দ করে তোলে: ViewModel CurrentValueSubject প্রকাশ করে, View sink-এর মাধ্যমে পরিবর্তনের সাথে সাবস্ক্রাইব করে।
উভয় Subject send(completion: .finished) বা send(completion: .failure(error)) কল করে স্ট্রিম শেষ করতে পারে। শেষ হওয়ার পর, Subject ইভেন্ট গ্রহণ এবং নির্গত করা বন্ধ করে দেয়। দীর্ঘস্থায়ী স্ট্রিমের জন্য যা শেষ হওয়া উচিত নয় (যেমন UI ইভেন্ট), ভুলবশত send(completion:) কল করা এড়াতে Never Failure সহ PassthroughSubject ব্যবহার করার পরামর্শ দেওয়া হয়।
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 সংস্করণ থেকে একটি গুরুত্বপূর্ণ পার্থক্য রয়েছে: এটি একটি ক্লোজার গ্রহণ করে যা একই Failure টাইপ সহ একটি Publisher ফেরত দেয় এবং নেস্টেড Publisher-কে মূল স্ট্রিমে সমতল করে। maxPublishers: .max(1) সহ flatMap switchMap-এর মতো আচরণ করে — যখন একটি নতুন মান আসে তখন আগের নেস্টেড Publisher বাতিল করে। এটি অনুসন্ধান পরিস্থিতির জন্য গুরুত্বপূর্ণ: যখন একটি নতুন অক্ষর টাইপ করা হয়, পূর্ববর্তী HTTP অনুরোধ স্বয়ংক্রিয়ভাবে বাতিল হয়ে যায়।
// পূর্ববর্তী অনুরোধ বাতিল সহ ডিবাউন্স অনুসন্ধান
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-কে একটি স্ট্রিমে একত্রিত করে, ক্রম সংরক্ষণের গ্যারান্টি নেই। Combine-এ selectও রয়েছে — একটি বিরল অপারেটর যা বেশ কয়েকটির মধ্যে প্রথম সম্পন্ন হওয়া Publisher বেছে নেয়, এবং share — পুনরায় নির্বাহ ছাড়াই একাধিক সাবস্ক্রাইবারে স্ট্রিম মাল্টিকাস্ট।
Scheduler Combine-এ একটি প্রোটোকল যা অপারেটরদের জন্য নির্বাহ প্রসঙ্গ সংজ্ঞায়িত করে। RxSwift-এর 5+ বিল্ট-ইন Schedulers-এর বিপরীতে, Combine বিদ্যমান Apple প্রক্রিয়া ব্যবহার করে: DispatchQueue, RunLoop এবং OperationQueue। এই প্রতিটি টাইপ Scheduler প্রোটোকল মেনে চলে, যা এগুলিকে অতিরিক্ত অ্যাডাপ্টার ছাড়াই সরাসরি receive(on:) এবং subscribe(on:) এ পাস করার অনুমতি দেয়।
receive(on:) ডাউনস্ট্রিমকে নির্দিষ্ট Scheduler-এ স্যুইচ করে — RxSwift-এ observeOn-এর সমতুল্য। receive(on:) এর পরে সমস্ত অপারেটর নির্দিষ্ট Scheduler-এ নির্বাহিত হয়। subscribe(on:) আপস্ট্রিম স্যুইচ করে — Publisher নির্বাহকে প্রভাবিত করে। একটি সাধারণ প্যাটার্ন: ব্যাকগ্রাউন্ড কাজের জন্য subscribe(on: DispatchQueue.global()) এবং UI আপডেটের জন্য receive(on: DispatchQueue.main)। 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 UI অপারেশনের জন্য DispatchQueue.main-এর একটি বিকল্প। পার্থক্য হল যে RunLoop.main বর্তমান অ্যাপ্লিকেশন ইভেন্ট লুপের সাথে আবদ্ধ, যেখানে DispatchQueue.main মূল থ্রেড গ্লোবাল কিউ-এর সাথে আবদ্ধ। UIKit-এর জন্য DispatchQueue.main সুপারিশ করা হয়, SwiftUI-এর জন্য — RunLoop.main। ImmediateWhenScheduler বর্তমান থ্রেডে সিঙ্ক্রোনাসভাবে অপারেশন সম্পাদন করে — পরীক্ষা এবং সাধারণ Publishers-এর জন্য ডিফল্টরূপে ব্যবহৃত হয়।
ObservableObject হল SwiftUI-এর একটি প্রোটোকল যা পরিবর্তন প্রকাশকারী অবজেক্টের জন্য। ObservableObject প্রয়োগকারী একটি ক্লাস @Published প্রপার্টি র্যাপার ব্যবহার করতে পারে সেই প্রপার্টিগুলির জন্য যাদের পরিবর্তন স্বয়ংক্রিয়ভাবে SwiftUI-কে পুনরায় আঁকার প্রয়োজনীয়তা সম্পর্কে জানায়। আন্ডার দ্য হুড, @Published একটি Publisher তৈরি করে যা wrappedValue পরিবর্তিত হলে objectWillChange Publisher-কে জানায়। SwiftUI @StateObject, @ObservedObject বা @EnvironmentObject-এর মাধ্যমে objectWillChange-এ সাবস্ক্রাইব করে।
@Published হল SwiftUI-তে Combine সংহত করার সবচেয়ে সাধারণ উপায়। যখন একটি @Published প্রপার্টির মান পরিবর্তিত হয়, SwiftUI সেই অবজেক্ট ব্যবহার করে সমস্ত View আপডেট করে। @StateObject একটি ObservableObject ইন্সট্যান্স তৈরি করে এবং এর পরিবর্তনগুলিতে সাবস্ক্রাইব করে। @StateObject-এর সাথে তৈরি একটি View @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 জেনেরিক-এর মাধ্যমে কঠোর ত্রুটি টাইপিং ব্যবহার করে, 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+ |
| ত্রুটির টাইপ | জেনেরিক Failure (UI-এর জন্য Never) | Error (যেকোনো) |
| UI ইন্টিগ্রেশন | @Published + SwiftUI | RxCocoa + UIKit |
| অপারেটর | ~100 বিল্ট-ইন | 400+ অপারেটর |
| Swift Concurrency | .values-এর মাধ্যমে (async সিকোয়েন্স) | ব্রিজ লাইব্রেরির মাধ্যমে |
সচরাচর জিজ্ঞাস্য
PassthroughSubject অবস্থা সংরক্ষণ করে না — সাবস্ক্রাইবার শুধুমাত্র সাবস্ক্রিপশনের পরে পাঠানো ইভেন্ট পায়। CurrentValueSubject বর্তমান মান সংরক্ষণ করে এবং এটি প্রতিটি নতুন সাবস্ক্রাইবারকে সাবস্ক্রিপশনের সময় অবিলম্বে পাস করে। CurrentValueSubject অবস্থা উপস্থাপনের জন্য উপযুক্ত (যেমন isLoggedIn)।
একটি সাবস্ক্রিপশন AnyCancellable ফেরত দেয়, যা cancel() কল করলে বা ডিইনিশিয়ালাইজেশনে বাতিল হয়। গ্রুপ ম্যানেজমেন্টের জন্য Set<AnyCancellable> ব্যবহার করুন — সেট পরিষ্কার হলে সমস্ত সাবস্ক্রিপশন বাতিল হয়। এটি RxSwift-এর DisposeBag-এর অনুরূপ।
হ্যাঁ, Combine স্ট্রিমিং ডেটার জন্য প্রাসঙ্গিক রয়ে গেছে: UI ইভেন্ট, debounce, combineLatest, WebSocket। async/await একক অনুরোধের জন্য সুবিধাজনক, Combine — ক্রমাগত বা একাধিক স্ট্রিমের জন্য। উভয় ফ্রেমওয়ার্ক পরিপূরক — Publisher কে AsyncSequence-এ রূপান্তর করা যায়।
UIKit-এ বিল্ট-ইন Publishers নেই, কিন্তু Apple এক্সটেনশন প্রদান করে: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher। কাস্টম UI ইভেন্টের জন্য, Future বা Subject-এর মাধ্যমে Publisher-এ মোড়ানো PassthroughSubject বা @IBAction ব্যবহার করা হয়।
ব্যাকপ্রেশার হল প্রবাহের হার নিয়ন্ত্রণের একটি প্রক্রিয়া: Subscriber Demand-এর মাধ্যমে Publisher-কে জানায় যে এটি কতগুলি উপাদান প্রক্রিয়া করতে প্রস্তুত। যদি Demand = .max(1) হয়, Publisher পরবর্তী মান পাঠানোর আগে অনুরোধের জন্য অপেক্ষা করে। এটি প্রযোজক এবং ভোক্তার গতির অমিল হলে বাফার ওভারফ্লো প্রতিরোধ করে।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন