Leave a rating/review
Notes: 12. Closure Syntax
Update Notes: This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.
Now that you’ve got the basic idea, let’s look at couple more special features of closures. Closures are meant to be more lightweight than functions. They’re really great alternatives to functions you wouldn’t get a lot of reuse out of.
That’s why you can just write them inline like this! So far we’ve been writing out closures in the longest form possible.
But there are several ways to shorten up closure syntax! This can make inline closure arguments more compact, and sometimes easier to read. It can also make them harder to read, so shortening closures is a power to use with care.
Even if you always write your closures in the long form, you should know how to read the short versions. So let’s take a look at what all the options are!
Let’s start with one more closure in the long form. Make a new constant and call it longClosure
let longClosure
Open up the closure. Give it two parameters of type Int, and just call them a and b
let longClosure 😺= { (a: Int, b: Int)
}
Have it return an Int, and start the body with in
let longClosure = { (a: Int, b: Int) 😺-> Int in
and then let’s just multiply those two parameters together.
a * b
Go ahead and copy that whole thing. Paste it below, and rename it noParameterTypes
let noParameterTypes
Now, to shorten this closure up, we’re actually going to be relying on type inference in a different way than we have before.
This time, we’ll explicitly type this constant so we can leave more information out of the closure itself. So use that Operate typealias again!
let noParameterTypes😺: Operate
Because we’re specifying the type, the types in the parameter list can be inferred. Get rid of them!
let noParameterTypes: Operate = { (a❌: Int❌, b❌: Int❌) -> Int in
Make a copy of that one, and call the next one noReturnType
let 😺noReturnType
You might have guessed this, but since Operate is also specifying the return type of this closure, we can leave that information out of the closure, just like the parameter types.
let noReturnType: Operate = { (a, b) in
If you don’t want names for your parameters, you don’t need to name them at all. In our case, we’re just multiplying these parameters together. The names, a and b, aren’t very helpful or meaningful.
Make a copy of noReturnType, and call the next one shortClosure Then go ahead and delete the parameter list and the in keyword.
let shortClosure: Operate = {...
You may be wondering how you can refer to the parameters, now, if they aren’t named. Each parameter is given a number, starting with 0. And you refer to the parameter by that number with a dollar sign in front.
So “a” becomes $0 and “b” becomes $1
let shortClosure: Operate = { $0 * $1 }
This can be a bit hard to read, at first. And I don’t recommend using this exact syntax all the time. It can, however, be really handy for simple operations like this.
If a function would have used underscores for all of the argument labels, then $0 and $1 might work out in the closure version.
Regardless of which of these styles you use, the closure will work the same way. So try calling all of these closures with the same arguments…
longClosure(4, 2)
noParameterTypes(4, 2)
noReturnType(4, 2)
shortClosure(4, 2)
And when you run the playground, the results in the sidebar should be exactly the same.
There is one thing about closure syntax that also applies to functions. All of the closures we’ve looked at so far take parameters and return something, but just like with functions, they don’t have to do either of those things.
But we haven’t seen how to express the type of a function or closure that takes no parameters and returns nothing. Well, it would look like this:
let voidClosure: () -> Void
An empty parameter list, the return token, and then Void. For this type of closure, you could use the long syntax. You’d need an empty parameter list, a Void return type, and the in keyword…
let voidClosure: () -> Void 😺= { () -> Void in
}😺
But if the type of closure is already clear, I don’t recommend it. Just have this closure print out “Yay, Swift!” in the body!
let voidClosure: () -> Void = { ❌() -> Void in❌
😺print("Yay, Swift!")🛑
}
Back to this type signature. If you option-click on Void… you’ll see it is actually just a typealias for an empty tuple.
So, technically, functions and closures without a return type do return something. That something is just an empty tuple. So you may see the type writtens like this, instead:
let voidClosure: () -> 😺()🛑
A function’s parameter list, however must always be surrounded by parentheses. You can’t use Void in place of the parameter list. So, if you tried to use Void -> Void as the type…
let voidClosure: 😺Void -> Void🛑
You’ll get a helpful error in the console when you run the playground. I prefer using Void for the return type. I personally find it more clear and easier to read. But it will work either way!
let voidClosure: 😺()🛑 -> Void
Hit “Command + K” to clear out the console. Then, go ahead and call your void closure and run the playground.
voidClosure()
Yay, Swift!
There’s one last thing I’ll show you about closure syntax. I included this printResult function in the playground page, again, but this time operate is the last parameter.
When you call a function that takes a closure as the last parameter, you can use something called “trailing closure syntax”. Let’s look at the difference.
First, call printResult with an inline closure like we’ve already practiced, but maybe try it with a shortened closure form this time.
printResult(10, 3, { $0 * $1 + 10 })
You can use the short form, here, because the function defines the type of the closure for you. Now we’ll do it once more, and pass in the same values for the first two arguments…
printResult(10, 3, operate: (Int, Int) -> Int)
But when this last argument placeholder is highlight, press return…
And this pre-formatted closure should have been autogenerated for you!
printResult(10, 3) 💛{ (<#Int#>, <#Int#>) -> Int in
<#code#>
}💛
Notice that the parentheses close after the second argument, and then we have the closure’s opening curly brace?
printResult(10, 3💛) {💛
This is trailing closure syntax! There are a lot of methods that take closures as the last or only parameter, so you’ll see this used pretty frequently in iOS development.
Now you can fill this closure in with whatever type of syntax you want. I’m just going to copy the one above…
printResult(10, 3) { 😺$0 * $1 + 10🛑 }
And if we run the playground, the results should, again, be the same in the sidebar.
Now that you’ve spent some time with closures, the ones you wrote in the tasklist app might be easier for you to read. For example, you created buttons with two different initializers!
This one takes a String as the first argument, and then a closure representing the button’s action as the second argument. Because the closure is the last argument, it’s using trailing closure syntax.
You may have also noticed that there’s no parameter list or return type in this closure! This closure type takes no parameters and returns nothing, just like our Void closure.
Button("Add") {
let priorityIndex = self.taskStore.getIndex(for: self.priority)
self.taskStore.prioritizedTasks[priorityIndex].tasks.append(
Task(name: self.text)
)
self.presentationMode.wrappedValue.dismiss()
}
The other button initializer you used takes two closures. The first one has an argument label, action, and the closure is written inline, inside the parentheses. This action also takes no parameters and return nothing.
The second one is the last parameter, so it’s written in the trailing syntax.
This one returns a view to act as the label, but it still doesn’t take any parameters. So the closure is written in the shortest form, and implicitly returns an Image.
Button(
action: { self.modalIsPresented = true }
) {
Image(systemName: "plus")
}
While it is a standard practice to use trailing closures, you aren’t required to. In the case of creating a button like this, you’d just need to include the argument label, and keep the closure inside of the parentheses.
Button(
action: { self.modalIsPresented = true }
) {
Image(systemName: "plus")
}
Button(
action: { self.modalIsPresented = true },
label: { Image(systemName: "plus") }
)
There was one more closure you wrote for the TaskList app that we’ll go over, but before we get to that one, there’s another challenge for you!
init(priority: Task.Priority, names: [String]) {
self.init(
priority: priority,
tasks: names.map 💛{ Task(name: $0) }💛
)
}