You’ll find closures throughout the API’s, but you may not know how to use them properly. Let’s take a basic tour of closures.
Download the exercise file, and you’ll find a project with a playground. In the playground you’ll find a function to compute a pizza volume.
func roundPizzaVolume(height:Double, diameter:Double) -> Double{ let pi = Double.pi let z = diameter / 2.0 let a = height let v = pi * z * z * a //My favorite pun. return v } roundPizzaVolume(height: 2, diameter: 10)
That’s for a round pan pizza, for a rectangle it won’t work.
But if I make a function that has a formula for the area, then I can do any area. In the anyPizzaVolume
, I’ll put one at the end of the argument list.
anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{
That declares a closure, which is not much more than a function that you pass as a parameter.
In my code I’ll multiply height by area, then return the volume.
func anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{ let volume = height * area(length,width) return volume }
I’ll call anyPizzaVolume.
In the autocomplete for anyPizzaVolume
, you’ll see the closure.
anyPizzaVolume(height: , length: , width: , area: Double>)
Add a height of 2, a length of 10 and a width of 10. Hit tab when you get to area, and you’ll get a stubbed closure. I usually use this, but I’m going to do it manually so you get the idea. You stick the closure as the last parameter, so you can put the closure outside the function. Otherwise you need to put it inside, and that can get confusing to read.
anyPizzaVolume(height: 2, length: 10, width: 10) { }
You start a closure identifying the declared variables, then add the keyword in
anyPizzaVolume(height: 2, length: 10, width: 10) { (l,w) in }
Now you write code based on those two values.
anyPizzaVolume(height: 2, length: 10, width: 10) { (l,w) in let r = l / 2.0 return r * r * Double.pi }
Run this and you get the answer you got before. I can copy this, paste it, and change the code to a rectangle area.
anyPizzaVolume(height: 2, length: 10, width: 10) { (l,w) in return l * w }
Run again, and you get a different answer. There are better ways of doing this method, but I wanted to illustrate the basic use of closures. Where they get used is Asynchronous processing. That’s when you don’t control when the code will execute like completion handlers, timers, and actions. For example, in the roundPizzaVolume
I could add a completion handler that handles errors.
completionHandler:(Bool,Double)-> Void
For this closure, I’m not returning anything to the function. Closures require a return type, so I useVoid
to indicate there is noting returned. I’ll add the completionHandler`
function to to the code
completionHandler(v>0,v)
The bool indicates if the area is a meaningful answer, and I pass the volume to the closure. Now I can change the roundPizzaVolume code to use the closure by adding this:
roundPizzaVolume(height: -2, diameter: 10){ (success,volume) in if !success{ print("Invalid Volume \(volume)") } }
Change the height to -2 and Run. Many closures, especially file handling, will do this and give you a success bool which you can then handle after it does it function.
Take a look at the ViewController
. You’ll find examples of API’s using closures. Action handlers are often closures. For example I have an alert in this code. UIAlertAction
s use closures to respond to the selection of the action.
let actionOne = UIAlertAction(title: “First Action”, style: .default) { (action) in
self.alertStatus.text = “First Action”
}
Notice that as asynchronous blocks, you need self
to use properties and methods of the class. Also you can make closures optional, so if you do not want to use them, they can be set to nil, as in this action.
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
There’s a lot you can do with closures. As you can see here, you will find them in code frequently.
The Whole Code
You’ll find the completed code on Github here. Below you’l find the playground and view controller for this project.
import UIKit // // An exercise file for iOS Development Tips Weekly // by Steven Lipton (C)2018, All rights reserved // For videos go to http://bit.ly/TipsLinkedInLearning // For code go to http://bit.ly/AppPieGithub // func roundPizzaVolume(height:Double, diameter:Double, completionHandler:(Bool,Double)->Void) -> Double{ let pi = Double.pi let z = diameter / 2.0 let a = height let v = pi * z * z * a //My favorite pun. completionHandler(v>0,v) return v } roundPizzaVolume(height: -2, diameter: 10){ (success,volume) in if !success{ print("Invalid Volume \(volume)") } } func anyPizzaVolume(height:Double, length:Double, width:Double, area:(Double,Double)->Double)-> Double{ let volume = height * area(length,width) return volume } anyPizzaVolume(height: 2, length: 10, width: 10) { (l,w) in let r = l / 2.0 return r * r * Double.pi } anyPizzaVolume(height: 2, length: 10, width: 10) { (l,w) in return l * w }
// // ViewController.swift // ClosureExercise // // An exercise file for iOS Development Tips Weekly // by Steven Lipton (C)2018, All rights reserved // For videos go to http://bit.ly/TipsLinkedInLearning // For code go to http://bit.ly/AppPieGithub // import UIKit class ViewController: UIViewController { @IBOutlet weak var presentStatus: UILabel! @IBOutlet weak var alertStatus: UILabel! @IBOutlet weak var alertButton: UIButton! @IBAction func alertButton(_ sender: UIButton) { let alert = UIAlertController(title: "Demo Alert", message: "Some Closure examples", preferredStyle: .alert) //Actions for methods like alerts often use closures to do the action let actionOne = UIAlertAction(title: "First Action", style: .default) { (action) in //This is on a seperate thread so use self to access the elclosing class' methods and properties. self.alertStatus.text = "First Action" //this is on a seperate thread so use self to access the elclosing class' methods and properties. } let actionTwo = UIAlertAction(title: "Second Action", style: .default) { (action) in //This is on a separate thread so use self to access the enclosing class' methods and properties. self.alertStatus.text = "Second Action" } // Closures can be optional, and then you can set the value to nil if you don't plan to use it. Make sure you handle the nil in the method though. let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alert.addAction(actionOne) alert.addAction(actionTwo) alert.addAction(cancelAction) // The present function has a closure we usually leave nil, but if you have clean up after you launch the method, this is where you do it. Here I print a status message. present(alert, animated: true) { self.presentStatus.text = "Presented Alert" } } override func viewDidLoad() { super.viewDidLoad() alertButton.layer.cornerRadius = 20 // Do any additional setup after loading the view, typically from a nib. } }
Leave a Reply