Book Image

Swift Essentials - Second Edition

By : Alex Blewitt
Book Image

Swift Essentials - Second Edition

By: Alex Blewitt

Overview of this book

Swift was considered one of the biggest innovations last year, and certainly with Swift 2 announced at WWDC in 2015, this segment of the developer space will continue to be hot and dominating. This is a fast-paced guide to provide an overview of Swift programming and then walks you through in detail how to write iOS applications. Progress through chapters on custom views, networking, parsing and build a complete application as a Git repository, all by using Swift as the core language
Table of Contents (17 chapters)
Swift Essentials Second Edition
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Preface
Index

Functions


Functions can be created using the func keyword, which takes a set of arguments and a body of statements. 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 ci = costs[item] {
.       cost += ci
.     }
.   }
.   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.

Note

The underscore (_) on the front of the costs parameter is required to avoid it being a named argument. The second and subsequent arguments in Swift functions are implicitly named. To ensure that it is treated as a positional argument, the _ before the argument name is required.

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.

The foo() expression calls the foo function with no argument. The foo expression represents the function itself, so an expression, such as let copyOfFoo = foo, results in a copy of the function; as a result, 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 ci = costs[item] {
.       cost += ci
.     }
.   }
.   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 of the same type).

Optional arguments and default values

Swift functions can have optional arguments by specifying default values in the function definition. When the function is called, if an optional argument is missing, the default value for that argument is used.

Note

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 equals (=) and then the expression. This expression is re-evaluated each time the function is called without a corresponding argument.

In the costOf example, instead of passing the value of costs each time, it could be defined with a default parameter:

> func costOf(items items:[String], costs:[String:Int] = costs) -> Int {
.   var cost = 0
.   for item in items {
.     if let ci = costs[item] {
.       cost += ci
.     }
.   }
.   return cost
. }
> costOf(items:shopping)
$R2: Int = 10
> costOf(items:shopping, costs:costs)
$R3: Int = 10

Please note that the captured costs variable is bound when the function is defined.

Note

To use a named argument as the first parameter in a function, the argument name has to be duplicated. Swift 1 used a hash (#) to represent an implicit parameter name, but this was removed from Swift 2.

Guards

It is a common code pattern for a function to require arguments that meet certain conditions before the function can run successfully. For example, an optional value must have a value or an integer argument must be in a certain range.

Typically, the pattern to implement this is either to have a number of if statements that break out of the function at the top, or to have an if block wrapping the entire method body:

if card < 1 || card > 13 {
  // report error
  return
}

// or alternatively:

if card >= 1 && card <= 13 {
  // do something with card
} else {
  // report error
}

Both of these approaches have drawbacks. In the first case, the condition has been negated; instead of looking for valid values, it's checking for invalid values. This can cause subtle bugs to creep in; for example, card < 1 && card > 13 would never succeed, but it may inadvertently pass a code review. There's also the problem of what happens if the block doesn't return or break; it could be perfectly valid Swift code but still include errors.

In the second case, the main body of the function is indented at least one level in the body of the if statement. When multiple conditions are required, there may be many nested if statements, each with their own error handling or cleanup requirements. If new conditions are required, then the body of the code may be indented even further, leading to code churn in the repository even when only whitespace has changed.

Swift 2 adds a guard statement, which is conceptually identical to an if statement, except that it only has an else clause body. In addition, the compiler checks that the else block returns from the function, either by returning or by throwing an exception:

> func cardName(value:Int) -> String {
.   guard value >= 1 && value <= 13 else {
.     return "Unknown card"
.   }
.   let cardNames = [11:"Jack",12:"Queen",13:"King",1:"Ace",]
.   return cardNames[value] ?? "\(value)"
. }

The Swift compiler checks that the guard else block leaves the function, and reports a compile error if it does not. Code that appears after the guard statement can guarantee that the value is in the 1...13 range without having to perform further tests.

The guard block can also be used to perform optional binding; if the guard condition is a let assignment that performs an optional test, then the code that is subsequent to the guard statement can use the value without further unwrapping:

> func firstElement(list:[Int]) -> String {
.   guard let first = list.first else {
.     return "List is empty"
.   }
.   return "Value is \(first)"
. }

As the first element of an array is an optional value, the guard test here acquires the value and unwraps it. When it is used later in the function, the unwrapped value is available for use without requiring further unwrapping.

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 of 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 a variadic parameter, which is denoted as an ellipses (…) after the type. The value can then be used as an array in the function.

Note

Swift 1 only allowed the variadic argument as the last argument; Swift 2 relaxed that restriction to allow a single variadic argument to appear anywhere in the function's parameters.

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… argument 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

Int.max is a constant representing the largest Int value, and Int.min is a constant representing the smallest Int value. Similar constants exist for other 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:

> 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
> minmax(1,2,3,4)
$R3: (Int, Int)? = (0 = 1, 1 = 4)
> 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 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 not clear which value is the minimum and which value is the maximum, and this can lead to subtle programming errors later on.

A structure (struct) 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 values of another.

A struct is defined with the struct keyword 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 that are 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 initializer; if MinMax() is used, then the default values for each of the structure types are given (based on the structure definition), but these 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() will return a structure with the appropriate minimum and maximum values filled in.

Note

When a structure is initialized, all the non-optional 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.

Error handling

In the original Swift release, error handling consisted of either returning a Bool or an optional value from function results. This tended to work inconsistently with Objective-C, which used an optional NSError pointer on various calls that was set if a condition had occurred.

Swift 2 adds an exception-like error model, which allows code to be written in a more compact way while ensuring that errors are handled accordingly. Although this isn't implemented in quite the same way as C++ exception handling, the semantics of the error handling are quite similar.

Errors can be created using a new throw keyword, and errors are stored as a subtype of ErrorType. Although swift enum values (covered in Chapter 3, Creating an iOS Swift App) are often used as error types, struct values can be used as well.

Exception types can be created as subtypes of ErrorType by appending the supertype after the type name:

> struct Oops:ErrorType {
.   let message:String
. }

Exceptions are thrown using the throw keyword and creating an instance of the exception type:

> throw Oops(message:"Something went wrong")
$E0: Oops = {
  message = "Something went wrong"
}

Note

The REPL displays exception results with the $E prefix; ordinary results are displayed with the $R prefix.

Throwing errors

Functions can declare that they return an error using the throws keyword before the return type, if any. The previous cardName function, which returned a dummy value if the argument was out of range, can be upgraded to throw an exception instead by adding the throws keyword before the return type and changing the return to a throw:

> func cardName(value:Int) throws -> String {
.   guard value >= 1 && value <= 13 else {
.     throw Oops(message:"Unknown card")
.   }
.   let cardNames = [11:"Jack",12:"Queen",13:"King",1:"Ace",]
.   return cardNames[value] ?? "\(value)"
. }

When the function is called with a real value, the result is returned; when it is passed an invalid value, an exception is thrown instead:

> cardName(1)
$R1: String = "Ace"
> cardName(15)
$E2: Oops = {
  message = "Unknown card"
}

When interfacing with Objective-C code, methods that take an NSError** argument are automatically represented in Swift as methods that throw. In general, any method whose arguments ends in NSError** is treated as throwing an exception in Swift.

Note

Exception throwing in C++ and Objective-C is not as performant as exception handling in Swift because the latter does not perform stack unwinding. As a result, exception throwing in Swift is equivalent (from a performance perspective) to dealing with return values. Expect the Swift library to evolve in the future towards a throws-based means of error detection and away from Objective-C's use of **NSError pointers.

Catching errors

The other half of exception handling is the ability to catch errors when they occur. As with other languages, Swift now has a try/catch block that can be used to handle error conditions. Unlike other languages, the syntax is a little different; instead of a try/catch block, there is a do/catch block, and each expression that may throw an error is annotated with its own try statement:

> do { 
.   let name = try cardName(15)
.   print("You chose \(name)")
. } catch {
.   print("You chose an invalid card")
. }

When the preceding code is executed, it will print out the generic error message. If a different choice is given, then it will run the successful path instead.

It's possible to capture the error object and use it in the catch block:

. } catch let e {
.   print("There was a problem \(e)")
. }

