Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing and storing closures/callbacks in Swift

I would like to do the following in swift code:

I have to call my api in order to update several items. So I call the api for each item asynchronously. Each api call executes a callback function when it's done. These callbacks decrease a counter, so that when the counter reaches 0 I know that all my api calls are completed. When the counter reaches 0 I would like to call a final callback function (once, when all calls are complete), in order to update my UI and so forth. This final callback is passes to my service in the beginning and stored in a class property for later execution.

Executable Playground source:

// Playground - noun: a place where people can play

class MyService
{
    let api = MyApi()

    var storedFinalCallback: () -> Void = { arg in }
    var queue: Int                      = 0

    func update(items: [String], finalCallback: () -> Void )
    {
        // Count the necessary API calls
        queue               = items.count
        // Store callback for later execution
        storedFinalCallback = finalCallback

        for item in items {
            // Call api per item and pass queueCounter as async callback
            api.updateCall(item, callback: self.callback())
        }
    }

    func callback()
    {
        queue--
        // Execute final callback when queue is empty
        if queue == 0 {
            println("Executing final callback")
            storedFinalCallback()
        }
    }

}

class MyApi
{
    func updateCall(item: String, callback: ())
    {
        println("Updating \(item)")
    }
}

let myItems: [String]     = ["Item1", "Item2", "Item3"]
let myInstance: MyService = MyService()

myInstance.update(myItems, finalCallback: {() -> Void in
    println("Done")
})

The problem is that with this code the final callback is called in the wrong order. As the console output from the playground shows.

Apparently the callback function is already executed and not properly passed. However, this was the only way I was able to do it, without compiler errors.

Any help would be really appreciated - I have been stuck on this for two days now.

like image 632
Jan Avatar asked Apr 02 '15 20:04

Jan


People also ask

How do you use closure callback in Swift?

Closures in Swift typealias completion = (Int) -> Void? A closure is simply a function with no name; you can pass it to a variable or pass it around like any other value. The idea is to make the same thing that we made with delegation using closure callbacks.

Is a closure the same as a callback?

a callback is executable code that is passed as an argument to other code. a closure is a function that is evaluated in an environment containing one or more bound variables. When called, the function can access these variables.

How many types of closures are there in Swift?

There are two kinds of closures: An escaping closure is a closure that's called after the function it was passed to returns. In other words, it outlives the function it was passed to. A non-escaping closure is a closure that's called within the function it was passed into, i.e. before it returns.

How do I call closure in Swift?

If I want to run my closure, I call it like a function myClosure() . This will cause the code to print the current value of counter . By writing [counter] in we create a capture list that takes a snapshot of the current value of counter which will cause us to ignore any changes that are made to counter .


1 Answers

I finally found the working code:

// Playground - noun: a place where people can play

class MyService
{
    let api = MyApi()

    var storedFinalCallback: () -> Void = { arg in }
    var queue: Int                      = 0

    func update(items: [String], finalCallback: () -> Void )
    {
        // Count the necessary API calls
        queue               = items.count
        // Store callback for later execution
        storedFinalCallback = finalCallback

        for item in items {
            // Call api per item and pass queueCounter as async callback
            api.updateCall(item, callback: self.callback)
        }
    }

    func callback()
    {
        queue--
        // Execute final callback when queue is empty
        if queue == 0 {
            println("Executing final callback")
            storedFinalCallback()
        }
    }

}

class MyApi
{
    func updateCall(item: String, callback: () -> Void)
    {
        println("Updating \(item)")
        callback()
    }
}

let myItems: [String]     = ["Item1", "Item2", "Item3"]
let myInstance: MyService = MyService()

myInstance.update(myItems, finalCallback: {() -> Void in
    println("Done")
})
like image 186
Jan Avatar answered Nov 09 '22 07:11

Jan