Callback — nedir, geri çağırma fonksiyonları ve nasıl çalışır

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

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 — asenkron yürütme için argüman olarak iletilen geri çağırma fonksiyonu.
  • Swift, callback için @escaping anahtar sözcüğüyle closure'ları kullanır.
  • Kotlin, callback için lambda ifadeleri ve yüksek dereceli fonksiyonları kullanır.
  • Retain cycle — iOS'ta callback'te self yakalandığında bellek sızıntısı.
  • Callback Hell — iç içe geçmiş callback sorunu, async/await ve coroutine ile çözülür.

Callback Nedir?

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.

Callback Çalışma Prensibi

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.

Senkron ve Asenkron Callback

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.

iOS ve Android'de Callback Nasıl Ç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.

iOS'ta Callback Yaşam Döngüsü

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.

swift
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 Yaşam Döngüsü

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.

kotlin
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) }
})

Swift ve Kotlin'de Callback Sözdizimi

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 vs Result) ortaya çıkar.

Swift'te Callback: Closure'lar

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 ... }.

swift
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'de Callback: Lambda'lar ve Yüksek Dereceli Fonksiyonlar

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.

kotlin
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 cycles ve Callback'te Bellek Sızıntıları

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'te Retain cycles

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.

swift
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 Bellek Sızıntıları

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.

kotlin
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 ve Mücadele Yöntemleri

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'te Çözümler: async/await

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.

swift
// İç 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'de Çözümler: Coroutine'ler ve Flow

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.

kotlin
// 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 vs Delegate: Hangisini Seçmeli?

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 Ne Zaman Seçilmeli

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 Ne Zaman Seçilmeli

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 ve yüksek dereceli fonksiyon arasındaki fark nedir?

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.

Bir callback birden çok kez çağrılabilir mi?

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.

Swift'te trailing closure nedir?

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.

Android'de callback ile bellek sızıntısı nasıl önlenir?

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 callback'leri tamamen değiştirecek mi?

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

  • Callback — işlem tamamlandıktan sonra asenkron yürütme için argüman olarak iletilen geri çağırma fonksiyonu.
  • Swift, callback'leri @escaping, capture list [weak self] ve trailing closure sözdizimi ile closure'lar aracılığıyla uygular.
  • Kotlin, asenkron için lambda'lar, yüksek dereceli fonksiyonlar ve coroutine suspend fonksiyonları kullanır.
  • Retain cycle iOS'ta capture list ile önlenir; Android'de — WeakReference ve lifecycle-aware bileşenlerle.
  • Callback Hell, Swift'te async/await ve Kotlin'de Flow ile coroutine'lerle çözülür.
  • Delegate, birden çok yöntemi olan protokoller için callback'e tercih edilir; tek seferlik işlemler için callback.
  • Basit asenkron işlemler için callback, sıralı zincirler için async/await, birden çok olay için delegate kullanın.

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