Tip

The default catch block will bind to a variable called error if not specified

Both of these two preceding examples will catch any errors thrown from the body of the code.

Note

It's possible to catch explicitly based on type if the type is an enum that is using pattern matching, for example, catch Oops(let message). However, as this does not work for struct values, it cannot be tested here. Chapter 3, Creating an iOS Swift App introduces enum types.

Sometimes code will always work, and there is no way it can fail. In these cases, it's cumbersome to have to wrap the code with a do/try/catch block when it is known that the problem can never occur. Swift provides a short-cut for this using the try! statement, which catches and filters the exception:

> let ace = try! cardName(1)
ace: String = "Ace"

If the expression really does fail, then it translates to a runtime error and halts the program:

> let unknown = try! cardName(15)

Fatal error: 'try!' expression unexpectedly raised an error: Oops(message: "Unknown card")

Tip

Using try! is not generally recommended; if an error occurs then the program will crash. However, it is often used with user interface codes as Objective-C has a number of optional methods and values that are conventionally known not to be nil, such as the reference to the enclosing window.

A better approach is to use try?, which translates the expression into an optional value: if evaluation succeeds, then it returns an optional with a value; if evaluation fails, then it returns a nil value:

> let ace = try? cardName(1)
ace: String? = "Ace"
> let unknown = try? cardName(15)
unknown: String? = nil

This is handy for use in the if let or guard let constructs, to avoid having to wrap in a do/catch block:

> if let card = try? cardName(value) {
.   print("You chose: \(card)")
. }

Cleaning up after errors

It is common to have a function that needs to perform some cleanup before the function returns, regardless of whether the function has completed successfully or not. An example would be working with files; at the start of the function the file may be opened, and by the end of the function it should be closed again, whether or not an error occurs.

A traditional way of handling this is to use an optional value to hold the file reference, and at the end of the method if it is not nil, then the file is closed. However, if there is the possibility of an error occurring during the method's execution, there needs to be a do/catch block to ensure that the cleanup is correctly called, or a set of nested if statements that are only executed if the file is successful.

The downside with this approach is that the actual body of the code tends to be indented several times each with different levels of error handling and recovery at the end of the method. The syntactic separation between where the resource is acquired and where the resource is cleaned up can lead to bugs.

Swift has a defer statement, which can be used to register a block of code to be run at the end of the function call. This block is run regardless of whether the function returns normally (with the return statement) or if an error occurs (with the throw statement). Deferred blocks are executed in reverse order of execution, for example:

> func deferExample() {
.   defer { print("C") }
.   print("A")
.   defer { print("B") }
. }
> deferExample()
A
B
C

Please note that if a defer statement is not executed, then the block is not executed at the end of the method. This allows a guard statement to leave the function early, while executing the defer statements that have been added so far:

> func deferEarly() { 
.   defer { print("C") } 
.   print("A") 
.   return 
.   defer { print("B") } // not executed
. }    
> deferEarly()
A
C