Skip to content

Instantly share code, notes, and snippets.

@loganwright
Last active August 29, 2015 14:02
Show Gist options
  • Save loganwright/a54826efbc86ddfc6cba to your computer and use it in GitHub Desktop.
Save loganwright/a54826efbc86ddfc6cba to your computer and use it in GitHub Desktop.
Closures
// Playground - noun: a place where people can play
import Cocoa
/*
"Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages."
If you've already worked with functions in Swift, closures should look relatively familiar to you because functions are a type of named closure. If you haven't looked at functions yet, I highly recommend doing that.
*/
/*
There are a lot of different syntax options for closures, so we're going to use functions occasionally as a reference for clarity. Let's first look at a simple function
*/
func sayHello() {
println("Hello From Function")
}
sayHello()
/*
This should look pretty familiar, and as you can see in the console, it prints hello from our function. Let's look at some of the same functionality within an inline closure
*/
var sayHelloClosure: () -> () = {
println("Hello From Closure")
}
/*
The similarities to functions is instantly apparent in playground because it actually shows our code as (Function) in our window (right side)
That might look a little bit strange so let's walk through it. What we're doing is declaring a variable 'sayHelloClosure' of type 'function that takes no arguments and returns no value'. We then assign this variable a closure using the curly braces to wrap our syntax. We can now call our closure using () like this:
*/
sayHelloClosure()
/*
If you look at our console, you'll see that this runs the code, just like the function. Let's remember though that 'sayHelloClosure' is actually a variable of type 'function with no args and no return'. Because our original function 'sayHello' conforms to the same type, we can assign this function to our closure variable
*/
sayHelloClosure = sayHello
sayHelloClosure() // actually, says hello from function
/*
Like most things in Swift, that's pretty easy so far, but we can go a lot further. Let's complicate things a bit, again showing a function for clarification.
*/
func introduceFunction(friendOne: String, friendTwo: String) -> String {
return "\(friendOne), I'd like you to meet my function friend, \(friendTwo)"
}
var functionIntroduction = introduceFunction("Jim", "Pam")
functionIntroduction // Shows in window
/*
Ok, now let's look at some of the same functionality in an inline closure syntax
*/
var introduceClosure: (String, String) -> String = {
(friendOne: String, friendTwo: String) -> String in
return "\(friendOne), I'd like you to meet my closure friend, \(friendTwo)"
}
var closureIntroduction = introduceClosure("Jim", "Pam")
/*
Here's what we just did. Again we declared a variable, but this time we said that it's of type 'function that takes two string arguments and returns a single string argument'. Because this is what we return, it's not necessary to declare the type explicitly, but it helps with clarity at the moment. The syntax of the assignment probably looks a little strange though.
Closure syntax at its most expressive is:
{ (parameters) -> return type in
statements
}
Let's talk about how we used that. In our closure definition the first line looks very similar to function syntax, with the biggest difference being that the parameters and return type are written INSIDE of the curly braces as opposed to outside of them. There is also the addition of the keyword 'in'. " This keyword indicates that the definition of the closure’s parameters and return type has finished, and the body of the closure is about to begin."
At this point, our inline closures have looked like sort of strange versions of function declarations. Let's take them one step further and exploit one of the most powerful Swift features ... inference. We'll start by declaring a variable that we want to assign a closure to. In this case, we aren't going to assign it when we declare it, so its necessary to specify its type explicitly.
*/
var combine: (String, String) -> String
/*
This time, we're going to skip the function declaration syntax and go right to inline closures.
*/
combine = {
a, b -> String in
return a + b
}
combine("Hello ", "World!")
/*
Because we already declared our 'combine' variable to be of type 'closure that takes two String args and returns a String', Swift recognizes this and we don't need to declare our parameter types explicitly in our definition. Swift ascombinees that the 'a' and 'b' variable are of type String
Let's abstract things a bit further.
*/
combine = {
a,b -> String in a + b
}
combine("Hello ", "World!")
// Or, as Single line
combine = { a,b -> String in a + b }
/*
Not only can Swift infer parameter types, it can also assume an implicit return of a single line argument.
Because we already declared the function type of our 'combine' variable, it is clear that a String type must be returned from the closure. Since our closure's body contains a singular statement that that returns a String value, "there is no ambiguity and the return keyword can be omitted"
In fact, it's not necessary to explicitly declare the return type. Here's what it would look like omitted */
combine = { a,b in a + b }
combine("Hello ", "World!")
/*
That's much more condensed than our original declaration, but we're not even close to being done yet. Let's abstract things even further.
*/
combine = { $0 + $1 }
combine("Hello ", "World!")
/*
That looks very different from anything we've seen so far. That's because we're using another new language feature. Swift automatically provides us type inferred shorthand argument names in inline closures. These follow the pattern of $0 - Arg1, $1 - Arg2, and so on ...
Swift combines the three features we've talked about until now:
1. Type Inference to recognize the argument type
2. Implicit returns of single-expression closures
3. Shorthand argument names
and it makes for one simple statement.
These shorthand arguments could also be used in a more largescale inline closure declaration. Let's look at something with a few more lines.
This inline closure will take a source string and return true if it matches its prefix and its suffix.
*/
let hasPrefixAndSuffix: (String, String, String) -> Bool = {
var hasPrefix = $0.hasPrefix($1)
var hasSuffix = $0.hasSuffix($2)
return hasPrefix && hasSuffix
}
hasPrefixAndSuffix("Jim - Bob", "Jim", "Bob")
/*
This is fairly understandable, but as you can see things could start to get confusing very quickly. Everything is a matter of opinion, but if shorthand arguments are starting to obscure readibility, it's probably best to just use named variables.
Now, let's look at how an inline Closure might be used as an argument. We've already seen functions as arguments, and inline closures as arguments are very similar, but instead of passing a function, they are often declared at the time of the method call. Let's look at some examples.
*/
let arrayOfInts = [0,1,2,3,4,5,6,7,8,9]
// Not necessary to declare Int[] explicitly. Because we use an array literal filled with one object type, Swift infers that 'arrayOfInts' is of type Int[]
/* Let's look at one of the functions associated with arrays, the sort method. As part of the sort method, it takes a closure of type : (Int, Int) -> Bool. It's going to pass us two values from the array, and we're going to return whether or not the first object should appear before the second object.
*/
func reverseSorterFunc(numberOne: Int, numberTwo: Int) -> Bool {
if numberOne < numberTwo {
return false
}
return true
}
// Shows sorted array in Playground display
sort(arrayOfInts, reverseSorterFunc)
/*
Now, let's check our familiar closure as variable declaration.
*/
let forwardSortClosureExplicit: (Int, Int) -> Bool = {
(numberOne: Int, numberTwo: Int) -> Bool in
if numberOne < numberTwo {
return true
}
return false
}
sort(arrayOfInts, forwardSortClosureExplicit)
/* And how that would look in shorthand */
let reverseSortClosure: (Int, Int) -> Bool = { $0 > $1 }
sort(arrayOfInts, reverseSortClosure)
/*
You'll notice we still had to declare our variable 'reverseSortClosure' as type (Int, Int) -> Bool. This is so that the compiler knows how to intfer our shorthand arguments. We can skip this step altogether though, if we declare it inline with the method call. This is because the second parameter of the sort function is already declared as type (Int, Int) -> Bool. This means the compiler can use that to infer our inline closure.
*/
// Forward
sort(arrayOfInts, {$0 < $1})
/*
That's pretty clean and it still serves the same function. However, when we are declaring this way, we can exploit Swift even further by taking advantage of operator functions.
Because Swift's Int type defines its int-specific implementation of the greater than and less operators (< & >), as a function that takes to ints and returns a bool // (Int, Int) -> Bool // we can pass that as our argument. Swift will infer that we are trying to use its Int-specific implementation
*/
// Reverse
sort(arrayOfInts, >)
/*
You can declare inline closures with as much complexity as is necessary to achieve your desired results, but sometimes longer closures can look a bit clunky in practice. For this reason, Swift introduces 'trailing closures'. Let's talk about these now, but let's use the 'map' method of the Array for a bit of variety. We're going to borrow an example from Apple's documentation because it works well for this concept.
*/
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let stringArray = arrayOfInts.map() {
(number: Int) -> String in
var returnString: String = digitNames[number]!
// using the .length counts every character and takes longer
let length = countElements(returnString)
if length < digitNames.count {
returnString = digitNames[length]!
}
return returnString
}
stringArray
/*
The map function takes a single closure as its argument. "The closure is called once for each item in the array, and returns an alternative mapped value (possibly of some other type) for that item. The nature of the mapping and the type of the returned value is left up to the closure to specify."
For our particular map, we make a very strange transformation that takes each integer, and then swaps it out for an integer that represents the number of letters in its name if it were spelled out in english.
This is obviously very strange, but we needed an example of a closure that took more than a single expression!
Whenever a closure or function takes a closure as its last argument, we can use a trailing closure directly following the method call. In fact, if a closure is the only argument, we don't even need to use the parenthesis.
*/
/* ** This is just for me, you can probably just delete the parenthesis you already have since you are speaking in real time
let stringArray = arrayOfInts.map {
(number: Int) -> String in
var returnString: String = digitNames[number]!
// using the .length counts every character and takes longer
let length = countElements(returnString)
if length < digitNames.count {
returnString = digitNames[length]!
}
return returnString
}
stringArray
*/
/*
The last thing that needs to be discussed is capture lists. Because closures are reference types, and because they capture references to their values, its important to avoid reference cycle.
A reference cycle is created when two objects both have a strong reference to each other. This can cause memory leaks because even if you remove all references to them, they won't be released from memory while they're still holding on to each other.
If we assign a closure to a property of a class instance, and the body of that closure captures the instance."
Let's cover this now.
*/
/* This is the bad version, but will compile. Write this, then add in parts below
class SomeClass {
var someProperty = "Hello world"
@lazy var propertyWithClosure: String = {
() -> String in
// do some stuff
return self.someProperty + ", nice to meet you"
}()
@lazy var someClosureProperty: () -> String = {
var modified = self.someProperty + ", how are you?"
return modified
}
}
*/
/*
Lazy loading is a concept that's familiar to most ObjC developers, but in the case of Swift, it is built into the language via the @lazy attribute. If we remove the @lazy attribute from this declaration, we'll get a compiler error because "You also cannot use the implicit self property, or call any of the instance’s methods" until the class is fully initialized. Because properties are assigned before the class is fully initialized, we can't access self at all. You will get error: 'use of unresolved identifier self'
To avoid this, we declare our property @lazy. This means, the property isn't loaded until we access it. This means that self will be initialized before we try to use it and we can use it within the closure.
Notice the '()' on the end of the propertyWithClosure. This is because for 'propertyWithClosure' we want to assign the value of the closure, not the closure itsself. In this case its ok to access self because self doesn't also retain the closure and there won't be a retain cycle.
With 'someClosureProperty' however, we retain the closure itself as a property. Because this closure also retains self, the two will never be released and will cause memory leaks. Let's look at how to fix this using 'capture lists'.
Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self or someInstance). These pairings are written within a pair of square braces, separated by commas.
[weak object1], [unowned object2], [weak object3], etc...
We will declare self as unowned in our capture lists because its safe to assume it won't be nil at any point. If a variable might become nil, you should declare it as weak.
Notice that in 'propertyWithClosure' the capture list precedes the closure’s argument list. In our 'someClosureProperty', we don't have an argument list, so we add our capture list to precede the 'in' keyword.
*/
class SomeClass {
var someProperty = "Hello world"
@lazy var propertyWithClosure: String = {
[unowned self] () -> String in
// do some stuff
return self.someProperty + ", nice to meet you"
}()
@lazy var someClosureProperty: () -> String = {
[unowned self] in
var modified = self.someProperty + ", how are you?"
return modified
}
}
/*
This way our closure won't hold a strong reference to self and our class can deinitialize properly. Use capture lists for any instances that are necessary to avoid retain cycles.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment