In Swift,
closures are self-contained blocks of code that can be passed around and used in your code. Think of closures as
anonymous functions in JavaScript,
lambda expressions in C#, and
blocks in C and Objective-C. Here is an example of closure in action in Swift:
let alertController = UIAlertController(
title: "Hello!",
message: "Hello, World!",
preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(
title: "OK",
style: UIAlertActionStyle.default) {
(action) -> Void in
print("Tapped on OK")
}
alertController.addAction(okAction)
self.present(alertController,
animated: true, completion: nil)
In the above code snippet, when the OK button in the alert is tapped, you handle the action using a closure:
{
(action) -> Void in
print("Tapped on OK")
}
This closure takes in a single parameter -
action, and does not return any value (
Void). The body of the closure is a single statement that prints a string to the Output window. You can of course have more than one statement in the body of the closure. This works just like a usual function without the
func keyword and the function name.
Besides this example, what are the real advantages of closures? Let's consider the classic example of the bubble sort:
func bubbleSort(items:inout [Int]) {
for j in 0 ..< items.count-1 {
var swapped = false
for i in 0 ..< items.count-1-j {
if (items[i] > items[i+1]) {
let temp=items[i+1]
items[i+1]=items[i]
items[i]=temp
swapped=true
}
}
if !swapped {
break
}
}
}
The above code snippet contains a function that takes in an array and then performs a bubble sort on it:
var numbers = [5,2,8,7,9,4,3,1]
bubbleSort(items: &numbers)
print (numbers) // [1, 2, 3, 4, 5, 7, 8, 9]
There are two potential disadvantages with this implementation:
- What happens if you want to sort non-integer values, such as string, doubles, etc? You need to rewrite the code to sort different data types. To solve this, you can use generics.
- What happens if you want to sort the values in descending order? You need to rewrite the code to perform the appropriate logic for swapping. And what happens if you have different criteria to determine the sort order for different data types?
A better approach would be to rewrite the
bubbleSort() function using a closure so that the caller of this method can provide its own implementation for the sort order. The rewritten function would look like this:
func bubbleSort(items:inout [T],
compareFunction:(T, T) -> Bool) {
for j in 0 ..< items.count-1 {
var swapped = false
for i in 0 ..< items.count-1-j {
//---if the two numbers need to be swapped---
if compareFunction(items[i],items[i+1]) {
let temp=items[i+1]
items[i+1]=items[i]
items[i]=temp
swapped=true
}
}
if !swapped {
break
}
}
}
Observe that:
- The function now has a second parameter (compareFunction:) of function type (T,T) -> Bool where T is a generic type. Caller of this function are expected to pass in a closure with two input parameters and return a Bool result.
- Within the bubbleSort() function, it will call the closure passed into it and use its result to determine if a variable swap is needed.
To use the rewritten bubbleSort()function, you can call it like this:
var numbers = [5,2,8,7,9,4,3,1]
bubbleSort(items: &numbers,
compareFunction: {
(num1:Int, num2:Int) -> Bool in
//--if num1 > num2, need to swap--
return num1 > num2
})
print (numbers) // [1, 2, 3, 4, 5, 7, 8, 9]
Note the closure highlighted in bold. In essence, you are passing in a function with two parameters. This function will return a
true if the first number is greater than the second number. Essentially, you are sorting the numbers in ascending order. If you want to sort the numbers in descending order, simply change the comparison operator to
<:
var numbers = [5,2,8,7,9,4,3,1]
bubbleSort(items: &numbers,
compareFunction: {
(num1:Int, num2:Int) -> Bool in
//--if num1 > num2, need to swap--
return num1 < num2
})
print (numbers) // [9, 8, 7, 5, 4, 3, 2, 1]
The real advantage of using closure if when you need to sort strings in your own custom order. For example, you have an array of strings like this:
var names = ["xxx","yy","z"]
To sort the strings based on string length, you can specify your closure like this:
bubbleSort(items: &names,
compareFunction: {
(name1:String, name2:String) -> Bool in
//--if length of name1 > length of name2, need to swap--
return name1.characters.count > name2.characters.count
})
print (names) // ["z", "yy", "xxx"]
For purist, the above code snippet can be rewritten like this:
bubbleSort(items: &names) {
(name1, name2) -> Bool in
name1.characters.count > name2.characters.count
}
print (names)
How cool is that. Hopefully, this posting can help you better understand and appreciate closures!