An Introduction to Swift: Part 2

In the first article of this introductory series on Swift, we talked about Swift's philosophy, took a first look at its syntax, and highlighted a few key differences with Objective-C. In this article, we continue our exploration of Swift's syntax. You'll also learn about optionals and see how memory management works in Swift.

1. Conditionals and Loops

If

If statements are identical in Swift and Objective-C with the exception of two subtle differences:

  • parentheses around the condition variable are optional
  • curly braces are required

These are about the only differences with if statements in Objective-C.

Ranges

As we saw in the first article, Swift includes two range operators ..< and ... to specify a range of values. These two operators are the half-closed range operator and the closed range operator.

A half-closed range, such as 1..<5, represents the values 1, 2, 3, and 4, excluding 5. A closed range, such as 1...5, represents the values 1, 2, 3, 4, and includes 5.

Ranges can be used in for loops, array subscript, and even in switch statements. Take a look at the following examples.

Switch

Switch statements are more powerful in Swift than they are in Objective-C. In Objective-C,  the result of the expression of a switch statement needs to be of type integer and the values of each case statement should be a constant or a constant expression. This isn't true in Swift. In Swift, the case statements can be of any type, including ranges.

In Swift, switch has no break statements and it doesn't automatically fall through from one case to another. When writing a switch statement, care must be taken that all conditions are being handled by its case statements, failing to do so will result in a compiler error. A sure way to cover all conditions is to include a default case statement.

Here's an example of a switch statement with String cases:

In Swift, case statements don't fall through by default. This was a deliberate design decision to avoid common errors. If a specific case needs to fall through, you can use the fallthrough keyword to indicate this to the compiler.

It doesn't stop here. Swift adds two other features to switch, value bindings and the where clause. Value binding is used with the case let keywords to bind a constant with the matching case. The where clause adds an extra condition to the case statement using the where keyword.

These two concepts are better explained with examples. The following code block shows how value binding works.

The first case statement, case (let x, 0), will match the values where yaxis is equal to 0 and any value  for xaxis, and we bind xaxis to the constant x to be used inside the case statement.

Here's an example of the where clause in action.

2. Functions and Closures

Functions

Defining functions and closures is easy in Swift. Objective-C developers know them better as functions and blocks.

In Swift, function parameters can have default values, which is reminiscent of scripting languages, such as PHP and Ruby.

The syntax for functions is as follows:

To write a sayHello function that takes a parameter name of type String and returns a Bool when successful, we write the following:

To pass a default value for the name parameter, the function's implementation would look as follows:

A feature that is completely absent in Objective-C are tuples. In Swift, functions can return multiple values in the form of a tuple. Tuples are treated as a single variable, which means that you can pass it around just like a variable.

Tuples are very easy to use. In fact, we've already worked with tuples in the previous article when we enumerated a dictionary. In the next code snippet, the key/value pair is a tuple.

How are tuples used and how do you benefit from them? Let's take a look at another example. Let's modify the above sayHello function to return a boolean when successful as well as the resulting message. We do this by returning a tuple, (Bool, String). The updated sayHello function looks like this:

Tuples have been on the wish list of many Objective-C programmers for a long time.

Another cool feature of tuples is that we can name the returned variables. If we revisit the previous example and give names to the variables of the tuple, we get the following:

This means that instead of defining a separate constant for each return element of a tuple, we can access the returned tuple elements using dot notation as shown in the above example, status.success and status.greeting.

Closures

Closures in Swift are the same as blocks in Objective-C. They can be defined inline, passed as a parameter, or returned by functions. We use them exactly as we would use blocks in Objective-C.

Defining closures is also easy. Actually, a function is a special case of closures. So it's no wonder that defining a closure looks a lot like defining a function.

Closures are a first-class type, which means that they can be passed and returned by functions just like any other type, such as IntStringBool, etc. Closures are essentially code blocks that can be called later and have access to the scope in which they were defined.

Creating an nameless closure is as simple as wrapping a block of code in curly braces. The parameters and return type of the closure are separated from the closure's body with the in keyword.

