In this tutorial we cover the following topics
In short, Swift functions can be characterized as follow:
- Each function parameter has both an argument label and parameter name.
- We write an argument label before the parameter name, separated by a space.
- The argument label is used when calling the function.
- The parameter name is used in the implementation of the function.
- By default, parameters use their parameter name as their argument label.
- All parameters must have use unique names.
- It is possible for multiple parameters to have the same argument label.
- If we don't want to use an argument label for a parameter, an underscore character must be used as label for that parameter.
- If we use an underscore character, we cannot use a parameter name as an argument label.
- If a parameter has an argument label, the argument must be labeled when we call the function.
- Although arguments have their labels, we cannot change arguments order.
- In-out parameters cannot have default values.
- Variadic parametes (parameters which accepts zero or more values) cannot be marked as in-out.
Let's start our tests...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// // main.swift // FunctionsGeneralInfo // // Created by Piotr Fulmański on 2017.03.31. // Copyright © 2017 Piotr Fulmański. All rights reserved. // import Foundation func functionSimplestForm(){ print("functionSimplestForm") } functionSimplestForm() // or func functionSimplestFormVersion2() -> Void { print("functionSimplestFormVersion2") } func functionWithoutParameters() -> String { return "noParameters" } print(functionWithoutParameters()) func functionWithOneParameter(parameter1: String) -> String { return "Parameter value: " + parameter1 } // Missing argument label 'parameter1:' in call when parameter name omited print(functionWithOneParameter(parameter1: "foo")) func functionWithMultipleParameters(parameter1: String, parameter2: String) -> String { return "Parameter value: " + parameter1 + " : " + parameter2 } print(functionWithMultipleParameters(parameter1: "foo", parameter2: "bar")) |
Results are
functionSimplestForm
functionSimplestFormVersion2
noParameters
Parameter value: foo
Parameter value: foo : bar
Let's continue...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// In this code we show // - (optional) tuple return // - guard usage func functionWithMultipleReturnValues(values: [Int]) -> (maxValue: Int, maxPos: Int)?{ guard values.count > 0 else { return nil } var maxValue: Int = values[0] var maxPos: Int = 0 for index in 0...values.count-1 { if values[index] > maxValue { maxValue = values[index] maxPos = index } } return (maxValue, maxPos) } var result = functionWithMultipleReturnValues(values: []) if result != nil { // With the following // print("maxValue: \(result?.maxValue), maxPos: \(result?.maxPos)") // Xcode 8.3 print the warning: // String interpolation produces a debug description for an optional valie; did you mean to make it explicit? // One solution to silence the warning is to use suggested print("maxValue: \(String(describing: result?.maxValue)), maxPos: \(String(describing: result?.maxPos))") // second print("maxValue: \(result?.maxValue as Int?), maxPos: \(result?.maxPos as Int?)") } else { print("No return value") } result = functionWithMultipleReturnValues(values: [1, 5, 2, 7, 9, 4, 3, 0, 8, 6]) if result != nil { print("maxValue: \(String(describing: result?.maxValue)), maxPos: \(String(describing: result?.maxPos))") } else { print("No return value") } |
No return value
maxValue: Optional(9), maxPos: Optional(4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func functionLabelsTest(argumentLabel parameterName: String, parameterWithDefaultLabel: String, _ parameterWithNoArgumentLabel: String, stupidLabel parameter1: String, stupidLabel parameter2: String) { print(parameterName + ":" + parameterWithDefaultLabel + ":" + parameterWithNoArgumentLabel + ":" + parameter1 + ":" + parameter2) } functionLabelsTest(argumentLabel: "first", parameterWithDefaultLabel: "second", "third", stupidLabel: "fourth", stupidLabel: "fifth") func fooFunction(noDefaultParameter: String, defaultParameter: String = "defaultValue") { return print("\(noDefaultParameter) \(defaultParameter)") } fooFunction(noDefaultParameter: "definedByUser") fooFunction(noDefaultParameter: "definedByUser", defaultParameter: "alsoDefinedByUser") |
first:second:third:fourth:fifth
definedByUser defaultValue
definedByUser alsoDefinedByUser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func average(_ values: Double...) -> Double { guard values.count > 0 else { return 0 } var sum = 0.0 for value in values { sum += value; } return sum/Double(values.count) } print(average(1.5, 1.5, 3, 4, 5)) // Should print 3.0 func swapInts(_ first: inout Int, _ second: inout Int){ let save = first first = second second = save } var firstToSwap = 3, secondToSwap = 5 print("before swap: \(firstToSwap) \(secondToSwap)") swapInts(&firstToSwap, &secondToSwap) print("after swap: \(firstToSwap) \(secondToSwap)") |
3.0
before swap: 3 5
after swap: 5 3
Program ended with exit code: 0
Every function in Swift has a specific function type consisting of the parameter types and the return type of the function.
For example, function defined as
1 2 3 |
func foo(){ // some code goes here } |
is of the type () -> Void
while function
1 2 3 |
func bar(_ a1:String, _ a2: String) -> String { // some code goes here } |
is of the type (String, String) -> String
.
A function type is used just like any other type.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var action: (Int, Int) -> Int // Function type func add (_ a: Int, _ b: Int) -> Int { return a + b } func sub (_ a: Int, _ b: Int) -> Int { return a - b } action = add print(action(3, 5)) |
8
In consequence we can use function type as a parameter type for another function.
1 2 3 4 5 |
func doSomethingWithTwoInts(_ int1: Int, _ int2: Int, _ action: (Int, Int) -> Int) { print(action(int1, int2)) } doSomethingWithTwoInts(3, 5, add) |
8
where
add
is a previously defined function.
An interesting question is: How we can use a function type as the return type of another function? An answer: As any other existing type.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func selectAction(_ decision: String = "add") -> ((Int, Int) -> Int)? { switch decision { case "add": return add case "sub": return sub default: return nil } } // Use exclamation mark to avoid Swift compile error // Value of optional type '((Int, Int) -> Int)?' not unwraped; did you mean to use '!' or '??' print(selectAction()!(3, 5)) |
8
Swift allowed us to do more with functions: it can be seen as strange and awkward but we can define functions inside the bodies of other functions -- this way we have so called nested functions to distinguish from previously discussed global functions. Nested functions are hidden from the outside, but of course can be called by and used by enclosing function. An enclosing function can also return one of its nested fnctions which allow the nested function to be used in another scope.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func returnAFunction(toDo: String) -> ((Int, Int) -> Int)? { func add(componentL: Int, componentR :Int) -> Int { return componentL + componentR } func sub(minuend: Int, subtrahend: Int) -> Int { return minuend - subtrahend } switch toDo { case "add": return add case "sub": return sub default: return nil } } let someFunction = returnAFunction(toDo: "sub") print(someFunction!(4,5)) |
-1
Although closures are very close related with functions we will describe them in separate section because of the complexity and some new ideas. Closures are self-contained blocks of code that can be passed around and used in our code. Closures in Swift are similar to blocks in Objective-C (see Blocks section in Objective-C, Classes, part 2 tutorial) and to lambdas in other programming languages.
Closures are a technique for implementing lexically scoped name binding. We can think of it as a record storing a function together with an environment. Each variable that is used locally, but defined in an enclosing scope is associate with the value or storage location to which the name was bound when the closure was created. What is important, a closure, unlike a plain function, allows the function to access those captured variables through the closure's reference to them, even when the function is invoked outside their scope.
In short, closures are Swift’s anonymous functions.
To understand how it works, let's analyze the following example (this code is in JavaScript, so you can test it in browser's developer tools)
1 2 3 4 5 6 7 8 9 10 11 |
var f; function foo() { var x=1; f = function() { return ++x; }; var result = f(); console.log('inside foo, call to f(): ' + result); } foo(); console.log('call to f(): ' + f()); |
In line
1 |
f = function() { return ++x; }; |
an anonymous function is defined. Variable x
is defined outside of this function but is used inside. So, we can say, that this functon needs x
to work corectly. And there is nothing surprising that call
1 |
foo(); |
and in consequence
1 |
var result = f(); |
works and returns correct values
inside foo, call to f(): 2
call to f(): 3
The queestion is how it is possible that code
1 |
console.log('call to f(): ' + f()); |
also works?! Now we are outside of foo
function and we try to call f()
. How it could be that f()
knows value of the x
variable? This is when closures comes into play.
The assignment statement
1 |
f = function() { return ++x; }; |
''saves'' function but also all the outer environment needed to execute this function (in this case, x
variable).
Now consider a second example (again in JavaScript).
1 2 3 4 5 6 7 8 9 10 11 |
function referenceValue(x) { return function(y) { return y + x; }; } var moveFrom3By = referenceValue(3); var moveFrom5By = referenceValue(5); console.log(moveFrom3By(7)); console.log(moveFrom5By(9)); |
This code should print 10 and 14 as a result.
The above code defines a function referenceValue
with a parameter x
and a nested anonymous function. The nested function has access to x
, because is in the lexical scope of x
(note that x
is not local to this anonymous function). The function referenceValue
returns a closure containing
- the inner anonymous function, which adds the
y
value to thex
(reference) value, - and a reference to the variable
x
from this invocation ofreferenceValue
, so inner function will know where to find it when invoked.
Note that, as referenceValue
returns a function. This means that both moveFrom3By
and moveFrom5By
are of function type, so we can invoke moveFrom3By(7)
and moveFrom5By(9)
. Interesting is that while moveFrom3By
and moveFrom5By
refer to the same anonymous function, the associated environments differ, and invoking the closures will bind the name x
to two distinct variables with different values (3 and 5) in the two invocations, thus evaluating the function to different results (10 and 14).
General closure expression syntax has the following form:
1 2 3 |
{(parameters) -> returnType in expressions } |
This syntax is very closde to function syntax but without name (for this reason we say about anonymous functions). The parameters can be in-out, named variadic parameter and tuples. We cannot use default values for them. Using previously defined doSomethingWithTwoInts(::)
function we have
1 |
doSomethingWithTwoInts(5, 7, {(a: Int, b: Int) -> Int in return a + b}) |
Because it is always possible to infer the parameter and return type when passing a closure to a function or method as an inline closure expression, so we never need to write an inline closure in its full form when the closure is used as a function or method argument.
1 |
doSomethingWithTwoInts(5, 7, {a, b in return a + b}) |
What is more, single expression closure can implicitly return the result by ommiting the return
keyword from declaration.
1 |
doSomethingWithTwoInts(5, 7, {a, b in a + b}) |
Even this short expression can be shorthanded. In Swift a shorthand argument names $0
, $1
and so on can be used to refer to the values of the closure's first, second and so on arguments. If the argument is a tuple $0.0
is a key, and $0.1
is a value of the closure's first argument.
1 |
doSomethingWithTwoInts(5, 7, {$0 + $1}) |
If you think that there is no option we cen write shorter expression, you are wrong. We can use operator methods (see operator methods description -- to do --). From their definition Swift can infer the number and the types of arguments and return value.
Todo (id=no todo id): Prepare operator methods description
1 |
doSomethingWithTwoInts(5, 7, +) |
If the closure is a final argument of a function we are going to use, and the closure expression is long, we can write it as trailing closure. A trailing closure is a closure which is written after the function call's parentheses (but it is still an argument to the function).
1 |
doSomethingWithTwoInts(5, 7){$0 + $1} |
If the closure expression is provided as the function or method's only argument and we provide that expression as a trailing closure, we don't need to write a parentheses after the function or method's names when we call it.
In the example below we will show very basic but useful example of closure usage. Please have in mind that in the code below we will create two constants, which in some sense will behaves like variables. This is because functions and closures are reference types. So event if we define a constant related with a closure this relation is constant (doesn't change) but the closure this constant refers to is still able to change some captured variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func getIncrementer(incrementBy value: Int) -> () -> Int { var incrementerValue = 0; func incrementer() -> Int { incrementerValue += value return incrementerValue } return incrementer } let incrementerBy_plus_2 = getIncrementer(incrementBy: 2) let incrementerBy_minus_2 = getIncrementer(incrementBy: -2) print(incrementerBy_plus_2()) print(incrementerBy_minus_2()) print(incrementerBy_plus_2()) print(incrementerBy_minus_2()) print(incrementerBy_plus_2()) print(incrementerBy_minus_2()) |
2
-2
4
-4
6
-6
Other topics related to closures: escaping closures and autoclosures will be described in the separated advanced material.
Todo (id=no todo id): Describe escaping closures and autoclosures in separated material