Functions
Functions can be created using the func
keyword, which takes a set of arguments and a body that can be invoked. The return
statement can be used to leave a function:
> var shopping = [ "Milk", "Eggs", "Coffee", "Tea", ] > var costs = [ "Milk":1, "Eggs":2, "Coffee":3, "Tea":4, ] > func costOf(items:[String], costs:[String:Int]) -> Int { . var cost = 0 . for item in items { . if let cm = costs[item] { . cost += cm . } . } . return cost . } > costOf(shopping,costs) $R0: Int = 10
The return type of the function is specified after the arguments with an arrow (->
). If missing, the function cannot return a value; if present, the function must return a value of that type.
Functions with positional arguments can be called with parentheses, such as the costOf(shopping,costs)
call. If a function takes no arguments, then the parentheses are still required.
Note
The foo()
expression calls the function foo
with no arguments. The expression foo
is the function itself, so an expression such as let copyOfFoo = foo
results in a copy of the function; so copyOfFoo()
and foo()
have the same effect.
Named arguments
Swift also supports named arguments, which can either use the name of the variable or can be defined with an external parameter name. To modify the function to support calling with basket
and prices
as argument names, the following can be done:
> func costOf(basket items:[String], prices costs:[String:Int]) -> Int { . var cost = 0 . for item in items { . if let cm = costs[item] { . cost += cm . } . } . return cost . } > costOf(basket:shopping, prices:costs) $R1: Int = 10
This example defines external parameter names basket
and prices
for the function. The function signature is often referred to as costOf(basket:prices:)
and is useful when it may not be clear what the arguments are for (particularly if they are for the same type).
A shorthand is available to use the same external name as the parameter name, by prefixing it with a hash (#
). These are called shorthand external parameter names:
> func costOf(#items:[String], #costs:[String:Int]) -> Int { . var cost = 0 . for item in items { . if let cm = costs[item] { . cost += cm . } . } . return cost . } > costOf(items:shopping, costs:costs) $R2: Int = 10
Refactoring shorthand external parameter names will lead to API breakage. If it is necessary to change the name internally in a function, convert it from a shorthand name to a separate external and internal parameter name.
Optional arguments and default values
Swift functions can have optional arguments by specifying default values in the function definition. When the function is called and an optional argument is missing, the default value for that argument is used.
Note
Note that an optional argument is one that can be omitted in the function call, rather than a required argument that takes an optional value. This naming is unfortunate. It may help to think of these as default arguments rather than optional arguments.
A default parameter value is specified after the type in the function signature, with an equal sign (=
) and then the expression. The expression is re-evaluated each time the function is called without a corresponding value. Default arguments are implicitly named so that the hash (indicating a named argument) is superfluous and will generate warnings.
In the costOf
example, instead of passing the value of costs
each time, it could be defined with a default parameter as follows:
> func costOf(#items:[String], costs:[String:Int] = costs) -> Int { . var cost = 0 . for item in items { . if let cm = costs[item] { . cost += cm . } . } . return cost . } > costOf(items:shopping) $R3: Int = 10 > costOf(items:shopping, costs:costs) $R4: Int = 10
Note that in the first expression, the captured costs
variable is bound when the function is defined. If costs
is re-assigned at a later stage, then the function will not be updated.
Anonymous arguments
Swift requires that arguments with default values are named, as are arguments that are used in initializers for classes (which are covered in the Classes in Swift section in Chapter 3, Creating an iOS Swift App).
In some cases, this is unnecessary or unhelpful. To disable requiring a named argument for a parameter, the special value underscore (_
) can be used:
> func costOf(items:[String], _ costs:[String:Int] = costs) -> Int { . var cost = 0 . for item in items { . if let cm = costs[item] { . cost += cm . } . } . return cost . } > costOf(shopping) $R0: Int = 10 > costOf(shopping,costs) $R1: Int = 10
Multiple return values and arguments
So far, the examples of functions have all returned a single type. What happens if there is more than one return result from a function? In an object-oriented language, the answer is to return a class; however, Swift has tuples, which can be used to return multiple values. The type of a tuple is the type of its constituent parts:
> var pair = (1,2) pair: (Int, Int) ...
This can be used to return multiple values from the function; instead of just returning one value, it is possible to return a tuple of values.
Note
Swift also has in-out arguments, which will be seen in the Handling Errors section in Chapter 6, Parsing Networked Data.
Separately, it is also possible to take a variable number of arguments. A function can easily take an array of values with []
, but Swift provides a mechanism to allow calling with multiple arguments, using variadic functions. The last argument in a function signature can be variadic, which means that it has ellipses after the type. The value can then be used as an array in the function.
Taken together, these two features allow the creation of a minmax
function, which returns both the minimum and maximum from a list of integers:
> func minmax(numbers:Int...) -> (Int,Int) { . var min = Int.max . var max = Int.min . for number in numbers { . if number < min { . min = number . } . if number > max { . max = number . } . } . return(min,max) . } > minmax(1,2,3,4) $R0: (Int, Int) = { 0 = 1 1 = 4 }
The numbers:Int...
indicates that a variable number of arguments can be passed into the function. Inside the function, it is processed as an ordinary array; in this case, iterating through using a for...in
loop.
Note
The Int.max
constant represents the largest Int
value, and Int.min
is a constant representing the smallest Int
value. Similar constants exist for specific integral types, such as UInt8.max
and Int64.min
.
What if no arguments are passed in? If run on a 64 bit system, then the output will be as follows:
> minmax() $R1: (Int, Int) = { 0 = 9223372036854775807 1 = -9223372036854775808 }
This may not make sense for a minmax
function. Instead of returning an error value or a default value, the type system can be used. By making the tuple optional, it is possible to return a nil
value if it doesn't exist, or a tuple if it does:
> func minmax(numbers:Int...) -> (Int,Int)? { . var min = Int.max . var max = Int.min . if numbers.count == 0 { . return nil . } else { . for number in numbers { . if number < min { . min = number . } . if number > max { . max = number . } . } . return(min,max) . } . } > minmax() $R2: (Int, Int)? = nil > mimmax(1,2,3,4) $R3: (Int, Int)? = (0 = 1, 1 = 3) > var (minimum,maximum) = minmax(1,2,3,4)! minimum: Int = 1 maximum: Int = 4
Returning an optional value allows the caller to determine what should happen in cases where the maximum and minimum values are not present.
Tip
If a function does not always have a valid return value, use an optional type to encode that possibility into the type system.
Returning structured values
A tuple is an ordered set of data. The entries in the tuple are ordered, but it can quickly become unclear as to what data is stored, particularly if they are of the same type. In the minmax
tuple, it is unclear which value is the minimum and which is the maximum, and this can lead to subtle programming errors later on.
A structure is like a tuple, but with named values. This allows members to be accessed by name instead of by position, leading to fewer errors and greater transparency. Named values can be added to tuples as well. In essence, tuples with named values are anonymous structures.
Tip
Structs are passed in a copy-by-value manner, like tuples. If two variables are assigned the same struct or tuple, then changes to one do not affect the value of another.
A struct is defined with the keyword struct
and has variables or values in the body:
> struct MinMax { . var min:Int . var max:Int . }
This defines a MinMax
type, which can be used in place of any of the types seen so far. It can be used in the minmax
function to return a struct
instead of a tuple:
> func minmax(numbers:Int…) -> MinMax? { . var minmax = MinMax(min:Int.max, max:Int.min) . if numbers.count == 0 { . return nil . } else { . for number in numbers { . if number < minmax.min { . minmax.min = number . } . if number > minmax.max { . minmax.max = number . } . } . return minmax . } . }
The struct
is initialized with a type constructor; if MinMax()
is used, then the default values for each of the structure members are used (based on the structure definition), but these defaults can be overridden explicitly if desired, with MinMax(min:-10,max:11)
. For example, if the MinMax
struct is defined as struct MinMax { var min:Int = Int.max; var max:Int = Int.min }
, then MinMax()
would return a structure with the appropriate maximum and minimum values filled in.
Note
When a structure is initialized, all the fields must be assigned. They can be passed in as named arguments in the initializer, or specified in the structure definition.
Swift also has classes; these are covered in the Swift classes section in the next chapter.