Callback — шта је то, функције повратног позива и како раде

Аутор: IT Sectr Објављено: 2026-03-17 Време читања: 11 мин

Callback — је функција која се прослеђује другој функцији као аргумент и извршава се након завршетка асинхроне операције. У мобилном развоју callback се примењује за обраду резултата мрежних захтева, рад са базама података и анимација. Према Apple Documentation (2025), затварања у Swift-у су основни облик callback-а и користе се у URLSession, GCD и Combine. У Android-у callback је реализован кроз интерфејсе, ламбде Kotlin-а и ListenableFuture.

Главно

  • Callback — функција повратног позива која се прослеђује као аргумент за асинхроно извршавање.
  • Swift користи затварања (closures) са кључном речи @escaping за callback.
  • Kotlin примењује ламбда изразе и функције вишег реда за callback.
  • Retain cycle — цурење меморије при хватању self у callback-у на iOS-у.
  • Callback Hell — проблем угњеждених callback-а, решаван кроз async/await и корутине.

Шта је Callback?

Callback (функција повратног позива) — је извршни код који се прослеђује другој функцији и позива се након завршетка одређене радње. У мобилном развоју callback је основни механизам асинхроног програмирања, омогућавајући реакцију на завршетак мрежних захтева, тајмера, анимација и улазно-излазних операција без блокирања главне нити. Swift и Kotlin пружају уграђене синтаксне конструкције за креирање callback-а — затварања и ламбде, респективно.

Принцип рада Callback-а

Функција вишег реда прихвата другу функцију као параметар и позива је након извршавања своје главне логике. Ток управљања се враћа позиваоцу кроз callback, одакле и назив. У iOS-у callback се примењује у UIKit (анимације UIView.animate), Foundation (URLSession.dataTask) и Combine (sink). У Android-у callback се користи у View.OnClickListener, Retrofit Callback и Room DAO. Модерни API-ји све више замењују callback са async/await или корутинама, али разумевање callback-а је неопходно за рад са legacy кодом и нискoнивоским API-јима.

Синхрони и асинхрони Callback

Callback може бити синхрони (позива се одмах унутар функције) и асинхрони (позива се касније из друге нити или реда). Синхрони callback-и се користе за сортирање (компаратори) и обилазак колекција. Асинхрони callback-и се примењују за мрежне захтеве, читање датотека и рад са сензорима. Разлика је критична за разумевање threading-а: синхрони callback се извршава у истој нити, асинхрони — у нити одређеној диспечером (DispatchQueue у iOS-у, Dispatchers у Kotlin-у).

Како ради Callback у iOS и Android?

Механизам callback-а на обе платформе заснива се на истом принципу: функција се прослеђује као објекат прве класе и чува до тренутка извршења. Међутим, имплементације се разликују због различитих језичких парадигми. У iOS-у callback је затварање (closure) које хвата променљиве из окружујућег контекста. У Android-у callback је најчешће реализован кроз анонимне класе или ламбда изразе Kotlin-а, компиловане у FunctionalInterface.

Животни циклус Callback-а у iOS-у

При позиву асинхроне функције, затварање се чува на heap-у заједно са захваћеним променљивим. Када се операција заврши, систем GCD или OperationQueue поставља callback у одговарајући ред (main queue или background queue). Након извршења, callback се уклања из меморије при одсуству јаких референци. Capture list ([weak self]) спречава задржавање објекта након његове деалокације. Без capture list-а настаје retain cycle, у коме објекат и callback упућују један на другог.

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

Животни циклус Callback-а у Android-у

У Android-у callback се прослеђује кроз интерфејс или ламбду. При извршавању асинхроне операције кроз ExecutorService или корутину, callback се чува у меморији до завршетка позадинског рада. Kotlin ламбде се компилују у анонимне класе које хватају спољашње променљиве. Одсуство слабих референци у JVM-у захтева ручно управљање: ресетовање callback-а у onDestroy() или отказивање корутина кроз Job.cancel(). ViewModel и LiveData решавају овај проблем на нивоу архитектуралне компоненте.

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

// Коришћење са ламбдом
repository.loadData(object : Callback<List<User>> {
    override fun onSuccess(data: List<User>) { showUsers(data) }
    override fun onError(error: Throwable) { showError(error.message) }
})

Синтакса Callback-а у Swift и Kotlin