Let's say we want to define a closure that returns true if a number is even, then that closure could look something like:

The isEven closure takes an Int as its single parameter and returns a Bool. The type of this closure is (number: Int) -> Bool, or (Int -> Bool) for short. We can call isEven anywhere in our code just like we would invoke a code block in Objective-C.

To pass a closure of this type as a parameter of a function, we use the closure's type in the function's definition:

In the above example, the verifier parameter of the verifyIfEven function is a closure that we pass to the function.

3. Classes & Structures

Classes

It's time to talk about the cornerstone of object-oriented programming, classes. Classes, as mentioned before, are defined in a single implementation file with a .swift extension. Property declarations and methods are all defined in that file.

We create a class with the class keyword followed by the name of the class. The class's implementation is wrapped in a pair of curly braces. As in Objective-C, the naming convention for classes is to use upper camel case for class names.

To create an instance of the Hotel class we write:

In Swift, there's no need to call init on objects as init is called automatically for us.

Class inheritance follows the same pattern as in Objective-C, a colon separates the class name and that of its superclass. In the following example, Hotel inherits from the BigHotel class.

As in Objective-C, we use dot notation to access an object's properties. However, Swift also uses the dot notation to invoke class and instance methods as you can see below.

Properties

Another difference with Objective-C is that Swift doesn't distinguish between instance variables (ivars) and properties. An instance variable is a property.

Declaring a property is just like defining a variable or a constant, using the var and let keywords. The only difference is the context in which they are defined, that is, the context of a class.

In the above example, rooms is an immutable value, a constant, set to 10 and fullRooms is a variable with an initial value of 0, which we can change later. The rule is that properties need to be initialized when they're declared. The only exception to this rule are optionals, which we'll discuss in a moment.

Computed Properties

The Swift language also defines computed properties. Computed properties are nothing more than fancy getters and setters that don't store a value. As their name indicates, they are computed or evaluated on the fly.

Below is an example of a computed property. I have changed the rooms property to a var for the rest of these examples. You'll find out why later.

Because the description property is read-only and only has a return statement, we can omit the get keyword and curly braces, and only keep the return statement. This is shorthand and that's what I'll be using in the rest of this tutorial.

We can also define read-write computed properties. In our class Hotel, we want an emptyRooms property that gets the number of empty rooms in the hotel, but we also want to update fullRooms when we set the emptyRooms computed property. We can do this by using the set keyword as shown below.

In the emptyRooms setter, the newValue constant is handed to us and represents the value passed to the setter. It's also important to note that computed properties are always declared as variables, using the var keyword, because their computed value can change.

Methods

We've already covered functions earlier in this article. Methods are nothing more than functions that are tied to a type, such as a class. In the following example we implement an instance method, bookNumberOfRooms, in the Hotel class we created earlier.

Initializers

The default initializer for classes is init. In the init function, we set the initial values of the instance that is created.

For example, if we would need a Hotel subclass with 100 rooms, then we would need an initializer to set the rooms property to 100. Remember that I previously changed rooms from being a constant to being a variable in the Hotel class. The reason is that we cannot change inherited constants in a subclass, only inherited variables can be changed.

Initializers can also take parameters. The following example shows you how this works.

Overriding Methods and Computed Properties

This is one of the coolest things in Swift. In Swift, a subclass can override both methods and computed properties. To do so, we use the override keyword. Let's override the description computed property in the CustomHotel class:

The result is that description returns the result of the superclass's description method with the string "Howdy!" appended to it.

What's cool about overriding methods and computed properties is the override keyword. When the compiler sees the override keyword, it checks wether the class's superclass implements the method that's being overridden. The compiler also checks if the properties and methods of a class are in conflict with properties or methods higher up the inheritance tree.

I don't know how many times a typo in an overridden method in Objective-C made me curse, because the code wasn't working. In Swift, the compiler will tell you exactly what's wrong in these situations.

Structures

