Callback হল একটি ফাংশন যা আরেকটি ফাংশনে আর্গুমেন্ট হিসেবে পাস করা হয় এবং একটি অ্যাসিনক্রোনাস অপারেশন শেষ হওয়ার পরে নির্বাহিত হয়। মোবাইল ডেভেলপমেন্টে, নেটওয়ার্ক রিকোয়েস্টের ফলাফল, ডেটাবেস অপারেশন এবং অ্যানিমেশন প্রক্রিয়াকরণের জন্য callback ব্যবহার করা হয়। Apple ডকুমেন্টেশন (2025) অনুসারে, Swift-এ ক্লোজারগুলি হল callback-এর প্রাথমিক রূপ এবং URLSession, GCD এবং Combine-এ ব্যবহৃত হয়। Android-এ, callback ইন্টারফেস, Kotlin ল্যাম্বডা এবং ListenableFuture-এর মাধ্যমে বাস্তবায়িত হয়।
মূল বিষয়
Callback (কলব্যাক ফাংশন) হল নির্বাহযোগ্য কোড যা আরেকটি ফাংশনে পাস করা হয় এবং একটি নির্দিষ্ট কাজ শেষ হওয়ার পরে কল করা হয়। মোবাইল ডেভেলপমেন্টে, callback অ্যাসিনক্রোনাস প্রোগ্রামিং-এর একটি মৌলিক প্রক্রিয়া, যা মূল থ্রেড ব্লক না করেই নেটওয়ার্ক রিকোয়েস্ট, টাইমার, অ্যানিমেশন এবং I/O অপারেশন শেষ হওয়ার প্রতিক্রিয়া জানাতে দেয়। Swift এবং Kotlin callback তৈরির জন্য অন্তর্নির্মিত সিনট্যাক্টিক কাঠামো প্রদান করে — যথাক্রমে ক্লোজার এবং ল্যাম্বডা।
একটি উচ্চ-ক্রম ফাংশন আরেকটি ফাংশনকে প্যারামিটার হিসেবে গ্রহণ করে এবং নিজের মূল লজিক নির্বাহ করার পরে সেটিকে কল করে। নিয়ন্ত্রণ প্রবাহ callback-এর মাধ্যমে কলারের কাছে ফিরে যায়, তাই এই নাম। iOS-এ, callback UIKit (UIView.animate অ্যানিমেশন), Foundation (URLSession.dataTask) এবং Combine (sink)-এ ব্যবহৃত হয়। Android-এ, callback View.OnClickListener, Retrofit Callback এবং Room DAO-তে ব্যবহৃত হয়। আধুনিক API ক্রমবর্ধমানভাবে callback-কে async/await বা করুটিন দিয়ে প্রতিস্থাপন করছে, তবে লিগ্যাসি কোড এবং নিম্ন-স্তরের API-র সাথে কাজ করার জন্য callback বোঝা প্রয়োজন।
Callback সিনক্রোনাস (ফাংশনের ভিতরে সাথে সাথেই কল) এবং অ্যাসিনক্রোনাস (পরে অন্য থ্রেড বা কিউ থেকে কল) হতে পারে। সিনক্রোনাস callback সর্টিং (কম্প্যারেটর) এবং কালেকশন ট্রাভার্সালের জন্য ব্যবহৃত হয়। অ্যাসিনক্রোনাস callback নেটওয়ার্ক রিকোয়েস্ট, ফাইল রিডিং এবং সেন্সরের সাথে কাজ করার জন্য ব্যবহৃত হয়। থ্রেডিং বোঝার জন্য পার্থক্যটি গুরুত্বপূর্ণ: সিনক্রোনাস callback একই থ্রেডে নির্বাহিত হয়, অ্যাসিনক্রোনাস callback ডিসপ্যাচার (iOS-এ DispatchQueue, Kotlin-এ Dispatchers) দ্বারা নির্ধারিত থ্রেডে নির্বাহিত হয়।
উভয় প্ল্যাটফর্মে callback প্রক্রিয়া একই নীতির উপর ভিত্তি করে: একটি ফাংশন প্রথম শ্রেণীর বস্তু হিসেবে পাস করা হয় এবং নির্বাহের মুহূর্ত পর্যন্ত সংরক্ষিত থাকে। তবে, বিভিন্ন ভাষার দৃষ্টান্তের কারণে বাস্তবায়ন ভিন্ন। iOS-এ, callback হল একটি ক্লোজার যা আশেপাশের প্রসঙ্গ থেকে ভেরিয়েবল ক্যাপচার করে। Android-এ, callback প্রায়শই বেনামী ক্লাস বা Kotlin ল্যাম্বডা এক্সপ্রেশনের মাধ্যমে বাস্তবায়িত হয়, যা FunctionalInterface-এ কম্পাইল হয়।
যখন একটি অ্যাসিনক্রোনাস ফাংশন কল করা হয়, ক্লোজারটি ক্যাপচার করা ভেরিয়েবল সহ heap-এ সংরক্ষিত হয়। যখন অপারেশন শেষ হয়, GCD বা OperationQueue সিস্টেম উপযুক্ত কিউতে (মূল কিউ বা ব্যাকগ্রাউন্ড কিউ) callback রাখে। নির্বাহের পরে, যখন কোন শক্তিশালী রেফারেন্স থাকে না, তখন callback মেমরি থেকে মুছে ফেলা হয়। ক্যাপচার লিস্ট ([weak self]) ডিলোকেশনের পরে অবজেক্ট ধরে রাখা প্রতিরোধ করে। ক্যাপচার লিস্ট ছাড়া, retain cycle তৈরি হয় যেখানে অবজেক্ট এবং callback একে অপরকে রেফারেন্স করে।
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
completion(.success(data))
}
task.resume()
}
// [weak self] এর সাথে ব্যবহার
fetchData { [weak self] result in
guard let self else { return }
switch result {
case .success(let data):
self.updateUI(data)
case .failure(let error):
self.showError(error)
}
}
Android-এ, callback একটি ইন্টারফেস বা ল্যাম্বডার মাধ্যমে পাস করা হয়। ExecutorService বা করুটিনের মাধ্যমে অ্যাসিনক্রোনাস অপারেশন নির্বাহ করার সময়, ব্যাকগ্রাউন্ড কাজ শেষ হওয়া পর্যন্ত callback মেমরিতে সংরক্ষিত থাকে। Kotlin ল্যাম্বডা বেনামী ক্লাসে কম্পাইল হয় যা বাহ্যিক ভেরিয়েবল ক্যাপচার করে। JVM-এ দুর্বল রেফারেন্সের অনুপস্থিতিতে ম্যানুয়াল ব্যবস্থাপনা প্রয়োজন: onDestroy()-এ callback নাল করা বা Job.cancel()-এর মাধ্যমে করুটিন বাতিল করা। ViewModel এবং LiveData স্থাপত্য উপাদান স্তরে এই সমস্যার সমাধান করে।
interface Callback<T> {
fun onSuccess(data: T)
fun onError(error: Throwable)
}
class Repository {
fun loadData(callback: Callback<List<User>>) {
thread {
try {
val result = api.fetchUsers()
runOnUiThread { callback.onSuccess(result) }
} catch (e: Exception) {
runOnUiThread { callback.onError(e) }
}
}
}
}
// ল্যাম্বডার সাথে ব্যবহার
repository.loadData(object : Callback<List<User>> {
override fun onSuccess(data: List<User>) { showUsers(data) }
override fun onError(error: Throwable) { showError(error.message) }
})
Callback সিনট্যাক্স ভাষার ফাংশনকে প্রথম শ্রেণীর বস্তু হিসেবে ব্যবহার করার ক্ষমতা দ্বারা নির্ধারিত হয়। Swift-এ, ক্লোজারগুলির স্বয়ংক্রিয় আর্গুমেন্ট নাম ($0, $1) সহ সংক্ষিপ্ত সিনট্যাক্স আছে। Kotlin-এ, ল্যাম্বডা একক আর্গুমেন্টের জন্য it সমর্থন করে। ভেরিয়েবল ক্যাপচার (Swift-এ ক্যাপচার লিস্ট বনাম Kotlin-এ পরিবর্তনযোগ্য রেফারেন্স) এবং টাইপিং (Result
Swift ক্লোজার হল কোডের একটি স্বয়ংসম্পূর্ণ ব্লক যা অন্য ফাংশনে পাস এবং ব্যবহার করা যেতে পারে। ক্লোজার গ্লোবাল (নামকৃত), নেস্টেড এবং এক্সপ্রেশন-স্তরের হতে পারে। @escaping সেই ক্লোজারকে চিহ্নিত করে যা ফাংশন ফিরে আসার পরে নির্বাহিত হবে — এটি অ্যাসিনক্রোনাস callback-এর জন্য বাধ্যতামূলক প্রয়োজন। @escaping ছাড়া, ক্লোজার শুধুমাত্র ফাংশন বডির ভিতরেই নির্বাহ করা যেতে পারে। Trailing closure সিনট্যাক্স বন্ধনীর পরে ক্লোজার পাস করার অনুমতি দেয়: fetchData { result in ... }।
typealias NetworkResult = (Result<[String: Any], Error>) -> Void
func performRequest(
url: URL,
then handler: @escaping NetworkResult
) {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
handler(Result {
guard let json = try JSONSerialization.jsonObject(with: data)
else { throw NetworkError.invalidData }
return json as! [String: Any]
})
}
task.resume()
}
performRequest(url: url) { result in
switch result {
case .success(let json): process(json)
case .failure(let error): log(error.localizedDescription)
}
}
Kotlin উচ্চ-ক্রম ফাংশন সমর্থন করে যা অন্য ফাংশনকে প্যারামিটার হিসেবে গ্রহণ করে। Callback Kotlin-এ (T) -> Unit টাইপের প্যারামিটার বা রিটার্ন ভ্যালুর জন্য (T) -> R-এর মাধ্যমে পাস করা হয়। Kotlin করুটিনের suspend ফাংশন callback-কে অনুক্রমিক কোড দিয়ে প্রতিস্থাপন করে, কিন্তু callback Java-সামঞ্জস্যপূর্ণ API এবং Android SDK (View.setOnClickListener, TextWatcher)-এ থেকে যায়। Kotlin ল্যাম্বডা স্বয়ংক্রিয়ভাবে val ভেরিয়েবল ক্যাপচার করে, var ভেরিয়েবলের জন্য পরিবর্তনযোগ্যতা র্যাপার প্রয়োজন।
fun <T, R> processWithCallback(
input: T,
transform: (T) -> R,
onResult: (R) -> Unit
) {
thread {
val result = transform(input)
runOnUiThread { onResult(result) }
}
}
// ল্যাম্বডার সাথে উদাহরণ
processWithCallback(
input = "Hello",
transform = { it.length },
onResult = { length ->
textView.text = "Length: $length"
}
)
Retain cycle হল এমন একটি অবস্থা যেখানে দুটি অবজেক্ট একে অপরের জন্য শক্তিশালী রেফারেন্স ধরে রাখে, যা মেমরি ম্যানেজারকে সেগুলি মুক্ত করতে বাধা দেয়। Swift-এ, retain cycle ঘটে যখন viewController একটি ক্লোজার ক্যাপচার করে এবং ক্লোজার self ক্যাপচার করে। Kotlin/Java-তে, লিক ঘটে যখন Activity একটি ইনার ক্লাস বা ল্যাম্বডা দীর্ঘস্থায়ী ব্যাকগ্রাউন্ড অপারেশনে পাস করে। WWDC সেশন 10216 (2024) অনুসারে, অনুপযুক্ত ক্লোজার ব্যবস্থাপনা iOS অ্যাপ্লিকেশনে মেমরি লিকের তৃতীয় সবচেয়ে সাধারণ কারণ।
Swift অটোমেটিক রেফারেন্স কাউন্টিং (ARC) ব্যবহার করে, যা রেফারেন্স কাউন্টার শূন্য হলে অবজেক্ট মুক্ত করে। ক্লোজারে ক্যাপচার লিস্ট [weak self] বা [unowned self] retain cycle প্রতিরোধ করে। weak self একটি অপশনাল রেফারেন্স তৈরি করে যা অবজেক্ট ডিলোকেট হলে nil হয়ে যায়। unowned self ধরে নেয় যে অবজেক্ট ক্লোজারের চেয়ে বেশি দিন বেঁচে থাকে — এই ধারণা লঙ্ঘন করলে ক্র্যাশ ঘটে। নিরাপদ ডিফল্ট হিসেবে weak self ব্যবহার করার সুপারিশ করা হয়।
class DataController {
var onDataUpdate: ((String) -> Void)?
func setupCallback() {
// Retain cycle!
onDataUpdate = { text in
self.process(text)
}
// [weak self] দিয়ে সংশোধিত
onDataUpdate = { [weak self] text in
guard let self else { return }
self.process(text)
}
}
func process(_ input: String) { }
}
Android-এ, callback লিক ঘটে যখন Activity বা Fragment একটি সিঙ্গেলটন কম্পোনেন্টে (যেমন EventBus বা Service) listener পাস করে। WeakReference গার্বেজ কালেক্টরকে Activity মুক্ত করতে দেয় এমনকি যদি তার দুর্বল রেফারেন্স থাকে। Lifecycle-aware কম্পোনেন্ট (LiveData, Flow) স্বয়ংক্রিয়ভাবে সমস্যার সমাধান করে। Kotlin ল্যাম্বডা যা Activity কনটেক্সট ক্যাপচার করে সেগুলিও লিক ঘটাতে পারে: ল্যাম্বডা নিহিতভাবে this-এর রেফারেন্স সংরক্ষণ করে।
class SafeCallbackManager {
private val listeners = mutableListOf<WeakReference<(String) -> Unit>>()
fun addListener(callback: (String) -> Unit) {
listeners.add(WeakReference(callback))
}
fun notifyAll(data: String) {
val iterator = listeners.iterator()
while (iterator.hasNext()) {
val ref = iterator.next().get()
if (ref != null) ref(data)
else iterator.remove()
}
}
}
// Fragment-এ ব্যবহার
manager.addListener { result ->
// WeakReference Fragment ধরে রাখে না
updateUI(result)
}
Callback Hell (Pyramid of Doom নামেও পরিচিত) এমন একটি অবস্থা যেখানে অনেক নেস্টেড callback কোডের একটি গভীরভাবে নেস্টেড কাঠামো তৈরি করে, যা পড়া এবং ডিবাগ করা কঠিন। প্রতিটি পরবর্তী ধাপে আগেরটি শেষ হওয়ার জন্য অপেক্ষা করতে হয়, যার ফলে 5-10 স্তর পর্যন্ত নেস্টিং হয়। এই সমস্যাটি অনুক্রমিক অ্যাসিনক্রোনাস অপারেশনের জন্য বৈশিষ্ট্যগত: ডেটা লোড করা → পার্স করা → DB-তে সংরক্ষণ করা → UI আপডেট করা।
Swift 5.5 অ্যাসিনক্রোনাস ফাংশন (async/await) চালু করেছে, যা অ্যাসিনক্রোনাস কোড অনুক্রমিকভাবে লেখার অনুমতি দেয়। AsyncSequence এবং AsyncStream callback-ভিত্তিক পুনরাবৃত্তি প্রতিস্থাপন করে। Combine ফ্রেমওয়ার্ক নেস্টিং ছাড়াই অ্যাসিনক্রোনাস স্ট্রিম রচনার জন্য flatMap, merge, combineLatest-এর মতো অপারেটর সরবরাহ করে। তবে, অবজেক্টিভ-C API এবং async সমর্থন ছাড়া তৃতীয় পক্ষের লাইব্রেরির সাথে কাজ করার জন্য callback প্রয়োজনীয় থেকে যায়।
// নেস্টেড callback — Callback Hell
loginUser(credentials) { user in
fetchProfile(user.id) { profile in
downloadAvatar(profile.avatarUrl) { image in
cacheImage(image) { success in
updateUI(user, profile, image)
}
}
}
}
// async/await — সমাধান
func loadUserExperience() async throws {
let user = try await loginUser(credentials)
let profile = try await fetchProfile(user.id)
let image = try await downloadAvatar(profile.avatarUrl)
try await cacheImage(image)
updateUI(user, profile, image)
}
Kotlin করুটিন অনুক্রমিক নির্বাহনের জন্য callback-কে suspend ফাংশন দিয়ে প্রতিস্থাপন করে। Flow map, flatMapConcat, combine-এর মতো অপারেটর সহ কোল্ড স্ট্রিম সরবরাহ করে। CoroutineScope একটি কম্পোনেন্ট ধ্বংস হলে সমস্ত চলমান করুটিন বাতিল করার অনুমতি দেয়। Room, Retrofit এবং অন্যান্য Jetpack লাইব্রেরিতে suspend ফাংশনের জন্য অন্তর্নির্মিত সমর্থন আছে, যা স্ট্যান্ডার্ড অপারেশনে callback-এর প্রয়োজনীয়তা দূর করে।
// অনুক্রমিক callback — Callback Hell
api.login(credentials) { user ->
api.fetchProfile(user.id) { profile ->
api.download(profile.avatarUrl) { bytes ->
file.save(bytes) { result ->
textView.text = result.toString()
}
}
}
}
// করুটিন — সমাধান
suspend fun loadUserData() {
val user = withContext(Dispatchers.IO) { api.login(credentials) }
val profile = withContext(Dispatchers.IO) { api.fetchProfile(user.id) }
val bytes = withContext(Dispatchers.IO) { api.download(profile.avatarUrl) }
withContext(Dispatchers.IO) { file.save(bytes) }
textView.text = "Done"
}
Callback এবং Delegate অ্যাসিনক্রোনাস বিজ্ঞপ্তির দুটি পদ্ধতি, এবং পছন্দটি স্থাপত্য প্রয়োজনীয়তার উপর নির্ভর করে। Callback একটি একক ফলাফল সহ একবারের অপারেশনের জন্য উপযুক্ত। Delegate বিভিন্ন মেথড সিগনেচার সহ একাধিক ইভেন্টের জন্য ডিজাইন করা হয়েছে। Apple একাধিক মেথড সহ জটিল প্রোটোকলের জন্য delegate এবং একক ফলাফল সহ সাধারণ ক্লোজারের জন্য callback সুপারিশ করে। Android-এ, ল্যাম্বডা সমর্থনের কারণে callback বেশিরভাগ ক্ষেত্রে delegate-কে প্রতিস্থাপন করে।
Callback একটি একক ফলাফল সহ অপারেশনের জন্য সর্বোত্তম: নেটওয়ার্ক রিকোয়েস্ট, ফাইল রিডিং, কমপ্লিশন ব্লক সহ অ্যানিমেশন। সুবিধা: সংক্ষিপ্ত সিনট্যাক্স, আলাদা প্রোটোকলের প্রয়োজন নেই, সরাসরি প্রসঙ্গ ক্যাপচার। অসুবিধা: একাধিক ফলাফলে (অগ্রগতি, বিরতি, বাতিল) জটিলতা, একাধিক প্রেরণের অসম্ভাব্যতা (যদি callback একাধিকবার কল করা যায় — publisher ব্যবহার করুন)।
Delegate একাধিক বাধ্যতামূলক এবং ঐচ্ছিক মেথড সহ প্রোটোকলের জন্য উপযুক্ত: UITableViewDelegate, CLLocationManagerDelegate, ব্লুটুথ সংযোগ। সুবিধা: প্রতিটি মেথডের জন্য স্পষ্ট টাইপিং, প্রোটোকলের মাধ্যমে ডকুমেন্টেশন, @objc optional-এর মাধ্যমে ঐচ্ছিক মেথড সমর্থন। অসুবিধা: বয়লারপ্লেট কোড, delegate-এর জন্য দুর্বল রেফারেন্স বাধ্যতামূলক (weak var delegate), প্রসঙ্গ ক্যাপচারে জটিলতা।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
Callback হল উচ্চ-ক্রম ফাংশনের একটি বিশেষ ক্ষেত্র। একটি উচ্চ-ক্রম ফাংশন আরেকটি ফাংশনকে আর্গুমেন্ট হিসেবে গ্রহণ করে বা সেটি ফেরত দেয়। Callback হল একটি ফাংশন যা বিশেষভাবে অপারেশন শেষ হওয়ার পরে অ্যাসিনক্রোনাস নির্বাহনের জন্য পাস করা হয়। সমস্ত callback উচ্চ-ক্রম ফাংশনের মাধ্যমে বাস্তবায়িত হয়, কিন্তু প্রতিটি উচ্চ-ক্রম ফাংশন callback নয়।
প্রচলিত নিয়ম অনুসারে, একটি callback ঠিক একবার কল করা উচিত — হয় success বা failure। একই callback-এর একাধিক কল ডিজাইন ত্রুটি হিসেবে বিবেচিত হয়। একাধিক ইভেন্টের জন্য (অগ্রগতি, ডেটা স্ট্রিম), Observable, Publisher বা Flow ব্যবহার করুন — এগুলি একাধিক মান নির্গমন সমর্থন করে। কিছু API এই নিয়ম লঙ্ঘন করে, যা খুঁজে পাওয়া কঠিন বাগ সৃষ্টি করে।
Trailing closure হল Swift সিনট্যাক্টিক সুগার যা ফাংশন কলের বন্ধনীর পরে ক্লোজার পাস করার অনুমতি দেয়। যদি একটি ফাংশন শেষ আর্গুমেন্ট হিসেবে ক্লোজার নেয়, তবে এটি বন্ধনীর বাইরে রাখা যেতে পারে: fetchData { result in ... }। একাধিক ক্লোজারের জন্য, trailing closure শুধুমাত্র শেষটির ক্ষেত্রে প্রযোজ্য; বাকিগুলি বন্ধনীর ভিতরে নামযুক্ত হয়। এটি callback-ভিত্তিক API-র পঠনযোগ্যতা উন্নত করে।
দীর্ঘজীবী শ্রোতাদের জন্য WeakReference ব্যবহার করুন, onDestroy()-এ Job.cancel()-এর মাধ্যমে করুটিন বাতিল করুন, স্বয়ংক্রিয় বাতিলের জন্য lifecycleScope ব্যবহার করুন। ViewModel + LiveData/Flow স্থাপত্য স্তরে সমস্যার সমাধান করে। স্ট্যাটিক callback-এ Activity কনটেক্সট পাস করা এড়িয়ে চলুন — Application context ব্যবহার করুন। Kotlin ল্যাম্বডা নিহিতভাবে this ক্যাপচার করে, মেমরি প্রোফাইলার দিয়ে পরীক্ষা করুন।
Async/await অনুক্রমিক অ্যাসিনক্রোনাস কোডের জন্য callback প্রতিস্থাপন করে, কিন্তু ইভেন্ট-চালিত স্থাপত্যের জন্য নয়। Callback সিস্টেম API (View.OnClickListener, URLSession delegates), অগ্রগতি callback এবং তৃতীয় পক্ষের লাইব্রেরিতে থেকে যায়। পশ্চাৎ-সামঞ্জস্যের কারণে সম্পূর্ণ প্রতিস্থাপন অসম্ভব। আধুনিক কৌশল হল callback র্যাপার (Swift-এ continuation, Kotlin-এ suspendCancellableCoroutine) সহ async/await ব্যবহার করা।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন