Make App Pie

Training for Developers and Artists

Understand and Use Closures in Swift

Have you ever seen some factory method in UIKit and see this strange parameter called completion or completionHandler? Usually you put nil there and leave it alone. These strange things like (Double,Double) -> Bool things are closures. Closures are functions you use as types, often in parameters of other functions. In this lesson we’ll explore closures and get under the hood to explain what they are and what they are really doing.

Set Up a Playground

Set up a playground in Xcode, the iPad or on the web at the IBM Swift Sandbox. IF using the IBM Swift Sandbox, make sure you have Foundation imported.

import Foundation

The Basics of Closures

Suppose I want to calculate the volume of a deep dish pizza. Add these functions to the playground:

 //area of a round pizza
    func roundArea(radius:Double) -> Double{
        return M_PI * radius * radius
    }
    //volume of a round pizza
    func roundVolume(height:Double, radius:Double ) -> Double{
        return roundArea(radius: radius) * height
    }

Add a variable to compute the volume of a pizza of radius 6 and height 2, then print it

var volume = roundVolume(height:2,radius:6)
print( String(format:"Round volume %4.2f",volume))

Run this and get the volume from the area on the console. Which is fine if I had only round pizzas. What if I had rectangle, oval or triangle pizzas? I’d have to write two new functions each: one for the area and one for the volume. However in each case I’m only changing the area calculation. What if I could pass just the calculation to the volume function? That’s what a closure is: a way of writing something like a function but assigning it as a type. Add the function to the class.

func volume(
    height:Double, 
    dim1:Double,
    dim2:Double, 
    area:(Double,Double) -> Double) -> Double
    {
        return area(dim1,dim2) * height
    }

Look at the parameter area. I’ve declared it (Double,Double) -> Double. That looks a lot like a function that returns Double, and two Double parameters. That’s what a closure does as a type. A closure gives a skeletal structure like a function you can pass as a parameter. Below the volume function add the following line of code.

let rectangleArea = { (length:Double,width:Double) -> Double in
    return length * width
}

When I declare the contents of the identifier, I flesh out something like a function inside of curly braces. Instead of func before the parameters, I put in after the return type. There are parameters (length:Double,width:Double). I have the return type as a Double. For closures, there must always be a return value. If you don’t have one mark it Void or ().

Since I declared the type in the closure declaration, I often can remove it from the declaration

        
let  rightTriangleArea = {(length,width) -> Double in
    return length * width / 2.0
}  
let ovalArea = {(length,width)->Double in
    return length/2.0 * width/2.0 * M_PI
}

It doesn’t work in all cases though. This give an error if you do not specify the type

let roundSliceArea = { (radius:Double, slices:Double) ->Double in
    return M_PI * radius * radius / slices
}

I can use the same volume function and change how I compute the area.

print (volume(height: 2,dim1: 10, dim2: 12, area:rectangleArea))
print (volume(height: 2,dim1: 10,dim2: 12, area:roundSliceArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:rightTriangleArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:ovalArea))

I do have a problem with a circle. I could define it like this in my storyboard:

 let circleArea = {(radius)->Double in
            return radius * radius * M_PI
 }
print (self.volume(height: 2,dim1: 10, dim2: 12, area:circleArea))        

This doesn’t work for the circle, because the number of parameters are different. You’d get this error message
Cannot convert value of type '(Double) -> Double' to expected argument type '(Double, Double) -> Double'

You have choices: Use the oval with the same length and width or a placemarker.  I tend to choose the place marker. Change circleArea to add the extra parameter. Fix the above code to this:

let circleArea = {(radius:Double, placeMarker:Double) -> Double in
    return M_PI * radius * radius 
}
print (volume(height: 2,dim1: 10, dim2: 12, area:circleArea))

Literal Closure in Parameters

You don’t have to assign the closure to a constant. You can write it explictly in the function. For a rectangle area, you can write this in the playground.

 
volume = volume(
    height: 2,
    dim1: 10,
    dim2: 12,
    area: {(width,length)->Double in
        return width * length
    }
)
        print(volume)

You’ll find this a very common way of specifying the closure. When the closure is the trailing parameter, you can place it after the function like this:

volume =  volume(
    height: 2,
    dim1: 10,
    dim2: 12) 
    {(radius,placemarker)->Double in
        return M_PI * radius * radius
    }
print(volume)

Closures as Completion Handlers