Structures, defined with the struct keyword, are more powerful in Swift than they are in C and Objective-C. In C, structs define only values and pointers. Swift structs are just like C structs, but they also support computed properties and methods.

Anything you can do with a class, you can do with a structure, with two important differences:

  • structures don't support inheritance like classes do
  • structures are passed around by value while classes are passed by reference

Here are a few examples of structures in Swift:

4. Optionals

Solution To a Problem

Optionals are a new concept if you're coming from Objective-C. They solve a problem we all face as programmers. When we access a variable whose value we're not sure about, we usually return an indicator, known as a sentinel, to indicate that the returned value is a no-value. Let me illustrate this with an example from Objective-C:

In the above example, we are trying to find the position of @"B" in someString. If @"B" is found, its location or position is stored in pos. But what happens if @"B" isn't found in someString?

The documentation states that rangeOfString: returns an NSRange with location set to the NSNotFound constant. In the case of rangeOfString:, the sentinel is NSNotFound. Sentinels are used to indicate that the returned value is not valid.

In Cocoa, there are many uses of this concept, but the sentinel value differs from context to context, 0, -1, NULL, NSIntegerMax, INT_MAX, Nil, etc. The problem for the programmer is that she must remember which sentinel is used in which context. If the programmer isn't careful, she can mistake a valid value for a sentinel and vice versa. Swift solves this problem with optionals. To quote Brian Lanier "Optionals are the one sentinel to rule them all."

Optionals have two states, a nil state, which means the optional contains no value, and a second state, which means it holds a valid value. Think of optionals as a package with an indicator to tell you if the package's contents is valid or not.

Usage

All types in Swift can become an optional. We define an optional by adding a ? after the type declaration like so:

We assign a value to an optional's package just like we do with constants and variables.

Remember that optionals are like packages. When we declared let someInt: Int?, we defined an empty box with a value of nil. By assigning the value 10 to the optional, the box contains an integer that is equal to 10 and its indicator or state becomes not nil.

To get to the contents of an optional we use the ! operator. We must be sure that the optional has a valid value before unwrapping it. Failing to do so will cause a runtime error. This is how we access the value stored in an optional:

The above pattern is so common in Swift that we can simplify the above code block by using optional binding with the if let keywords. Take a look at the updated code block below.

Optionals are the only type that can take a nil value. Constants and variables cannot be initialized or set to nil. This is part of Swift's safety policy, all non-optional variables and constants must have a value.

5. Memory Management

If you remember, back when ARC was introduced we were using the strong and weak keywords to define object ownership. Swift also has a strong and weak ownership model, but it also introduces a new one, unowned. Let's take a look at each object ownership model in Swift.

strong

Strong references are the default in Swift. Most of the time, we own the object that we're referencing and we are the ones responsible for keeping the referenced object alive.

Since strong references are the default, there is no need to explicitly keep a strong reference to an object, any reference is a strong reference.

weak

A weak reference in Swift indicates that the reference points to an object that we're not responsible for keeping alive. It's mainly used between two objects that don't need the other to be around in order for the object to continue its life cycle.

There is one but, however. In Swift, weak references must always be variables with an optional type, because they are set to nil when the referenced object is deallocated. The weak keyword is used to declare a variable as weak:

unowned

Unowned references are new for Objective-C programmers. An unowned reference means that we're not responsible for keeping the referenced object alive, just like weak references.

The difference with a weak reference is that an unowned reference is not set to nil when  the object it references is deallocated. Another important difference with weak references is that unowned references are defined as a non-optional type.

Unowned references can be constants. An unowned object doesn't exist without its owner  and therefore the unowned reference is never nil. Unowned references need the unowned keyword before the definition of the variable or constant.

Conclusion

Swift is an amazing language that has a lot of depth and potential. It's fun to write programs with and it removes a lot of the boilerplate code we write in Objective-C to make sure our code is safe.

I highly recommend The Swift Programming Language, which is available for free in Apple's iBooks Store.

Tags:

Comments

Related Articles