DEV Community

Gamya
Gamya

Posted on

Swift Closures โ€” Accepting Functions as Parameters ๐ŸŽฏ

So far we've been passing closures into functions that Swift provides โ€” like sorted(), filter(), and map(). But what if you wanted to write your own function that accepts another function as a parameter? That's exactly what we're covering today. ๐Ÿฅ

This might sound complicated, but once you see it in action it starts to make a lot of sense โ€” and it's everywhere in SwiftUI.


Why Would You Even Want This?

Before we write any code, let's think about why this matters.

Imagine you're building an anime battle app that needs to fetch data from a server โ€” maybe pulling in a list of all One Piece characters. Your iPhone can do billions of things per second, but waiting for a server response can take half a second or more. That's practically glacial by comparison.

If your app just sat there waiting for the server to respond, the whole UI would freeze. Nobody wants that.

The solution? Pass in a closure that says: "go do this slow work, and when you're done, call this function with the result." Your app keeps running smoothly, and the closure fires when the data arrives. That's closures as parameters in a real-world nutshell. ๐ŸŒ€


Writing a Function That Accepts a Function

Let's start with a practical example. Here's a function that generates an array of random jutsu power levels by calling another function repeatedly:

func generatePowerLevels(count: Int, using generator: () -> Int) -> [Int] {
    var levels = [Int]()

    for _ in 0..<count {
        let newLevel = generator()
        levels.append(newLevel)
    }

    return levels
}
Enter fullscreen mode Exit fullscreen mode

Let's break down what's happening on that first line:

func generatePowerLevels(count: Int, using generator: () -> Int) -> [Int]
Enter fullscreen mode Exit fullscreen mode
  • count: Int โ€” how many power levels to generate
  • using generator: () -> Int โ€” a function parameter called generator, which takes no parameters itself but returns an Int every time it's called
  • -> [Int] โ€” the whole generatePowerLevels function returns an array of integers

Inside the function, we just call generator() on each loop iteration, collecting its returned value into our array.

Now let's call it using a trailing closure:

let powerLevels = generatePowerLevels(count: 5) {
    Int.random(in: 1...9000)
}

print(powerLevels) // e.g. [4521, 8832, 312, 7741, 999]
Enter fullscreen mode Exit fullscreen mode

Swift sees the trailing closure and knows it matches the generator: () -> Int parameter โ€” no labels needed.

You can also pass in a named function instead of a closure:

func randomChakra() -> Int {
    Int.random(in: 1...9000)
}

let chakraLevels = generatePowerLevels(count: 5, using: randomChakra)
print(chakraLevels)
Enter fullscreen mode Exit fullscreen mode

Both produce exactly the same result โ€” a closure and a named function are interchangeable here, because they have the same type: () -> Int.


Reading the Function Signature

The trickiest part of accepting functions as parameters is reading the syntax. Let's slow down on it:

func generatePowerLevels(count: Int, using generator: () -> Int) -> [Int]
Enter fullscreen mode Exit fullscreen mode

There are two -> arrows here, which can be confusing at first:

  • The first -> (inside () -> Int) belongs to the parameter function โ€” it describes what the function we're passing in returns
  • The second -> (at the end, -> [Int]) belongs to our function โ€” it describes what generatePowerLevels itself returns

Think of it like this: generator is a function that lives inside the parameter list. It has its own type, just like Int or String would โ€” it just happens to be a function type.


Multiple Trailing Closures

Here's something that appears constantly in SwiftUI: functions that accept multiple function parameters.

Imagine an anime training sequence that needs to run three stages โ€” warmup, training, and cool-down โ€” each customizable:

func runTrainingArc(warmup: () -> Void, training: () -> Void, cooldown: () -> Void) {
    print("๐Ÿƒ Starting warmup...")
    warmup()
    print("โš”๏ธ Starting training...")
    training()
    print("๐Ÿง˜ Starting cooldown...")
    cooldown()
    print("โœ… Training arc complete!")
}
Enter fullscreen mode Exit fullscreen mode

When calling a function with multiple trailing closures, the first one works exactly like before โ€” no label, just {. But the second and third each get their label written outside the brace:

runTrainingArc {
    print("Stretching and light jogging")
} training: {
    print("Naruto Shadow Clone Jutsu x1000")
} cooldown: {
    print("Ramen break ๐Ÿœ")
}
Enter fullscreen mode Exit fullscreen mode

Output:

๐Ÿƒ Starting warmup...
Stretching and light jogging
โš”๏ธ Starting training...
Naruto Shadow Clone Jutsu x1000
๐Ÿง˜ Starting cooldown...
Ramen break ๐Ÿœ
โœ… Training arc complete!
Enter fullscreen mode Exit fullscreen mode

This multiple-trailing-closure syntax is something you'll see all the time in SwiftUI โ€” for example, creating a Section with a header, content, and footer each uses a separate trailing closure.


The Function Type Cheat Sheet

When writing function parameters, here's how to read and write the types:

What the function does Its type
Takes nothing, returns nothing () -> Void
Takes nothing, returns an Int () -> Int
Takes a String, returns nothing (String) -> Void
Takes two Strings, returns a Bool (String, String) -> Bool

The parameter list goes in the first (), and the return type goes after ->. That's all function types are โ€” a description of what goes in and what comes out.


Why This Matters for SwiftUI

In SwiftUI, almost every component you build uses this pattern. A Button accepts a function for what happens when tapped, and a function for what to display. A List accepts a function to generate each row. Even a VStack uses a closure to hold all its child views.

Once you're comfortable with the idea of passing functions around as arguments, SwiftUI's syntax stops looking mysterious and starts reading like a natural description of your UI. That's the payoff for all this closure work. ๐ŸŒธ


Wrap Up

Concept What It Means
Function as parameter You can pass a function (or closure) as an argument to another function
() -> Int A function type โ€” takes nothing, returns an Int
Multiple trailing closures Call each extra closure with its label outside the brace
Named function vs closure Interchangeable as long as the type matches

This article was written by me; AI was used to improve grammar and readability.


Top comments (0)