One very common use of closures is completion handlers. Make a new function like this:

func volume(
    height:Double,
    dim1:Double,
    dim2:Double,
    completionHandler:(Double)->()
){
    let result = dim1 * dim2 * height
    completionHandler(result) 
}

Neither the completionHandler closure nor the volume function returns anything. Instead the result is the parameter of the completion handler.
Why would anyone do this? The reason is asynchronous processing. There are methods which Apple or the developer don’t want to run in the main thread. If you are querying a database, or opening a file any results appear in a closure. The database query might take some time, and you don’t want the rest of your application to freeze while you wait for the result. Similarly, while loading a file you don’t want to freeze the system waiting to learn if the load was successful.
Instead of return in these functions, the method runs on another thread at its own pace and lets the main thread go on its merry way. When done, the method calls something like completionHandler, handler, or completion with the results it returns as the completion parameter.

The developer uses the result their implementation of the completion handler. This is why most often closures happen completely within a function call. Usually they are specific to the code around it. For example, the volume function with the completionHandler would code like this to save the result to the volume property and output the result.

volume(height: 2,dim1: 10,dim2: 12)
{(result)->() in
    print(result)
    volume = result  //in a class use self
    resultLabel.text = String(format:"Completion %5.2f")
}

Completion handlers often pass off the value to a property in a class, like the code above assigns the value of  result to volume. If you run the code above within a class, be sure to include the class identifier or self. volume = result should be self.volume = result. The identifiers within a closure have no scope to the class. They must be explicitly stated.

A Real Example of a Completion Handler

As a general rule, you’ll find completion handlers when you don’t know when you will complete a task. One good example of this is presenting alert views.

Set up a new single view project named ClosureDemo, with Swift as the language. Go to the storyboard and drag a label and button.

Set the font on both to Title 1.  Select the button. Title the button Alert. Click the alignment iconalignment icon in the auto layout menu bar. Check Horizontally in Container and Vertically in container.  Change Update from to Items of new constraints like this:

center alignment

Click Add 2 constraints.  Select the Label. Title it Results.  Click the pin icon pinMenuButton in the auto layout toolbar.  In the dialog box that appears type 10 then tab, 10 then tab and 10 then tab. Click down to the bottom and Update frames with Items of New Constraints.

2016-09-02_07-07-01

Click Add 3 constraints.

Open the assistant editor and control-drag the label to the code. Name the outlet resultsLabel. Control-Drag from the button to the code. Change to an action. Name the action presentAlert.

Close the Assistant editor.  Open the Viewcontroller.swift code.