Синтакса callback-а је одређена језичким могућностима рада са функцијама као објектима прве класе. У Swift-у затварања имају кратку синтаксу са аутоматским именима аргумената ($0, $1). У Kotlin-у ламбде такође подржавају it за један аргумент. Разлике се манифестују у обради хватања променљивих (capture list у Swift-у наспрам променљивих референци у Kotlin-у) и типизацији (Result<Success, Failure> vs Result<T>).

Callback у Swift-у: затварања (closures)

Swift затварање је самодостатан блок кода који може бити прослеђен и коришћен у другој функцији. Затварања могу бити глобална (именована), угњеждена и expression-level. @escaping означава затварање које ће бити извршено након повратка из функције — ово је обавезан захтев за асинхроне callback-е. Без @escaping, затварање може бити извршено само унутар тела функције. Trailing closure syntax омогућава прослеђивање затварања након округлих заграда: 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)
    }
}

Callback у Kotlin-у: ламбде и функције вишег реда

Kotlin подржава функције вишег реда које прихватају друге функције као параметре. Callback у Kotlin-у се прослеђује кроз параметар типа (T) -> Unit или (T) -> R за повратну вредност. suspend функције Kotlin корутина замењују callback секвенцијалним кодом, али callback остаје у Java-компатибилним API-јима и Android SDK-у (View.setOnClickListener, TextWatcher). Kotlin ламбде аутоматски хватају val променљиве, var променљиве захтевају мутабилна омотаче.

kotlin
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 cycles и цурење меморије у Callback-у

Retain cycle — ситуација када два објекта држе јаке референце један на другог, спречавајући њихово ослобађање од стране сакупљача меморије. У Swift-у retain cycle настаје када viewController ухвати затварање, а затварање ухвати self. У Kotlin/Java-и цурење настаје када Activity прослеђује унутрашњу класу или ламбду дуготрајној позадинској операцији. Према WWDC Session 10216 (2024), неправилно управљање затварањима је трећи најчешћи узрок цурења меморије у iOS апликацијама.

Retain cycles у Swift-у

Swift користи Automatic Reference Counting (ARC), који ослобађа објекат при ресетовању бројача референци. Capture list [weak self] или [unowned self] у затварању спречава retain cycle. weak self креира опциону референцу која постаје nil при деалокацији објекта. unowned self претпоставља да објекат живи дуже од затварања — кршење ове претпоставке узрокује crash. Препоручује се коришћење weak self као сигурне подразумеване опције.

swift
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-у

У Android-у цурење callback-а настаје када Activity или Fragment прослеђује слушаоца синглтон компоненти (нпр. EventBus или Service). WeakReference омогућава сакупљачу смећа да ослободи Activity, чак и ако постоји слаба референца на њу. Lifecycle-aware компоненте (LiveData, Flow) решавају проблем аутоматски. Kotlin ламбде које хватају Activity контекст такође могу изазвати цурење: ламбда имплицитно чува референцу на this.

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-у
manager.addListener { result ->
    // WeakReference не задржава Fragment
    updateUI(result)
}

Callback Hell и начини борбе

Callback Hell (такође познат као Пирамида пропасти) — ситуација у којој мноштво угњеждених callback-а ствара дубоко угњеждену структуру кода, тешку за читање и отклањање грешака. Сваки следећи корак захтева чекање на завршетак претходног, што доводи до угњежђења од 5-10 нивоа. Овај проблем је карактеристичан за секвенцијалне асинхроне операције: учитавање података → парсирање → чување у бази → ажурирање UI-ја.

Решења у Swift-у: async/await

Swift 5.5 је увео асинхроне функције (async/await) које омогућавају писање асинхроног кода секвенцијално. AsyncSequence и AsyncStream замењују итерације засноване на callback-у. Combine фрејмворк пружа операторе flatMap, merge, combineLatest за композицију асинхроних токова без угњежђења. Међутим, callback остаје неопходан за рад са Objective-C API-јима и библиотекама трећих страна без async подршке.

swift
// Угњеждени 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-у: корутине и Flow

Kotlin корутине замењују callback suspend функцијама са секвенцијалним извршењем. Flow пружа cold токове са операторима map, flatMapConcat, combine. CoroutineScope омогућава отказивање свих покренутих корутина при уништењу компоненте. Room, Retrofit и друге Jetpack библиотеке имају уграђену подршку за suspend функције, елиминишући потребу за callback-ом за стандардне операције.

kotlin
// Секвенцијални 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 vs Delegate: шта одабрати?

Callback и Delegate — два приступа асинхроном обавештавању, а избор између њих зависи од архитектуралних захтева. Callback је погодан за једнократне операције са једним резултатом. Delegate је намењен за вишеструке догађаје са различитим потписима метода. Apple препоручује delegate за сложене протоколе са више метода, callback — за једноставна затварања са једним резултатом. У Android-у callback замењује delegate у већини случајева због подршке за ламбде.

Када одабрати Callback

Callback је оптималан за операције са једним резултатом: мрежни захтев, читање датотеке, анимација са completion блоком. Предности: компактна синтакса, без посебног протокола, директно хватање контекста. Недостаци: сложеност при вишеструким резултатима (напредак, пауза, отказивање), немогућност вишеструког слања (ако callback може бити позван више пута — користите publisher).

Када одабрати Delegate

Delegate је погодан за протоколе са више обавезних и опционих метода: UITableViewDelegate, CLLocationManagerDelegate, Bluetooth везе. Предности: јасна типизација сваке методе, документација кроз протокол, подршка за опционе методе кроз @objc optional. Недостаци: boilerplate код, обавезна слаба референца на delegate (weak var delegate), сложеност при хватању контекста.

Често постављана питања

Која је разлика између callback-а и функције вишег реда?

Callback је посебан случај функције вишег реда. Функција вишег реда прихвата другу функцију као аргумент или је враћа. Callback је функција прослеђена специјално за асинхроно извршавање након завршетка операције. Сви callback-и се реализују кроз функције вишег реда, али није свака функција вишег реда callback.

Може ли callback бити позван више пута?

По конвенцији, callback треба да буде позван тачно једном — или success или failure. Вишеструко позивање истог callback-а сматра се грешком у дизајну. За вишеструке догађаје (напредак, ток података) користите Observable, Publisher или Flow — они подржавају вишеструку емисију вредности. Неки API-ји крше ово правило, што доводи до тешко ухватљивих грешака.

Шта је trailing closure у Swift-у?

Trailing closure — синтаксни шећер Swift-а који омогућава прослеђивање затварања након округлих заграда позива функције. Ако функција прихвата затварање као последњи аргумент, може се поставити ван заграда: fetchData { result in ... }. За више затварања, trailing closure се примењује само на последње, остала се именују унутар заграда. Ово побољшава читљивост callback-базираних API-ја.

Како избећи цурење меморије при callback-у у Android-у?

Користите WeakReference за дуготрајне слушаоце, откажите корутине кроз Job.cancel() у onDestroy(), примените lifecycleScope за аутоматско отказивање. ViewModel + LiveData/Flow решавају проблем на архитектуралном нивоу. Избегавајте прослеђивање Activity контекста у статичке callback-е — користите Application контекст. Kotlin ламбде имплицитно хватају this, проверавајте кроз memory profiler.

Да ли ће async/await потпуно заменити callback?

Async/await замењује callback за секвенцијални асинхрони код, али не и за event-driven архитектуру. Callback остаје у системским API-јима (View.OnClickListener, URLSession делегати), повратним позивима с напретком и библиотекама трећих страна. Потпуна замена је немогућа због уназадне компатибилности. Модерна стратегија је коришћење async/await са callback омотачима (continuation у Swift-у, suspendCancellableCoroutine у Kotlin-у).

Закључак

  • Callback — функција повратног позива прослеђена као аргумент за асинхроно извршавање након завршетка операције.
  • Swift реализује callback кроз затварања са @escaping, capture list [weak self] и trailing closure синтаксом.
  • Kotlin користи ламбде, функције вишег реда и suspend функције корутина за асинхроност.
  • Retain cycle у iOS-у се спречава capture list-ом; у Android-у — WeakReference и Lifecycle-aware компонентама.
  • Callback Hell се решава кроз async/await у Swift-у и корутине са Flow-ом у Kotlin-у.
  • Delegate је пожељнији од callback-а за протоколе са више метода; callback — за једнократне операције.
  • Користите callback за једноставне асинхроне операције, async/await за секвенцијалне ланце, delegate за вишеструке догађаје.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође