A closure's type is declared like the argument and return type of a function

  var someWork:()->()

This one doesn't take any arguments, or return any values. Notice that even when we have nothing to return, we still need an arrow and empty parentheses. The empty parentheses are technically an empty tuple, which in Swift, is identical to Void.

  var someWork:()->Void

In other words, it doesn't return anything.

When adding arguments, you'll notice that argument labels are not allowed:

  var someWork:(a:String, b:Int)->Void
  //error:Function types cannot have argument label.

As a result, you may often see closure types with nothing but argument types:

  var someWork:(String, Int)->()

We can however, place in an argument name:

  var someWork:(_ a:String, _ b:Int)->()

Create a closure with curly braces.

  var someWork:(_ a:String, _ b:Int)->() = {
    print("a = \(a), b = \(b)")
  }

This doesn't compile, because in order to use argument names, we'll need to declare them inside the closure.

  var someWork:(_ a:String, _ b:Int)->() = { (a:String, b:Int)->() in
    print("a = \(a), b = \(b)")
  }

We also need to write in after the arguments and return declaration.

A closure is executed with parentheses, like a function.

  someWork("5", 4)

Notice that we aren't using argument labels. For functions, argument labels are used to help select which function to call. Since you'll provide the closure explicitly, they are not needed. This can get a little confusing at the read site.

You must use documentation comments to communicate what the arguments represent.

Optional Closures

Just as we can use optionals to create a variable that may or may not contain an Int or String or other type, so we can create Optional closures.

  var someWork:(_ a:String, _ b:Int)->()?

However, the ? binds with the return type, instead of the entire closure. Surround the entire closure type with parentheses, and place the ? after that.

  var someWork:((_ a:String, _ b:Int)->())? = nil
  someWork = { (a:String, b:Int)->() in
    print("a = \(a), b = \(b)")
  }

To call an optional closure, use the ?, just like optional chaining for any other type.

  someWork?("5", 4)
  //a = 5, b = 4

When we accept closure definitions as an argument or return type of a function, it can cause parentheses blindness.

  func doSomething(a:(()->())?, g:(()->())?)->((String)->(Int))

Use typealias to name the combination of argument and return types.

  typealias ACombiner = (Int)->(String)
  typealias GInterpreter = (String)->(Int)
  typealias TailConverter = (String)->(Int)
  func doSomething(a:ACombiner?, g:GInterpreter?)->TailConverter

The typealiases not only make the function site cleaner, they can declare common formats for closures in your code.

Capturing Local Scope

Closures have access to more than just their arguments.

  var c:Int = 8
  someWork = { (a:String, b:Int)->() in
    print("a = \(a), b + c = \(b + c)")
  }

Closures have access to "local scope", in this case, c.

Here's an example of a closure created inside a method.

  struct Person {
    var firstName:String
    var lastName:String
  }

  enum Sorting {
    case firstName, lastName
  }

  class PersonDatabase {
    var people:[Person] = []
    var preferredSorting:Sorting = .firstName
    func peopleBeginning(with nameComponent:String)->[Person] {
      return people.filter({(person)->Bool in
        return person.firstName.hasPrefix(nameComponent) || person.lastName.hasPrefix(nameComponent)
      }).sorted(by: {(aPerson, anotherPerson) -> Bool in
        switch self.preferredSorting {
        case .firstName:
          return aPerson.firstName < anotherPerson.firstName
        case .lastName:
          return aPerson.lastName < anotherPerson.lastName
        }
      })
    }
  }

In this example, a PersonDatabase contains an array of Persons. A function, peopleBeginning(with returns an array of Persons that have part of their name beginning with the argument. In this example, I'm not suggesting that the right way to maintain your contacts database is in-RAM, but we are looking at how closures capture values.

In the implementation, we use two closures. The first is provided to the filter method. The closure returns a Bool which tells filter whether to include each Person or not. This closure captures the functions' argument so it can be used in the .hasPrefix function. filter then returns an array, and we're on to the next method.

The second closure supplied to the sorted method captures the PersonDatabase instance, and accesses its preferredSorting property to determine whether to sort the results by first name or last name. Notice that we can't just capture preferredSorting by itself. We have to write self. in front of it. This is to remind us that we're capturing the instance in the closure. We'll talk more about the memory management ramifications in the next video.

Functional Programming

Array implements filter and sorted methods to support "functional programming". It uses a closure to implement a single piece of work, to make a key decision, and then uses that decision to complete the overall task.

Another major functional programming method is map. This transforms an Array of one type into an Array of other types. Let's look at getting full names from an Array of Persons.

  var everybody:[Person] = [Person(firstName: "Ben", lastName: "Spratling")
  ,Person(firstName: "Daily", lastName: "Drip")]

  let fullNames:[String] = everybody.map { (person) -> String in
    return "\(person.firstName) \(person.lastName)"
  }

In this example, the closure "transforms" the Person into a String, by returning a String from a closure that takes a Person as an argument. Our code doesn't have to iterate or create the new array, the map function does that for us.

Summary

Today, we learned how to identify closures in syntax. We learned about basic capture or other properties and variables. We also learned how to use basic functional programming concepts like filter, map, and sorted.

Using closures to mix object-oriented and functional programming, now that's Swift!

Matthew Ray

Matthew is a tech news enthusiast. He spends most of his time reading tech focused news, watching sports and drinking coffee. You will probably find him at a coffee shop or the library.

  1. Comments for Writing Closures

You must login to comment

You May Also Like