Callback, başka bir fonksiyona argüman olarak iletilen ve asenkron bir işlem tamamlandıktan sonra çalıştırılan bir fonksiyondur. Mobil geliştirmede callback, ağ isteklerinin sonuçlarını işlemek, veritabanı işlemleri ve animasyonlar için kullanılır. Apple Dokümantasyonu'na (2025) göre, Swift'teki closure'lar callback'in ana biçimidir ve URLSession, GCD ve Combine'da kullanılır. Android'de callback, arayüzler, Kotlin lambda'ları ve ListenableFuture aracılığıyla uygulanır.
Önemli Noktalar
Callback (geri çağırma fonksiyonu), başka bir fonksiyona iletilen ve belirli bir eylem tamamlandıktan sonra çağrılan yürütülebilir koddur. Mobil geliştirmede callback, ana iş parçacığını bloke etmeden ağ isteklerinin, zamanlayıcıların, animasyonların ve G/Ç işlemlerinin tamamlanmasına tepki vermeyi sağlayan asenkron programlamanın temel bir mekanizmasıdır. Swift ve Kotlin, callback oluşturmak için yerleşik sözdizimsel yapılar sağlar — sırasıyla closure'lar ve lambda'lar.
Yüksek dereceli bir fonksiyon, başka bir fonksiyonu parametre olarak kabul eder ve ana mantığını çalıştırdıktan sonra onu çağırır. Kontrol akışı callback aracılığıyla çağırana geri döndürülür, adı da buradan gelir. iOS'ta callback, UIKit'te (UIView.animate animasyonları), Foundation'da (URLSession.dataTask) ve Combine'da (sink) kullanılır. Android'de callback, View.OnClickListener, Retrofit Callback ve Room DAO'da kullanılır. Modern API'ler giderek callback'leri async/await veya coroutine'lerle değiştirmektedir, ancak eski kod ve düşük seviyeli API'lerle çalışmak için callback'i anlamak gereklidir.
Callback senkron (fonksiyon içinde hemen çağrılan) ve asenkron (daha sonra başka bir iş parçacığından veya kuyruktan çağrılan) olabilir. Senkron callback'ler sıralama (karşılaştırıcılar) ve koleksiyon geçişi için kullanılır. Asenkron callback'ler ağ istekleri, dosya okuma ve sensörlerle çalışma için kullanılır. Fark, iş parçacığını anlamak için kritiktir: senkron callback aynı iş parçacığında çalışır, asenkron callback ise dağıtıcı (iOS'ta DispatchQueue, Kotlin'de Dispatchers) tarafından belirlenen bir iş parçacığında çalışır.
Her iki platformdaki callback mekanizması aynı prensibe dayanır: bir fonksiyon birinci sınıf bir nesne olarak iletilir ve yürütme anına kadar saklanır. Ancak, farklı dil paradigmaları nedeniyle uygulamalar farklılık gösterir. iOS'ta callback, çevreleyen bağlamdan değişkenleri yakalayan bir closure'dır. Android'de callback çoğunlukla anonim sınıflar veya Kotlin lambda ifadeleri aracılığıyla uygulanır ve FunctionalInterface'ye derlenir.
Asenkron bir fonksiyon çağrıldığında, closure yakalanan değişkenlerle birlikte heap'te saklanır. İşlem tamamlandığında, GCD veya OperationQueue sistemi callback'i uygun kuyruğa (ana kuyruk veya arka plan kuyruğu) yerleştirir. Yürütmeden sonra, güçlü referans kalmadığında callback bellekten kaldırılır. Capture list ([weak self]), nesnenin serbest bırakıldıktan sonra tutulmasını önler. Capture list olmadan, nesne ve callback'in birbirini referans aldığı retain cycle oluşur.
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] ile kullanım
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'de callback bir arayüz veya lambda aracılığıyla iletilir. ExecutorService veya bir coroutine aracılığıyla asenkron bir işlem yürütülürken, arka plan işi tamamlanana kadar callback bellekte saklanır. Kotlin lambda'ları, dış değişkenleri yakalayan anonim sınıflara derlenir. JVM'de zayıf referansların olmaması manuel yönetim gerektirir: onDestroy()'da callback'i geçersiz kılma veya Job.cancel() ile coroutine'leri iptal etme. ViewModel ve LiveData bu sorunu mimari bileşen düzeyinde çözer.
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) }
}
}
}
}
// Lambda ile kullanım
repository.loadData(object : Callback<List<User>> {
override fun onSuccess(data: List<User>) { showUsers(data) }
override fun onError(error: Throwable) { showError(error.message) }
})
Callback sözdizimi, dilin fonksiyonlarla birinci sınıf nesneler olarak çalışma yeteneği tarafından belirlenir. Swift'te closure'lar otomatik argüman adlarıyla ($0, $1) kısa bir sözdizimine sahiptir. Kotlin'de lambda'lar tek bir argüman için it'i destekler. Farklılıklar, değişken yakalama işleme (Swift'te capture list vs Kotlin'de değiştirilebilir referanslar) ve tip belirlemede (Result
Swift closure'ı, başka bir fonksiyona iletilip kullanılabilen bağımsız bir kod bloğudur. Closure'lar global (adlandırılmış), iç içe ve ifade düzeyinde olabilir. @escaping, fonksiyon döndükten sonra yürütülecek closure'ı işaretler — bu asenkron callback'ler için zorunlu bir gerekliliktir. @escaping olmadan, closure yalnızca fonksiyon gövdesi içinde yürütülebilir. Trailing closure sözdizimi, closure'ı parantezlerden sonra geçirmeye izin verir: 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, diğer fonksiyonları parametre olarak kabul eden yüksek dereceli fonksiyonları destekler. Callback, Kotlin'de (T) -> Unit türünde bir parametre veya dönüş değerleri için (T) -> R aracılığıyla iletilir. Kotlin coroutine'lerinin suspend fonksiyonları, callback'leri sıralı kodla değiştirir, ancak callback'ler Java uyumlu API'lerde ve Android SDK'da (View.setOnClickListener, TextWatcher) kalır. Kotlin lambda'ları otomatik olarak val değişkenlerini yakalar, var değişkenleri değiştirilebilirlik sarmalayıcıları gerektirir.
fun <T, R> processWithCallback(
input: T,
transform: (T) -> R,
onResult: (R) -> Unit
) {
thread {
val result = transform(input)
runOnUiThread { onResult(result) }
}
}
// Lambda ile örnek
processWithCallback(
input = "Hello",
transform = { it.length },
onResult = { length ->
textView.text = "Length: $length"
}
)
Retain cycle, iki nesnenin birbirine güçlü referanslar tutması ve bellek yöneticisinin bunları serbest bırakmasını engellemesi durumudur. Swift'te, bir viewController bir closure'ı yakaladığında ve closure self'i yakaladığında retain cycle oluşur. Kotlin/Java'da, bir Activity uzun süreli bir arka plan işlemine bir iç sınıf veya lambda ilettiğinde sızıntı oluşur. WWDC Oturumu 10216'ya (2024) göre, uygunsuz closure yönetimi iOS uygulamalarında bellek sızıntılarının üçüncü en yaygın nedenidir.
Swift, referans sayacı sıfıra ulaştığında bir nesneyi serbest bırakan Otomatik Referans Sayımı (ARC) kullanır. Bir closure'daki capture list [weak self] veya [unowned self], retain cycle'ları önler. weak self, nesne serbest bırakıldığında nil olan isteğe bağlı bir referans oluşturur. unowned self, nesnenin closure'dan daha uzun yaşadığını varsayar — bu varsayımın ihlali çökmeye neden olur. Güvenli varsayılan olarak weak self kullanılması önerilir.
class DataController {
var onDataUpdate: ((String) -> Void)?
func setupCallback() {
// Retain cycle!
onDataUpdate = { text in
self.process(text)
}
// [weak self] ile düzeltildi
onDataUpdate = { [weak self] text in
guard let self else { return }
self.process(text)
}
}
func process(_ input: String) { }
}
Android'de, bir Activity veya Fragment bir singleton bileşene (örneğin, EventBus veya Service) bir dinleyici ilettiğinde callback bellek sızıntısı oluşur. WeakReference, zayıf bir referans olsa bile çöp toplayıcının Activity'yi serbest bırakmasına izin verir. Lifecycle-aware bileşenler (LiveData, Flow) sorunu otomatik olarak çözer. Activity bağlamını yakalayan Kotlin lambda'ları da sızıntılara neden olabilir: lambda örtük olarak this'e bir referans saklar.
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'te kullanım
manager.addListener { result ->
// WeakReference Fragment'i tutmaz
updateUI(result)
}
Callback Hell (ayrıca Ölüm Piramidi olarak da bilinir), çok sayıda iç içe callback'in okunması ve hata ayıklaması zor, derinlemesine iç içe geçmiş bir kod yapısı oluşturması durumudur. Her sonraki adım bir öncekinin tamamlanmasını beklemeyi gerektirir ve 5-10 seviye iç içe geçmeye yol açar. Bu sorun, sıralı asenkron işlemler için karakteristiktir: veri yükleme → ayrıştırma → DB'ye kaydetme → UI güncelleme.
Swift 5.5, asenkron kodun sıralı olarak yazılmasına izin veren asenkron fonksiyonları (async/await) tanıttı. AsyncSequence ve AsyncStream, callback tabanlı yinelemelerin yerini alır. Combine framework'ü, iç içe geçme olmadan asenkron akışların oluşturulması için flatMap, merge, combineLatest gibi operatörler sağlar. Ancak, Objective-C API'leri ve async desteği olmayan üçüncü taraf kitaplıklarla çalışmak için callback'ler gerekli olmaya devam eder.
// İç içe callback'ler — 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 — çözüm
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 coroutine'leri, sıralı yürütme için callback'leri suspend fonksiyonlarla değiştirir. Flow, map, flatMapConcat, combine gibi operatörlerle soğuk akışlar sağlar. CoroutineScope, bir bileşen yok edildiğinde çalışan tüm coroutine'leri iptal etmeye izin verir. Room, Retrofit ve diğer Jetpack kitaplıkları, suspend fonksiyonları için yerleşik desteğe sahiptir ve standart işlemlerde callback ihtiyacını ortadan kaldırır.
// Sıralı callback'ler — Callback Hell
api.login(credentials) { user ->
api.fetchProfile(user.id) { profile ->
api.download(profile.avatarUrl) { bytes ->
file.save(bytes) { result ->
textView.text = result.toString()
}
}
}
}
// Coroutine'ler — çözüm
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 ve Delegate, asenkron bildirime yönelik iki yaklaşımdır ve seçim mimari gereksinimlere bağlıdır. Callback, tek bir sonuçla tek seferlik işlemler için uygundur. Delegate, farklı yöntem imzalarına sahip birden çok olay için tasarlanmıştır. Apple, birden çok yöntemi olan karmaşık protokoller için delegate'i, tek bir sonucu olan basit closure'lar için callback'i önerir. Android'de lambda desteği nedeniyle çoğu durumda callback, delegate'in yerini alır.
Callback, tek bir sonucu olan işlemler için idealdir: ağ isteği, dosya okuma, tamamlama bloğuyla animasyon. Avantajları: kompakt sözdizimi, ayrı bir protokol gerekmemesi, doğrudan bağlam yakalama. Dezavantajları: birden çok sonuçta (ilerleme, duraklatma, iptal) karmaşıklık, birden çok kez gönderme imkansızlığı (bir callback birden fazla çağrılabiliyorsa — publisher kullanın).
Delegate, birden çok zorunlu ve isteğe bağlı yöntemi olan protokoller için uygundur: UITableViewDelegate, CLLocationManagerDelegate, Bluetooth bağlantıları. Avantajları: her yöntemin net tip belirlemesi, protokol aracılığıyla dokümantasyon, @objc optional ile isteğe bağlı yöntem desteği. Dezavantajları: kalıp kodu (boilerplate), delegate'e zayıf referans zorunluluğu (weak var delegate), bağlam yakalamada karmaşıklık.
Sıkça Sorulan Sorular
Callback, yüksek dereceli fonksiyonun özel bir durumudur. Yüksek dereceli bir fonksiyon, başka bir fonksiyonu argüman olarak kabul eder veya onu döndürür. Callback, bir işlem tamamlandıktan sonra asenkron yürütme için özel olarak iletilen bir fonksiyondur. Tüm callback'ler yüksek dereceli fonksiyonlar aracılığıyla uygulanır, ancak her yüksek dereceli fonksiyon bir callback değildir.
Kural gereği, bir callback tam olarak bir kez çağrılmalıdır — ya success ya da failure. Aynı callback'in birden çok kez çağrılması tasarım hatası olarak kabul edilir. Birden çok olay için (ilerleme, veri akışı) Observable, Publisher veya Flow kullanın — bunlar birden çok değer yayılımını destekler. Bazı API'ler bu kuralı ihlal eder ve bulunması zor hatalara yol açar.
Trailing closure, closure'ın fonksiyon çağrısının parantezlerinden sonra iletilmesine izin veren Swift sözdizimsel şekeridir. Bir fonksiyon son argüman olarak bir closure kabul ediyorsa, parantezlerin dışına yerleştirilebilir: fetchData { result in ... }. Birden çok closure için, trailing closure yalnızca sonuncuya uygulanır; diğerleri parantez içinde adlandırılır. Bu, callback tabanlı API'lerin okunabilirliğini artırır.
Uzun ömürlü dinleyiciler için WeakReference kullanın, onDestroy()'da Job.cancel() ile coroutine'leri iptal edin, otomatik iptal için lifecycleScope kullanın. ViewModel + LiveData/Flow sorunu mimari düzeyde çözer. Statik callback'lere Activity bağlamı iletmekten kaçının — Application context kullanın. Kotlin lambda'ları örtük olarak this'i yakalar, bellek profili oluşturucuyla kontrol edin.
Async/await, sıralı asenkron kod için callback'leri değiştirir, ancak olay odaklı mimari için değiştirmez. Callback'ler sistem API'lerinde (View.OnClickListener, URLSession delegeleri), ilerleme callback'lerinde ve üçüncü taraf kitaplıklarda kalır. Geriye dönük uyumluluk nedeniyle tamamen değiştirme mümkün değildir. Modern strateji, callback sarmalayıcıları (Swift'te continuation, Kotlin'de suspendCancellableCoroutine) ile async/await kullanmaktır.
Özet
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.
Ayrıca okuyun