Hello, Closures

Introduction

Closures or completion blocks in Swift is a neat way of passing functions to a function for different use cases. They are generally used for adding code after a function has finished executing. For instance, when you create a function that is trying to find the 6th largest number and want to add additional operations after finding that 6th largest number, you can pass that value to a closure and perform additional operations. If that analogy did not work for you, recall the functions you would use when performing network calles with Alamofire or URLSessions. Both functions have a closure with one ore more parameters that return the response (usually response for the actual response and error for any errors from the database) from the network call.

While they are widely used in network calls with databases because of its asynchonous nature, you can apply this logic for a lot of other things. In this article, I will show you how to create a closure and how to use them!


Constructing a Closure

To create a closure, you simply create a function with a parameter of type Void as followed:


func helloWorld(_ closure: () -> Void) {
    
}
            

Looking at the parameter closure: () -> Void basically says pass any parameters for the closure if necessary, (in this case, none) and specify the function return type (which is none, so it's a Void function). Inside the function, lets make a simple print statement. The closure named closure is essentially a function of its own. To use closure, we simply call if as if its a regular function right below the print statement.


func helloWorld(_ closure: () -> Void) {
    print("Hello, World")
    closure()
}
            

To use the function, we simply call it and Xcode autocomplete should show it as a function with a closure. Inside that closure, lets create a simple print statement.


helloWorld {
    print("Bye, World")
}
            

When you run your code, the following print statement would print in the following order.


Hello, World
Bye, World
            

To explain what is happening: the statement Hello, World is coming from the print statement inside the helloWorld() function while the statement Bye, World is coming from the closure after using the helloWorld() function .

Closures with Parameters

Closures with parameters is just like what the title suggests. We just realized that closures are nothing more than just functions passed as a parameter. Like functions, we can also apply parameters into the mix. Let's see how that would look like.

In the following example, we will create a function that will load some data. The function will also have a completion block which will have a single parameter that will show wether or not the data has loaded successfully.


func loading(_ loadingComplete: (_ success: Bool) -> Bool) {

}
            

Inside the function, we'll create a simple if statement checking if the data has loaded successfully.


func loading(_ loadingComplete: (_ success: Bool) -> Bool) {
    if dataFinishedLoading {
        loadingComplete(true)
    } else {
        loadingComplete(false)
    }
}
            

To explain what is happening, if the data has finished loading, we call the loadingComplete closure and pass in true, otherwise pass in false

So, when we use our loading() function, we will have a completion block with a required parameter.


loading { (success) in
    if success {
        print("YAY!")
    } else {
        print("Aww")
    }
}
            

To explain what is happening, when the loading finishes inside the loading() function, the results are then passed through the completion block (true or false). We can access those results inside the completion block.

Clean Closures

The examples I gave are just simple demonstrations on how they work. In bigger closures, your code can get pretty messy. One way to clean your code is modularize your closures. You can actually create a separate variable for your closures and pass them when it's time to use them; making your code a lot cleaner.

Using the same example above, we want to create something called a typealias and giving it a name. We'll call it LoadingBlock


typealias LoadingBlock = (_ success: Bool) -> Void
            

Instead of having (_ success: Bool) -> Void inside the function parameter, we can pass the LoadingBlock typealias.


func loading(loadingComplete: LoadingBlock) {
    if dataFinishedLoading {
        loadingComplete(true)
    } else {
        loadingComplete(false)
    }
}

Now it's time to use our loading() function. Before we call it, we want to define our closure ahead of time. You can do this as followed.


var loadingBlock: LoadingBlock = { success in 

    if success {
        print("YAY!")
    } else {
        print("Aww")
    }

}
            

After creating your closure of type LoadingBlock, you can pass the loadingBlock variable as a closure for our function loading()


func loading(loadingComplete: loadingBlock)
            

Nice and clean! 😎

Final Note

After going through a few example on how to create them, you can tell that closures are especially useful in framework design; allowing your users to add logic after a function call. We also covered how to reduce clutter by modularizing closures. Note that by doing this, you also run the risk of alienating other people reading your code, so try to somehow document what is happening in that instance.