Gain an in-depth understanding of iOS development by learning about closures and their use for callbacks. Enhance your coding skills by writing self-contained functions, known as closures, and exploring how to use them to simplify and shorten your code.
Key Insights
- During iOS development, closures can be used to simplify and shorten code. These are self-contained functions without a full declaration and name, hence they are anonymous.
- Closures can be used for the same purposes as functions, including passing arguments and getting return values.
- In iOS development, closures can be used to return values. For example, they can be used to calculate the amount of money owed for gas used in a car.
- Inline closures can be written as an alternative to separate functions. Instead of declaring a function name, the equivalent of the function can be written within curly braces {}.
- Closures can be used for callbacks. Callbacks are asynchronous functions that can be passed into another function as an argument, to be executed at a later time. This can define a behavior to be completed when a certain task is done.
- Closures can be passed to classes, which accept them as functions and create a callback based on whether a certain condition has succeeded or failed.
Learn how to simplify your iOS development code with closures in this comprehensive tutorial, including how to define a closure, use closures with parameters and returned values, and utilize closures for callbacks.
This exercise is excerpted from Noble Desktop’s past app development training materials and is compatible with iOS updates through 2021. To learn current skills in web development, check out our coding bootcamps in NYC and live online.
Topics Covered in This IOS Development Tutorial:
Defining a Closure, Closures with Parameters & Returned Values, Closures Used for Callbacks
Exercise Overview
In this exercise you’ll learn how to pass around self-contained functions called closures in your code. Closures are basically functions without a full declaration and name—they’re anonymous. You can do many of the same things that you can do with a function, from passing arguments to getting return values. We’ll look at how using closures can shorten and simplify our code.
Getting Started
Launch Xcode if it isn’t already open.
If you completed the previous exercise, Car.xcodeproj should still be open. If you closed it, re-open it now.
-
We suggest you complete the previous exercises (4A–4B) before doing this one. If you did not do the previous exercises, complete the following:
- Go to File > Open.
- Navigate to Desktop > Class Files > yourname-iOS App Dev 1 Class > Car Protocols Done and double–click on Car.xcodeproj.
Closures with Returned Values
We want to calculate the amount of money owed for gas used in the car. Using what you’ve learned so far, you could write a function to return the amount of gas:
func calculateGas(gallons: Int, price: Int) -> Int {
return gallons * price
}
var amountOwedForGas = calculateGas(5, price: 3)
But there is a shorter way of doing this. You can write an inline closure, rather than having a separate function.
In the Project Navigator, go to ViewController.swift.
-
Inside viewDidLoad, around line 21, write a closure that will return the amount owed for gas (shown in bold):
mustang.delegate = self mustang.start() var amountOwedForGas = { } }
Closures are written as a function type, with parameters and a return type followed by the keyword in as shown below. The in keyword indicates that the definition of the closure’s parameters and return type is done, and that the body of the closure is about to begin.
{(parameters) -> returnType in statements }
-
Within the curly braces { } create a function:
var amountOwedForGas = {(gallons: Int, price: Int) -> Int in }
This is the equivalent of the calculateGas example above, but without having to declare the function name.
-
Let’s assign the return of our anonymous function back to the amountowedForGas variable. Add the following statement as shown below:
var amountOwedForGas = {(gallons: Int, price: Int) -> Int in return gallons * price }
-
You can now use the variable as a function, and pass values into it. Add the print function as shown below:
var amountOwedForGas = {(gallons: Int, price: Int) -> Int in return gallons * price } print("The amount you owe for gas is \(amountOwedForGas(5,3))")
Let’s test out our code so far.
At the top right, click the Show the Debug area button
if it’s not already showing.
-
At the top left of Xcode, click the Run button
.
The simulator will take a moment to load. At this point, it will just be a blank white screen.
-
Go back to the main Xcode window. Take a look at the Debug area to see the last message says: The amount you owe for gas is 15
Our calculation worked!
Closures Used for Callbacks
There may be times when you want to have a method send back a note about whether something has succeeded or failed—this is known as a callback.
A callback is an asynchronous function that can be passed into another function as an argument, to be executed at a later time. This is usually done to define a behavior to be completed when a certain task is done. Similarly to how you’ve previously used delegates, you can use closures to get callbacks from functions as well.
Let’s create a function in our Car class. In the Project Navigator, select Car.swift.
-
At the bottom of the Car class, around line 56, add a function declaration for accelerateTo with the parameter for speed:
func gasUsed(milesDriven: Float) -> Float { let gas = milesDriven / mpg! return gas } func accelerateTo(speed: Int) { }
-
Now add in the callback:
func accelerateTo(speed: Int, success:() -> (), fail: () -> ()) { }
Let’s break this down: The parameter name is the callback name. The success and fail parameters have a return function with an undefined value type.
NOTE: If a function does not define a return data type, we indicate this using empty parentheses ().
-
We want the accelerateTo function to inform us if it has succeeded or failed. Write an if-else statement inside the new function:
func accelerateTo(speed: Int, success:() -> (), fail: () -> ()) { if speed >= 20 { success() } else { fail() } }
When you tell the car to accelerate, if its speed is greater than or equal to 20, the success method will be called. If the speed is less than 20, the fail method will be called.
Now let’s add the accelerateTo method to our viewDidLoad method in our viewController. In the Project Navigator, go to ViewController.swift.
-
As shown in bold below, add:
print("The amount you owe for gas is \(amountowedForGas(5,3))") mustang.accelerateTo(speed: 20, success: {() -> () in }, fail: {() -> () in }) }
-
Add print functions to log succeeded or failed messages:
mustang.accelerateTo(speed: 20, success: {() -> () in print("We have succeeded") }, fail: {() -> () in print("We have failed") })
We are passing in closures to the Car class, which accepts them as functions and creates a callback based on whether the condition succeeded or failed.
Time to test our code! At the top right, click the Show the Debug area button
if it’s not already showing.
At the top left of Xcode, click the Stop
button.
-
Click the Run button
.
The simulator will take a moment to load. At this point, it is still a blank white screen.
-
Go back to the main Xcode window. Take a look at the Debug area to see the last message says: We have succeeded
The success method runs because you set the speed value to 20 in the viewDidLoad method.
-
Let’s see what happens if you change the value. In ViewController.swift around line 27, change 20 to 10:
mustang.accelerateTo(speed: 10,
Click the Stop button
.
Click the Run button
.
-
Take a look at the Debug area of Xcode to see the last message now says:
We have failed
The fail method runs because we set the speed value to 10 in the viewDidLoad method. Remember that earlier we specified that if the speed is less than 20, the fail method will be called.
Save and close the file. Keep Xcode open, as we’ll use it in the next exercise.