In the presentAlert action, add the following code:

 @IBAction func presentAlert(_ sender: UIButton) {
     volume = 240.0
 //example of a real completion handler.
     let alert = UIAlertController(
         title: "Alert",
         message: "Volume is \(volume)",
         preferredStyle: .alert)
   

Alert actions only activate after the alert shows and the action’s button gets tapped by the user. The UIAlertAction initializer uses handlers to describe the action when pressed. Add this to the compute action:

   
let clearAction = UIAlertAction(
    title: "Clear", style: .destructive,
    handler: {(action)->() in
        self.volume = 0
        self.resultLabel.text = "Cleared"
    }
)
 

In clearAction, the user sets the volume property to 0 and reflects that in the Label.  Again, the closure is independent of the scope of its class. You must specify self in order to use the ViewController classes’ properties and methods.

Add two more examples of actions:

       
let doubleAction = UIAlertAction(
    title: "Double",
    style: .default,
    handler: {(action)->() in
        self.volume *= 2
        self.resultLabel.text = "\(self.volume)"
    })
let cancelAction = UIAlertAction(
    title: "Cancel",
    style: .cancel,
    handler:{(action)->() in
       self.resultLabel.text = "\(self.volume)"
    })
   

Add the actions to the alert

alert.addAction(clearAction)
alert.addAction(doubleAction)
alert.addAction(cancelAction)

Finally present the alert controller. present has a completion handler. Add a print statement to it to see it in action, then a print after the present.

 
present(alert, animated: true, completion: 
{()->Void in
    print ("present Completed")
 })
 print ("Outside Handler")

Build and run. Tap the Alert button. You get the alert. The console printed this:

Outside Handler
present Completed

After calling present, The system executed the next step, and did not wait for it to finish printing  Outside Handler. Once the presentation completed, the system printed present Completed. The alert shows:

2016-09-12_06-03-57

Tap the double button. The label reads 240. Try the other buttons.

2016-09-12_06-04-38

You’ll find closures in most often in these handlers. Anything that takes and unpredictable amount of time will use a handler in a closure.

The Whole Code

You can find the code for this tutorial on the IBM Swift Sandbox. For cutting and pasting into an Apple Playground on iPad or Xcode, Use the code below.

//
// Closure Demo file 
// For MakeAppPie.com  closure tutorial 
// Sep 2016 by Steven Lipton 
// 
//
import Foundation

 //area of a round pizza
    func roundArea(radius:Double) -> Double{
        return M_PI * radius * radius
    }
    //volume of a round pizza
    func roundVolume(height:Double, radius:Double ) -> Double{
        return roundArea(radius: radius) * height
    }
    var volume = roundVolume(height:2,radius:6)
print( String(format:"Round volume %4.2f",volume))

//Closure to change the area formula
func volume(
    height:Double, 
    dim1:Double,
    dim2:Double, 
    area:(Double,Double) -> Double) -> Double
    {
        return area(dim1,dim2) * height
    }
    
    //Assigning type (Double,Double) -> Double to Classes
    
    let rectangleArea = { (length:Double,width:Double) -> Double in
    return length * width
}
let  rightTriangleArea = {(length,width) -> Double in
    return length * width / 2.0
}  
let ovalArea = {(length,width)->Double in
    return length/2.0 * width/2.0 * M_PI
}
let roundSliceArea = { (radius:Double, slices:Double) ->Double in
    return M_PI * radius * radius / slices
}
//Trying out the volume function

print (volume(height: 2,dim1: 10, dim2: 12, area:rectangleArea))
print (volume(height: 2,dim1: 10,dim2: 12, area:roundSliceArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:rightTriangleArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:ovalArea))

//Fitting a single parameter formula with a placemarker
let circleArea = {(radius:Double, placeMarker:Double) -> Double in return M_PI * radius * radius }
print (volume(height: 2,dim1: 10, dim2: 12, area:circleArea))
// closure within parameters
        volume = volume(height: 2, dim1: 10, dim2: 12, area: {(width,length)->Double in return width * length})
        print(volume)
// Trailing closure outside parentheses
volume =  volume(height: 2, dim1: 10, dim2: 12) {
     (radius,placemarker)->Double in
          return M_PI * radius * radius
}

//Closures as Completion Handlers
func volume(
    height:Double,dim1:Double,dim2:Double,
    completionHandler:(Double)->()
){
    let result = dim1 * dim2 * height
    completionHandler(result) 
}

// For asynchronous processing, use the closure instead of returning the result. 
volume(height: 2,dim1: 10,dim2: 12)
{(result)->() in
         print(result)
         volume = result
         
}

The ClosureDemo Alert project

This is the code for the ClosureDemo Alert project. Add the label and button as described above than connect to this code in ViewController.

//
//  ViewController.swift
//  ClosurePlay
//
//  Created by Steven Lipton on 9/9/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    var volume = 0.0
    
    @IBOutlet weak var resultLabel: UILabel!
    @IBAction func compute(_ sender: UIButton) {
        volume = 240.0
        //example of a real completion handler.
        let alert = UIAlertController(
            title: "Alert",
            message: "Volume is \(volume)",
            preferredStyle: .alert)
        
        let clearAction = UIAlertAction(
            title: "Clear", style: .destructive,
            handler: {(action)->() in
                self.volume = 0
                self.resultLabel.text = "\(self.volume)"
        })
        
        let doubleAction = UIAlertAction(
            title: "Double",
            style: .default,
            handler: {(action)->() in
                self.volume *= 2
                self.resultLabel.text = "\(self.volume)"
        })
        let cancelAction = UIAlertAction(
            title: "Cancel",
            style: .cancel,
            handler:{(action)->() in
                self.resultLabel.text = "\(self.volume)"
        })
        
        
        alert.addAction(clearAction)
        alert.addAction(doubleAction)
        alert.addAction(cancelAction)
        present(alert, animated: true, completion: {()->Void in
            print ("In present Handler")
        })
        print ("Outside Handler")
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
}

One response to “Understand and Use Closures in Swift”

  1. […] has a closure as its parameter. (If you are not familiar with closures you might want to refer here. ) You add your code to the closure to process a collection of requests. Here I named it requests. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: