Skip to Content
Course content

7: Functions and Closures

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen a lot of people trip up when they start treating functions as "first-class citizens" in Swift. By that, I mean the moment you realize you can pass a function into another function just like you'd pass an integer or a string. It's incredibly powerful, but the syntax for closures and function references can feel like a riddle if you aren't expecting a few specific quirks.

Take a look at this. Imagine we're building a simple e-commerce app. We have a Product struct and a function that filters a list of products based on a condition we provide.

struct Product {
    let name: String
    let price: Double
}

func filterProducts(products: [Product], condition: (Product) -> Bool) -> [Product] {
    return products.filter(condition)
}

func isExpensive(product: Product) -> Bool {
    return product.price > 100.0
}

let myProducts = [Product(name: "Cable", price: 10.0), Product(name: "Monitor", price: 200.0)]

// ❌ This line causes a compiler error!
let expensiveStuff = filterProducts(products: myProducts, condition: isExpensive)

The Argument Label Trap

If you try to compile this, Swift will scream at you. It'll tell you that it can't convert a function (Product) -> Bool to a closure of type (Product) -> Bool. That sounds insane—they look identical, right? But here is the catch: in Swift, argument labels are part of the function's type signature.

The filterProducts function expects a closure that takes a Product with no label. However, the isExpensive function is defined as taking a Product with the label product:. To the compiler, (Product) -> Bool and (product: Product) -> Bool are completely different types. I've spent way too many hours debugging this exact mistake early in my career.

Stripping the Label for Flexibility

To fix this, we need to tell Swift that the isExpensive function doesn't require a label when it's being called as a closure. We do this using an underscore _ in the function definition. This makes the function "label-less" for the caller.

// ✅ Fixed: Added the underscore to remove the external label
func isExpensive(_ product: Product) -> Bool {
    return product.price > 100.0
}

let expensiveStuff = filterProducts(products: myProducts, condition: isExpensive)
// Now it works perfectly.

Writing Closures Inline

While passing existing functions is great, you'll rarely do it that often. Most of the time, you'll write a closure—essentially an anonymous function—directly inside the call. This saves you from polluting your namespace with tiny functions you'll only use once.

You can replace isExpensive with a closure like this:

let cheapStuff = filterProducts(products: myProducts) { product in
    return product.price < 20.0
}

Notice two things here. First, I used trailing closure syntax. Since the closure is the last argument in the function call, I can move it outside the parentheses. It's much cleaner and is the standard way we write Swift. Second, the product in keyword is how we name the arguments being passed into the closure.

Capturing Values from the Outside

One of the coolest (and most dangerous) parts of closures is "capturing." A closure can "remember" variables from the environment where it was created, even if that environment is gone. This is how we handle things like network callbacks or button taps in iOS.

Check this out:

func makePriceChecker(threshold: Double) -> (Product) -> Bool {
    // This function returns ANOTHER function
    return { product in
        return product.price > threshold 
    }
}

let isOverFifty = makePriceChecker(threshold: 50.0)
let isOverThousand = makePriceChecker(threshold: 1000.0)

let result = isOverFifty(Product(name: "Keyboard", price: 60.0)) // true

The closure returned by makePriceChecker "captured" the threshold value. Even after makePriceChecker finished executing, the closure held onto that 50.0. It's a bit like a function with a built-in memory.




📋 Practical Task

Build a Custom Movie Genre Filter

You are building a movie library app. You have a Movie struct with a title and a genre. Your task is to create a flexible filtering system that allows the user to filter movies by any criteria they want.

Requirements:

  • Create a Movie struct with title: String and genre: String.
  • Write a function called filterMovies that takes an array of Movie objects and a closure. The closure should take a Movie and return a Bool.
  • Create an array of at least 4 movies with different genres (e.g., "Sci-Fi", "Drama", "Comedy").
  • Call filterMovies using trailing closure syntax to return only the movies that belong to the "Sci-Fi" genre.
  • Call filterMovies again using a closure that returns movies whose titles have more than 10 characters.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.