7. Types

When it comes to types, Swift offers three distinct families. Its type system includes classes, which provide reference types, and enumerations and structures, which are both algebraic value types. Each provides unique strengths and features to support your development. This chapter surveys some of the key concepts used in the Swift language and explores how its types work in your applications.

Language Concepts

Before diving into type specifics, this section offers a quick review of some key concepts that Swift uses in its language design. The following terms support your understanding of Swift’s type system and enable you to better understand what each type brings to your programs.

Reference and Value Types

When selecting types, it’s critical to understand the distinction between value types and reference types. The big difference between the two is this: A reference type is not copied when assigned to a variable or passed as a parameter, but the data in a value type is.

Instead of copying the value, reference types keep track of a single data location in memory. The reference to that value is copied instead of the value itself. Two references that track the same location can access and modify that memory using different names. This is known as aliasing. A modification to one reference affects the contents of the other and vice versa. In contrast, two values initialized with the same semantic contents will not affect each other no matter how their data is updated.

This is best seen by example. In Swift, classes are reference types, and structures are value types. The following code establishes two constructs with identical member properties:

class MyClass {
    var name: String = "Tom"
    var age: Int = 35
}

struct MyStruct {
    var name: String = "Tom"
    var age: Int = 35
}

The following statements create an instance of each type and perform an assignment to this instance:

let classInstance1 = MyClass()
let classInstance2 = classInstance1
var structInstance1 = MyStruct()
var structInstance2 = structInstance1

In this example, the class instances are constants (using let), and the struct instances are variables (using var).

When working with reference types, you modify the data the reference points to, not the reference itself. Changes to the shared reference affect both variables:

classInstance1.age = 88
print(classInstance2.age) // 88
classInstance2.age = 55
print(classInstance1.age) // 55

In contrast, structs are value types. Because data is copied, updates to one leave the other instance unchanged and vice versa:

structInstance1.age = 88
print(structInstance2.age) // 35
structInstance2.age = 55
print(structInstance1.age) // 88


Note

Swift closures and functions are reference types.


Copy-and-Write-Back

Swift does not use pointers. When you call a method using an inout parameter, it uses a copy-and-write-back mechanism that avoids using references to an outer variable from inside a function. This approach means you need not dereference pointers, the way you would in C or Objective-C. The following code updates the value stored in myString using its inout string parameter:

func myFunction(inout myParameter: String) {
    myParameter = "Hello"
}

var myString = "My String"
myFunction(&myString)
print(myString) // "Hello"

Although the call uses an ampersand to indicate inout, it is not applying an address function or directly modifying the instance. You see this best by calling the same function using a computed property. This structure establishes a gettable/settable property that isn’t actually affected by assignment:

struct MyTest {
    var property: String {
        get {print("getting property"); return "Precomputed"}
        set {print("setting property")}
    }
}

When you construct a new instance, the property value remains unaffected by assignment:

var myTestInstance = MyTest()
myTestInstance.property // "Precomputed"
myTestInstance.property = "Try to update but can't"
print(myTestInstance.property) // It is still "Precomputed"

Copy-and-write-back means this structure works with the myFunction implementation in a way that it could not if you were using Objective-C and using direct memory updates. The following snippet calls the getter to populate myParameter, executes the code in myFunction, and then attempts to copy back the value using the setter:

myFunction(&myTestInstance.property) // calls getter and then setter
print(myTestInstance.property) // "Precomputed"

Chris Lattner writes on the old Apple DevForums, “An important design point of [S]wift is that we want it to provide strong support for long-term API evolution....[Y]ou can replace a stored property with a computed property without breaking clients. Being able to run getters and setters in the face of inout is a key part of this.”

Algebraic Data Types

An algebraic data type is a composite type formed from other types. That is, it’s a data type that can be constructed using primitive data types like strings, doubles, and so forth, and from other composite types, like CGPoint, GLKVector3, and CLLocationCoordinate2D. These come in two forms, which you may already be familiar with under the C-style names struct and union. Swift-specific algebraic data types are structures, which are product types, and enumerations, which are sum types:

Image A product type is a record. Its components consist of members that can be accessed by label. It retains a fixed order of its member properties no matter how the type is used. For example, a person struct might be composed of a string-type name and an integer-type age.

Image A sum type is a tagged union (also called a variant record). Its storage can be configured using several layouts, called cases. Only one case applies at any time, and that case defines a fixed order of member properties. An enumeration might have several cases, one of which, the person case, stores a string and an integer, a pet case that stores a species enumeration, and a rock case that stores no further data. Each case uses the same memory footprint, but the layout specifics vary depending on which case is active.

Other Terminology

As Swift uses several kinds of types, it’s hard to discuss the entire language using legacy concepts like “classes” and “objects.” Not all Swift types are classes, and not all Swift instances are objects. You can instantiate an enumeration and extend a struct. To address this, Swift offers new unifying terms to express the things you’re working with more concretely:

Image Classes, structures, and enumerations are constructs. You use these programmatic building blocks to build your code. Declarations establish new constructs. You create new instances by calling a constructor.

Image Constructs include members. These include stored and computed properties, initializers (and deinitializers), methods, and subscripts. When you work with protocols, they too refer to required members and use the same member terminology as the constructs that conform to and implement those requirements.

Image A static member maintains just one version for the entire type. You can create static methods and properties for all constructs, not just classes, even though many people think of them as “class methods” and “class properties.”

Image A construct uses initializers to set up an instance’s starting state and optional deinitializers to prepare an instance for deallocation. A convenience initialializer adds a simple entry point for creating constructs without requiring you to provide initial values for all properties or to derive properties from a related type. When working with classes, a designated initializer provides a primary initialization point; it is guaranteed to fully initialize all subclass and inherited properties. A required initializer must be added to subclasses or provided for protocol conformance for any type.

Image In Swift, a global function is a function. Within a type, it’s a method. A special class of functions called operators enables you to use symbols and phrases to apply behavior outside the standard method name and parenthesized argument list grammar.

Image You extend construct behavior by implementing extensions. Constructs also extend behavior by conforming to protocols with default implementations. This is called traits or mix-ins in other programming languages, although the phrases are not standard for Swift. Classes (and only classes) inherit behavior by subclassing. Conformance-based behaviors are included, not inherited.

Image Swift’s access control establishes the degree to which code is visible relative to the source file and modules they’re defined in. You declare an access level for constructs that participate in this system. Public access ensures that entities can be universally seen and used and specifies a public interface for your code. Using internal access creates module-wide visibility that’s not seen by any source files external to that module. You limit access on a source file-by-source file basis with a private access level, which hides implementation details beyond the scope of its source file. Marking a module as testable enables unit tests to bypass access levels and access any construct.

Enumerations

Enumerations contain sets of named distinct elements. A finger enumeration might include Thumb, Index, Middle, Ring, and Pinkie. A level enumeration could establish Low, Medium, and High values. Enumerations need not make sense or be conceptually complete in any way. The set of Shinbone, Sneeze, PixieDust, and Penguin is as valid as the enumeration Hearts, Moons, Stars, and Clovers. (To be technically accurate, I should note that over the years, Lucky Charms also included diamonds, horseshoes, balloons, trees, rainbows, and pots of gold, among other charms; the “default” charm set is non-canonical.) Enumerations need only be syntactically complete as some Swift features like switch statements rely on exhaustive cases.

Swift offers three styles of enumeration, with one extra variation that doesn’t quite meet the standard of a distinct style. These include basic enumerations, raw value enumerations, enumerations with associated values, and enumerations with indirect values. The following discussion introduces these styles and describe their roles in Swift development.

Basic Enumerations

A basic enumeration consists of a list of cases. The enum keyword declares the construct. The case declarations follow within braces. You can break each enumeration into individual case declarations or combine them together, as in this example:

enum Finger {case Thumb, Index, Middle, Ring, Pinkie}

Supply one case keyword per line and separate colinear cases with commas. Use uppercase type (Finger) and case (Thumb, Index, and so on) names.

Basic enumerations automatically implement Equatable, enabling you to use them with ==. Each case has an associated hash value. These values are not guaranteed to follow any particular algorithm, although you can pretty easily guess how they’re being generated. You cannot construct instances from those hash values:

Finger.Thumb.hashValue // 0
Finger.Middle.hashValue // 2

An enumeration does not report how many cases it has (in this example, five). A basic enumeration without associated or raw values does not provide constructors. You create new instances simply by listing the type name followed by the case (for example, Finger.Ring).

Swift’s type inference system enables you to skip the type name prefix in situations where the type itself is unambiguous. In such cases, you begin the enumeration with a raw period—for example, passing [.Index, .Ring, .Pinkie] to a parameter marked as an array of Finger.

Using Hash Values to Support Ranges

You can list several fingers in a single switch case, as in the following example:

let finger: Finger = .Index
switch finger {
case .Thumb, .Index, .Middle, .Ring: print("Not pinkie")
default: print("Pinkie")
}

You cannot, at least in raw Swift, apply ranges to your basic enumeration cases even though basic enumerations provide hash values that are intrinsically numeric and ordered; these are implementation details that can change in future Swift updates. The following code won’t compile in its current state:

switch finger {
case .Thumb...(.Ring): print("Not pinkie")
default: print("Pinkie")
}

The compiler complains that the range operator (...) cannot be applied to these arguments. While there are many solutions to this problem, the simplest involves conforming to the Comparable protocol, as pointed out by developer Davide De Franceschi. Add the conformance declaration operator implementation in Recipe 7-1, and the switch case begins to work.

Recipe 7-1 Adding Comparable Support to Basic Enumerations


extension Finger: Comparable {}
func <(lhs: Finger, rhs: Finger)-> Bool {
    return lhs.hashValue < rhs.hashValue }


In this approach, the minimum hash value is always 0, and the maximum hash value is unknown. It assumes that a semantic ordering of the values is reflected by the underlying hash value.

It’s always dangerous to reference language implementation details because they may change at a future date, but it’s compelling to understand how basic enumerations work, which is why I included this section. A basic enumeration that holds between 2 and 255 cases is just a byte with a name for each value. Longer enumerations require additional storage.

Using this knowledge, you can theoretically create a protocol that allows you to construct instances from hash values and enumerate members. (Don’t do this for production code.) The following snippet provides a few great wins for throw-away utilities that can save you some time:

protocol HashableEnumerable {}
extension HashableEnumerable {
    // Construct a byte and cast to the enumeration
    init?(fromHashValue hash: Int) {
        let byte = UInt8(hash)
        // "Warning: Breaks the guarantees of Swift's type system;
        // use with extreme care.  There's almost always a better way
        // to do anything." -- unsafeBitCast docs
        // Also, if you pass this an out-of-range value, it returns garbage
        self = unsafeBitCast(byte, Self.self)
    }

     // Enumerate members
     static func sequence() -> AnySequence<Self> {
        var index = 0
        return AnySequence {
            return anyGenerator {
                return Self(fromHashValue: index++)
            }
        }
    }
}

Reserve this approach for “fun” implementations; it isn’t suitable for App Store code. If you really need this behavior, use raw value enumerations instead, which are described in the following section.

To use the preceding approach, just declare conformance. Here are some examples of how you might conform and call the default implementations:

extension Finger: HashableEnumerable {}
Finger(fromHashValue: 2) // Middle
Finger(fromHashValue: 8) // nil
for item in Finger.sequence() { print(item) } // prints members

You might use this approach to create an array of random fingers:

extension Array {
    var randomItem: Element {
        return self[Int(arc4random_uniform(UInt32(count)))]
    }
}
var allFingers = Array(Finger.sequence())
let myFingers = (1...10).map({_ in allFingers.randomItem})
print("My random fingers array is (myFingers)")

And then iterate through the sequence, counting how many times each enumeration case appears in that array:

for eachFinger in Finger.sequence() {
    let count = myFingers.filter({$0 == eachFinger}).count
    print(eachFinger, count, separator: "   ")
}

As you can see, if you’re using enumerations to mark cases in any collection, this is an interesting way to explore them. As things stand in the current state of Swift, however, I recommend that you avoid this hashValue solution and use raw value enumerations, described next.

Raw Value Enumerations

You establish a raw value enumeration by appending a type name after the initial declaration. For example, you might use strings to create a greetings enumeration:

enum Greetings: String {
    case Hello = "Hello, Nurse!"
    case Goodbye = "Hasta la vista"
}

Each case uses the same type but offers a unique value, which you access through the rawValue member:

Greetings.Hello.rawValue // Hello, Nurse!
Greetings.Hello.hashValue // 0
Greetings.Goodbye.rawValue // Hasta la vista
Greetings.Goodbye.hashValue // 1

Unlike with basic enumerations, you can construct instances from raw values. Swift offers default initializers that take a rawValue argument:

Greetings(rawValue: "Hasta la vista")?.hashValue // 1
Greetings(rawValue: "No-op") // nil

The initializers are failable and return nil when you pass an invalid raw value.

In many programming languages, enumerated values are based on numbers. Swift supports numeric enumerations and can automatically populate its members. Here’s a simple example:

enum Dwarf: Int {
    case Bashful = 1, Doc, Dopey, Grumpy, Happy, Sleepy, Sneezy
}

Order matters. Only Bashful uses an explicit assignment. Swift infers the remaining values, starting with 2 and continuing from there. Although Dwarf uses 1 as its first value, the default count starts at 0, as you see in the following example:

enum Level: Int {case Low, Medium, High}
Level.High.rawValue // 2

However, you can set any value you like. This example offers some meaningless (but necessarily non-overlapping) values for each case:

enum Charm: Int {case Heart = 5, Moon = 30,
    Star = 15, Clover = 20}
Charm.Star.rawValue // 15

Raw Value Members and Sequences

Raw value enumerations use failable initializers, which means if you know the starting value for the first enumeration member and the raw value sequence pattern from one member to the next, there’s a predictable mechanism for creating generators, counting members, and so forth. Recipe 7-2 adds both a computed members property and a computed sequence. This recipe works only because Dwarf uses consecutive raw values, and the loop breaks at the first gap.

Recipe 7-2 Creating Sequences from Raw Value Enumerations


extension Dwarf {
    // The computed members property constructs an array on each call
    static var members: [Dwarf] {
        var items : [Dwarf] = []
        var index = 1 // initial value
        while let dwarf = Dwarf(rawValue: index++) {
            items.append(dwarf)
        }
        return items
    }

    // The simpler sequence implementation offers a better solution
    // for most iteration and member queries
    static func sequence() -> AnySequence<Dwarf> {
        return AnySequence {
            _ -> AnyGenerator<Dwarf> in
            var index = 1
            return anyGenerator {
                return Dwarf(rawValue: index++)
            }
        }
    }
}


Recipe 7-2’s approach is a lot safer than the private-implementation-specific hashValue initializer you read about earlier in this chapter. You must still account for individual enumeration quirks. Recall that the Dwarf enumeration started with an initial value of 1, which is why index starts at 1 for these methods. The declaration of index is internal to the AnySequence scope, ensuring that multiple calls to generate() don’t share an index.

This approach is most suited for raw value enumerations with a well-defined underlying sequence. With this understanding, you can then use this solution for any number of situations where you store members in collections, such as when you create and sort a deck of cards or iteratively roll members of a dice enumeration and want to evaluate their statistics.

Associated Values

Associated values enable you to create variable structures whose fields vary by enumeration case. With a true sum type or disjoint union, the storage in each enumeration instance changes based on the case in use. This approach allows the enumeration to use variable data layout while sharing the same memory footprint for each case.

In the following example, Clue.NextClue stores an integer and a string. Clue.End uses no additional storage:

enum Clue {
    case End
    case NextClue(Int, String)
}

You construct new instances either by direct reference (for no-value cases, Clue.End) or by passing a tuple of values (Clue.NextClue(5, "Go North 5 paces")). You access their associated values by using pattern matching, as in the following example:

let nextClue = Clue.NextClue(5, "Go North 5 paces")
if case Clue.NextClue(let step, let directions) = nextClue where step > 2 {
    print("You're almost there!. Next clue: (directions)")
}

Pattern matching enables you to reach within the enumeration to perform conditional assignments based on the case while accessing constituent associated values.

Although the preceding example does not use labels, labels can add helpful context to your cases. In the following example, labeled enumerated values help build stages for a turtle graphics interface:

enum TurtleAction {
    case PenUp
    case PenDown
    case Goto(x: Double, y: Double)
    case Turn(degrees: Double)
    case Forward(distance: Double)
}

var actions: Array<TurtleAction> = [.Goto(x:200.0, y:200.0), .PenDown,
    .Turn(degrees:90.0), .Forward(distance:100.0), .PenUp]

You can ignore labels when performing variable binding, as in the following example:

let action = actions[0]; var x = 0.0; var y = 0.0
if case let .Goto(xx, yy) = action {(x, y) = (xx, yy)}

If you want to perform the assignment in a single line and are willing to leave the scope for case other than .Goto, you can use guard as in the following example:

// Unwrap and use in primary scope, else return
guard case let .Goto(x, y) = action else {return}

This approach enables you to convert x and y to constants and use them directly in the primary scope.

Enums with associated values do not provide either hash values or raw values. They cannot and should not provide the sequence semantics that were discussed with respect to basic and raw value enumerations.

Indirect Values

Indirect enumerations enable you to create recursive data structures by storing associated values indirectly. You can declare a single case indirect or the enumeration as a whole. For example, you might create a linked list as in Recipe 7-3. This recipe creates a generic enumeration type with two cases. A Nil list offers a natural termination point. A Cons constructor adds a new value to the head of a list and points to a tail with the rest of the list. It’s a standard Intro to Data Structures implementation that adds a Swift flair with its indirect enumeration implementation.

Recipe 7-3 Using Indirect Values to Create a Recursive Linked List


enum List<T> {
    case Nil
    indirect case Cons(head: T, tail: List<T>)
    func dumpIt() {
        switch self {
            // This case uses Lisp-style notation
            // You can read more about Lisp's car, cdr, and cons
            // at https://en.wikipedia.org/wiki/CAR_and_CDR
            case .Cons(head: let car, tail: let cdr):
                print(car); cdr.dumpIt()
            default: break
        }
    }
}

// Construct a list and dump it
// Always adds the new value to the head
// so the constructed list is 5.4.3.2.1.Nil
var node = List<Int>.Nil
for value in [1, 2, 3, 4, 5] { node = List.Cons(head: value, tail: node) }
node.dumpIt()
    // As each value is appended to head, this implementation
    // prints its output in reverse numeric order


Switches

As in other languages, Swift’s switch statements enable you to branch code. Their code organization joins run-on conditionals into well-organized flows. Starting with a single value, you test that value against any number of conditions. When the condition succeeds, the switch construct executes associated statements.

The simplest switch statement is this:

switch value {
default: break
}

Unlike in Objective-C, you do not add parentheses around the value unless the value is actually a tuple and requires parentheses for syntax. The cases are unfortunately left-aligned, which is admittedly not visually appealing.

When you add a body to this default case, the switch executes that behavior, regardless of any value that is passed. A switch without case requests means “ignore the value and do whatever the default case says to do.” The body can appear to the right of the case, as in the following example, or indented beneath it:

switch value {
default: print("Always prints")
}

Branching

You branch a switch statement by adding case bodies. Follow each case keyword with one or more values, a colon, and then one or more statements, either on the same line or on following lines. When the case matches, the associated statements (and only those statements) execute. For example, the following statement adds two cases in addition to its default. When the integer value is 2 or 5, it prints messages specific to those values:

switch intValue {
case 2: print("The value is 2")
case 5: print("The value is 5")
default: print("The value is not 2 or 5")
}

switch statements must be exhaustive. If a value can pass through each case without matching, you must add a default clause, the catchall else branch of switches. The wildcard expression _ (that’s an underscore) adds another way to provide exhaustive coverage. It matches anything and everything. The following statement is functionally equivalent to the default-only example you saw at the start of this switch discussion:

switch value {
case _: print("Always prints")
}

Breaks

Use the break statement to create do-nothing cases or to short-circuit ongoing evaluation. This next code snippet showcases break, and it ignores any value except 4 and 6:

switch (intValue) {
case 1...3: break
case 4...6:
    if intValue == 5 {break}
    print("4 or 6")
default: break
}

Fallthroughs

Unlike in Objective-C, Swift switch-cases do not normally fall through. In Objective-C, the break keyword ends case evaluation and prevents other cases from executing—a common point for programming errors when you forget to add that critical “stop here” directive. Swift supports break, but it is not required for the most part. After matching, switch normally completes its execution at the start of the next case statement.


Note

The only place you really need break in switch statements is for no-op default cases, although in the current Swift (but please don’t do this), you can substitute () for the break keyword.


In the following example, the second case 2 never gets executed:

switch intValue {
case 2: print("The value is 2")
case 2: print("The value is still 2, by heavens!")
default: print("The value is not 2")
}

It is legal, however, to put it there, and this code will compile without erroring. The takeaway is that the first matching case is executed. This is especially important when you’re working with ranges and other complex cases that may include members with special handling requirements.

A fallthrough keyword continues execution into the next case, regardless of whether that next case is satisfied or not. When you pass a value of 5, this next switch statement prints out first 5 and then 5 or 6:

switch intValue {
case 5: print("5"); fallthrough
case 6: print("5 or 6")
case 7: print("7")
default: print("not 5, 6, or 7")
}

It’s an odd feature that I don’t use much except in one circumstance. When dealing with behavior that applies to a subset of case members as in the following, fallthrough enables special-handling cases to continue to more general cases:

switch myVariable {
    case needsSpecialHandling1, needsSpecialHandling2:
       // code specific to these cases
       fallthrough // handling continues in the following case
    case generalCases1, generalCases2, etc:
        // code that applies to both the general cases and
        // the code that needed special handling
    other cases and default: // blah blah
}

In such cases, call out the fallthrough behavior with commenting or consider alternative approaches. For example, you might use two successive switch statements or an if statement followed by the switch. Although you may be fully trained in the nuances of fallthrough, the people reading your code in the future might fail to recognize the oddities of this particular flow. Comment copiously regardless.


Note

fallthrough cannot be used when the following case binds any values in its pattern.


Complex Cases

It’s simple to combine cases by adding comma-delimited and range values to a case. switch uses the pattern match operator ~= to test case elements against the passed argument. This approach enables you to test against ranges as well as single values, as in the following example:

switch intValue {
case 5, 6: print("5 or 6")
case 7...12: print("7 - 12")
case 13...15, 17, 19...23:
    print("13, 14, 15, 17, or 19-23")
default: print("Not 5-15, nor 17, nor 19-23")
}

Tuples

switch matches tuples as well as individual values. This example tests a 2-ary tuple to check whether 3s appear within the tuple. The wildcard expressions in the second case match a single 3 to either position:

switch tup {
    case (3, 3): print("Two threes")
    case (_, 3), (3, _):
        print("At least one three")
    case _: print("No threes")
}

Swift enables you to use ranges inside tuple arguments, offering even more flexibility for your cases:

switch tup {
case (0...5, 0...5):
    print("Small positive numbers")
case (0...5, _), (_, 0...5):
    print("At least one small positive number")
default:
    print("No small positive numbers")
}

Pattern Matching with Value Bindings

The let and var keywords bind values to temporary variables. This is where switch statements move beyond simple if replacements and showcase their true power. This next example shows how binding and coercion can be combined with cases for a powerful effect:

enum Result<T> {
    case Error(ErrorType)
    case Value(Any)
    init (_ t: T) {
        switch t {
        case let error as ErrorType: self = .Error(error)
        default: self = .Value(t)
        }
    }
}

struct CustomError: ErrorType {let reason: String}
print(Result("Hello")) // Value("Hello")
print(Result(CustomError(reason: "Something went wrong")))
    // Error(CustomError(reason: "Something went wrong"))

The initializer tests its parameter t against the coerced ErrorType to try to construct an enumeration instance. If that assignment succeeds, the case continues to its body. If the cast fails, the result is wrapped in a general Value.

Although the var keyword is available, it’s rarely needed. Here’s a pointless example that uses var instead of let:

switch intValue {
case var value: print(++value)
}

where Clauses

Using where adds logic to case conditions beyond pattern matching. This example tests for membership in a range and tests whether the number is even or odd:

switch intValue {
case 0...20 where intValue % 2 == 0: print("Even number between 0 and 20")
case 0...20: print("Odd number between 0 and 20")
default: print("Some other number")
}

The where clause offers flexible logic. It lets you compare members of a tuple or test values against ranges or add any other condition your application demands:

switch tuple {
case (let x, let y) where x == y:
    print("tuple items are equal")
case (0, let y) where 0...6 ~= y:
    print("x is 0 and y is between 0 and 6")
default: break
}

You can also use where clauses in for loops, guards, and if-let statements. The following example prints out just the even numbers:

for num in 0...10 where num % 2 == 0 {print(num, "is even")}

Unwrapping Optional Enumerations

switch statements are amazing when you work with associated types. Here’s an example that I used until I replaced my implementation with a superior solution based on enumerations. Forgive me. It’s a poor example of how to represent Bezier elements but a good one on how to use optional members:

public struct BezierElement {
    public var elementType: CGPathElementType =
        CGPathElementType.CloseSubpath
    public var point: CGPoint?
    public var controlPoint1: CGPoint?
    public var controlPoint2: CGPoint?
}

The point, controlPoint1, and controlPoint2 fields are all optional. Not every element type requires every field. For example, a quadratic curve doesn’t use controlPoint2. The following switch statement emits Swift code by matching elements against a field tuple:

extension BezierElement {
    public var codeValue: String {
        switch (elementType, point, controlPoint1, controlPoint2) {
        case (CGPathElementType.CloseSubpath, _, _, _):
            return "path.closePath()"
        case (CGPathElementType.MoveToPoint, let point?, _, _):
            return "path.moveToPoint(CGPoint(x:(point.x), y:(point.y)))"
        case (CGPathElementType.AddLineToPoint, let point?, _, _):
            return "path.addLineToPoint(CGPoint(x:(point.x), y:(point.y)))"
        case (CGPathElementType.AddQuadCurveToPoint, let point?,
            let controlPoint1?, _):
            return "path.addQuadCurveToPoint(" +
                "CGPoint(x:(point.x), y:(point.y)), " +
                "controlPoint:" +
                "CGPoint(x:(controlPoint1.x), y:(controlPoint1.y)))"
        case (CGPathElementType.AddCurveToPoint, let point?,
            let controlPoint1?, let controlPoint2?):
            return "path.addCurveToPoint(" +
                "CGPoint(x:(point.x), y:(point.y)), " +
                "controlPoint1:" +
                "CGPoint(x:(controlPoint1.x), y:(controlPoint1.y)), " +
                "controlPoint2:" +
                "CGPoint(x:(controlPoint2.x), y:(controlPoint2.y)))"
        default: break
        }
        return "Malformed element"
    }
}

The let x? constructs are equivalent to let .Some(x) and unwrap optional fields. Not only does this switch statement match element type, all relevant field data is prepared for use in the case body. Best of all, each case won’t execute if the element is malformed and does not provide the required fields. Although you can use compound if-let statements to achieve the same result, using switch statements enables you to pattern match and add tests (using where) at the same time.

As this discussion showcases, switch statements are ridiculously powerful and remarkably useful. They are simply one of the best Swift language features available and a perfect companion to enumerations, both with and without associated types.

Embedding Values by Type

It’s common to use enumerations to handle type-specific heterogeneous storage, like the kinds you run into with JSON. This next example doesn’t use generics at all. It creates a container that stores an integer, a string, or a double or that indicates nil:

enum Container {
    case NilContainer
    case IntContainer(Int)
    case StringContainer(String)
    case DoubleContainer(Double)
}

This next example creates several Container instances and prints them out. This initial implementation is both long and wordy:

for c in [.NilContainer,
    .IntContainer(42),
    .StringContainer("Hello!"),
    .DoubleContainer(M_PI)] as [Container] {
        switch c {
        case .NilContainer: print("nil")
        case .DoubleContainer (let d): print("Double: (d)")
        case .StringContainer (let s): print("String: (s)")
        case .IntContainer (let i): print("Int: (i)")
        }
}

Raw values simplify this printing task. This next extension returns raw values extracted from the associated types. The return instances are typed Any?. This accommodates the heterogeneous mix-and-match nature of the extracted values:

extension Container {
    var rawValue: Any? {
        switch self {
        case .NilContainer: return nil
        case .DoubleContainer (let d): return d
        case .StringContainer (let s): return s
        case .IntContainer (let i): return i
        }
    }
}

Now, you can just print the contained value for each item. Since they’re returned as optionals, use nil coalescing to extract values and print a string for the nil case:

for c in [.NilContainer,
    .IntContainer(42),
    .StringContainer("Hello!"),
    .DoubleContainer(M_PI)] as [Container] {
        print(c.rawValue ?? "nil")
}

Despite improvements, the constructors remain cumbersome. Contrast with how you create an optional by passing a value:

Optional(23)

So why not extend Container to perform the same kind of inferred typing for construction? Here’s an extension that uses type casting and conditional binding to test for type matches to establish new instances:

extension Container {
    init() {self = .NilContainer}
    init<T>(_ t: T){
        switch t {
        case let value as Int: self = .IntContainer(value)
        case let value as Double: self = .DoubleContainer(value)
        case let value as String: self = .StringContainer(value)
        default: self = .NilContainer
        }
    }
}

Now creating each type is super simple. Just pass a value of any type and let the Container type build enumeration instances for you:

for c in [Container(), Container(63),
    Container("Sailor"), Container(M_PI_4)] {
        print(c.rawValue ?? "nil")}

There is still room to improve. If you extend the Container enumeration for CustomStringConvertible conformance, you can skip the awkward c.rawValue implementation:

extension Container: CustomStringConvertible {
    var description: String {let v = rawValue ?? "nil"; return "(v)"}
}
for c in [Container(), Container(63),
    Container("Sailor"), Container(M_PI_4)] {
        print(c)
}

The result is a really simple set of constructors and an even simpler way to print the values they produce.

Here’s one last trick before moving on. Using for-case iteration enables you to pluck out single enumeration members, as you see in the following example:

let items: [Any] = [1, 20, "Hello", 3.5, "Narf", 6, "Weasels"]
let containedItems = items.map({Container($0)})
for case .StringContainer(let value) in containedItems {print("String:", value)}
for case .IntContainer(let value) in containedItems {print("Int:", value)}
for case .DoubleContainer(let value) in containedItems {print("Double:", value)}

This approach offers a convenient way to operate on heterogeneous enumeration collections, selecting just the case you are interested in.

Option Sets

Option sets are lightweight sets of Boolean values. This feature received a welcome facelift in Swift 2. Option sets enable you to store and query independent flags, which Swift treats as a collection of orthogonal settable switches. Now, you just build sets using simple, readable constructs and then add and remove and test members as needed. Option sets are easier to use than before, and the code you work with is more readable and maintainable than ever.

Revisiting NS_OPTIONS

To understand OptionSetType, it helps to step back and look at Objective-C’s bit fields. In iOS 6 and OS X Mountain Lion, Apple introduced NS_OPTIONS along with its sibling NS_ENUM to replace typedef enum statements. These macros created a consistent way to build bit flags and enumerations, allowing the compiler to test for completeness in switch statements and ensuring that all cases were covered. They specify both the type and size of option and enumeration members.

Many Objective-C constructs use NS_OPTIONS to create bit flag sets. For example, you might want to specify the edges along which a view controller might extend:

typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
    UIRectEdgeNone   = 0,
    UIRectEdgeTop    = 1 << 0,
    UIRectEdgeLeft   = 1 << 1,
    UIRectEdgeBottom = 1 << 2,
    UIRectEdgeRight  = 1 << 3,
    UIRectEdgeAll    = UIRectEdgeTop | UIRectEdgeLeft |
        UIRectEdgeBottom | UIRectEdgeRight
} NS_ENUM_AVAILABLE_IOS(7_0);

In Objective-C, you or together, for example, the UIRectEdgeTop and UIRectEdgeLeft values provided by UIRectEdge. In addition to individual edges, this option set also offers both UIRectEdgeNone and a UIRectEdgeAll option that combines all edges into a single value. Swift imports NS_OPTIONS to conform to the OptionSetType protocol, which in Swift 2 provides simple easy-to-use construction and testing.

Building Enumerations

Until Swift 2, you tested options using low-level bitwise math. For example, you might have written the following test to determine whether the .Left flag was set in a UIRectEdge value:

if edgeOptions & .Left == .Left {...}

Now, in Swift 2, you replace that test with a membership function:

if edgeOptions.contains(.Left) {...}

The syntax looks and feels like you’re working with standard sets. You use square brackets and apply functions like contains, union, intersection, exclusive-or, and so forth. Instead of a no-flags raw value of 0, use an empty set []. Here’s what option membership looks like in Swift 2:

 // Create empty option set
var options : UIRectEdge = []
options.insert(.Left)
options.contains(.Left) // true
options.contains(.Right) // false

// Create another set and union
var otherOptions : UIRectEdge = [.Right, .Bottom]
options.unionInPlace(otherOptions)
options.contains(.Right) // true

The following call uses the options just built to set a view controller’s extended layout edges with respect to its parent’s navigation container:

UIViewController().edgesForExtendedLayout = options

Building Option Sets

Default protocol implementations are another powerful Swift 2 feature. These enable option sets to create fully working bit flag implementations requiring almost no work on your part. Consider Recipe 7-4, which builds a new option set. The Features struct declares OptionSetType conformance, adds a rawValue field, and then declares a simple list of static flags.

Recipe 7-4 Creating an Option Set


struct Features : OptionSetType {
    let rawValue : Int
    static let AlarmSystem = Features(rawValue: 1 << 0)
    static let CDStereo = Features(rawValue: 1 << 1)
    static let ChromeWheels = Features(rawValue: 1 << 2)
    static let PinStripes = Features(rawValue: 1 << 3)
    static let LeatherInterior = Features(rawValue: 1 << 4)
    static let Undercoating = Features(rawValue: 1 << 5)
    static let WindowTint = Features(rawValue: 1 << 6)
}


Unlike enumerations, which can self-populate with integers, option sets require you to code each raw value by hand, as you see in this example. This may change in future Swift updates, as this approach is vulnerable to typos. Start with a value of 1 (1 << 0) and increase from there with single bit-shift increments.

Every other feature is already built in. The default option set implementations mean you inherit initialization, set operations (union, intersect, exclusive or), membership management (contains, insert, remove), bitwise operations (unionInPlace, intersectInPlace, exclusiveOrInPlace), and more. In fact, all you need to do is define your flags, and you’re immediately ready to use them, as in the following examples:

var carOptions : Features = [.AlarmSystem, .Undercoating, .WindowTint]
carOptions.contains(.LeatherInterior) // false
carOptions.contains(.Undercoating) // true

Viewing Options

Although Swift 2’s mirroring automatically converts enumeration representations to printable descriptions, this convenience does not yet extend to OptionSetType instances. For example, this enumeration prints each member in a human-consumable fashion:

enum Colors {
    case Red, Orange, Yellow, Green, Blue, Indigo, Violet
}
print(Colors.Red) // prints Colors.Red

When you print the option sets created earlier in this section, they look something like this instead:

C.UIRectEdge(rawValue: 14)
Features(rawValue: 194)

Raw values are both ugly and not particularly helpful. I doubt this lack of print support will continue for long as the developer community files its bug reports with Apple. Until a fix appears, you can create custom representations by using the CustomStringConvertible protocol and adhering to successive-bit declarations.

The extension in Recipe 7-5 applies the new Swift 2 for-in-where construct to gather just those flags contained in the active set and returns a string representation of those members. It works by enumerating the static featureStrings array and testing each offset against the option set. This implementation assumes that your flags start at 1 << 0 and monotonically increase bit by bit from there.

Recipe 7-5 Printable Option Sets


extension Features : CustomStringConvertible {
    static var featureStrings = ["Alarm System", "CD Stereo",
        "Chrome Wheels", "Pin Stripes", "Leather Interior",
        "Undercoating", "Window Tint"]
    var description : String {
        return Features.featureStrings.enumerate().lazy
            .filter({(flag, _) in //test membership
                self.contains(Features(rawValue:1<<flag))})
            .map({$0.1}) // extract strings
            .joinWithSeparator(", ") // comma-delineated
    }
}
print(carOptions) // Alarm System, Undercoating, Window Tint


If you want the output to look more set-like, you can easily add square brackets around the comma-delineated string returned by the property extension.

Classes

In Swift, nearly all advantages that many developers mentally attribute to classes are available for both structures and enumerations. You can add methods and computed properties, implement type extensions, and conform to protocols. With all these improvements and added compiler efficiency for value types, why use classes? The answer boils down to reference semantics, Objective-C interoperability (Objective-C limits its type-level methods to classes), and the ability to subclass.

Reference semantics ensure that each instance occupies a unique location in memory and that the assignment always points to the same object. You use reference semantics for any item that might be used and modified by multiple owners. If it doesn’t make sense to create an entirely new instance on assignment (think views, data stores, resource managers, and so on), use classes to implement them. There is no other way to achieve this outside of classes.

Subclassing offers another important reason to prefer classes in Swift, although perhaps it is the weakest reason, especially when you distance yourself from Objective-C interoperability and view/view-controller specialization. With generics, protocols, enumerations, and extensions, many of the traditional reasons to use subclasses no longer apply to Swift.

Unlike subclassing, Swift’s newer technologies enable you to differentiate behavior based in the types in play (generics and protocols) or differentiate stored structure based on roles (enumerations and variable unions). Although there are still compelling reasons to create root classes and differentiate them through inheritance, the need for this particular pattern is diluted by the availability of other elegant approaches.

Optimization

Swift offers a way to combine reference semantics with computational efficiency. You achieve this by limiting subclassing behaviors for class types. The final keyword prevents a method, property, subscript, or even an entire class from being overridden by a subclass. Marking items with final enables the compiler to introduce performance improvements. These improvements depend on moving away from dynamic dispatch indirection, which requires a runtime decision to select which implementations to call. Removing indirect calls for methods and property access greatly improves your code performance.

Swift’s new whole-module optimization can automatically infer many final declarations by scanning and compiling an entire module at once. Its inference capabilities apply only to constructs and members marked as internal (it’s the default access modifier) and private. Class members that use public access must explicitly declare final to participate in this optimization.


Note

In classes, static is equivalent to class final.


Initializers

In development, initializers prepare language elements for use. They establish default values for stored properties and perform basic setup tasks. Swift enables you to initialize instances of classes, structures, and enumerations, but it is the initializers in classes that prove the trickiest to work with. That’s because classes adopt inheritance, enabling subclasses to inherit the features and initializers of their superclass.

When you build a subclass and add stored properties, that storage must be set up either with a default value or by using an initializer. A class that declares default values for each property does not need initializers beyond those it inherits. The following UIViewController subclass compiles without errors:

class MyControllerSubclass: UIViewController {
    var newVar: String = "Default"
}

Without that default assignment, the compiler complains that the subclass has no initializers. It’s up to you to build one or, typically in the case of view controllers, more.

Initialization Steps

A normal Swift instance initializer follows three basic steps:

1. You initialize any instance variables created by your class to a default starting state.

2. You call the superclass (if one exists) to initialize its instance variables. Yes, in Swift, you perform this action after you set up locally declared variables. This ordering ensures that the class storage is consistent at all hierarchical levels. You’ll have a chance to update these values after this primary initialization.

3. Perform any other setup duties required by your instance. Here is where you can override or tweak any inherited properties and call instance methods, and freely refer to self as a value.

Designated and Convenience Initializers

Swift (and Objective-C, and many other modern languages for that matter) uses two distinct initializer patterns. A designated initializer follows the three steps you just read through. It exhaustively establishes default values for all storage introduced by the class declaration. When you introduce a subclass, the subclass’s designated initializer sets its new properties first and then calls an initializer that walks up the superclass chain.

A convenience initializer provides a secondary construction utility. Although designated initializers should be few and functionally complete, a convenience initializer enables you to piggyback on designated initializers. They provide constructors that are shorter to call or provide an indirect initialization mechanism.

Initialization Rules

Swift’s documentation officially defines three base initializer rules:

1. Designated initializers in subclasses must call designated initializers in superclasses and must never call convenience ones.

2. Convenience initializers must call other initializers (designated or convenience) defined in the same class. Convenience initializers always delegate sideways and may not walk up the chain to a superclass.

3. Convenience initializers must end up redirecting to a designated initializer in the same class. Eventually the initializer chain from Rule 2 must end, and it must do so by calling a designated initializer declared in the same class as itself.

No matter how much you keep delegating sideways, eventually you end up at a designated initializer that walks up the chain. And because designated initializers cannot call convenience initializers, that chain stops wandering as soon as you hit the designated one. By Rule 1, you walk up the class tree in a straight path of designated initializers.

There are two more rules for automatic initializer inheritance that go like this:

4. If you provide default values for all new properties (or simply do not add new properties) and your subclasses don’t define designated initializers, the subclasses automatically inherit designated initializers from the superclass.

5. Any class that inherits or overrides all its superclass’s designated initializers inherits its convenience initializers.

Rules 4 and 5 work because the convenience initializers defined in the superclass always have a local designated initializer to end up at. Rule 2 ensures that the inherited initializers hop sideways through the child class. Because the possible initializer endpoints from Rules 1 and 3 are well defined, the inherited initializers are guaranteed to end up at a designated initializer in the child class.

Custom iOS view controllers inherit two designated initializers:

public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
public init?(coder aDecoder: NSCoder)

If you want to create a more convenient way to construct instances in code, you default to working through these initializers. Fortunately, the nib version accepts nils for both parameters, enabling you to establish fully code-based view controller creation:

class MyControllerSubclass: UIViewController {
    var newVar: String

    convenience init(newVar: String) {
        self.init(nibName: nil, bundle: nil)
        self.newVar = newVar
    }
}

Unfortunately, view controllers and let properties don’t blend well, especially if you want to share code between your initializers. You cannot call a shared setup routine because you’re not allowed to call methods and use self until after the preliminary tasks. The easiest solution is to use variables instead of constants. You might also consider using a custom structure to store constant properties, which allows you to use a common constructor. It’s ugly, but it does work. It’s generally easier to stick to variables with default values over constants to lower your initialization task overhead.

Building Convenience Initializers

Convenience initializers are, as their name suggests, convenient or handy. For example, you might pass a string that holds the path to a nib for a view class or provide an offset relative to the current time (timeIntervalSinceNow) in place of a canonical reference time (timeIntervalSinceReferenceDate) that an NSDate instance normally uses to set itself up. Convenience initializers provide entry points built around the typical API needs of a client rather than the internal structure of the class. They offer a developer-friendly way to create shortcut patterns for instance construction.

The convenience keyword marks initializers that provide these entry points. Rule 4 enables you to simplify the MyControllerSubclass implementation by providing a default value for newVar. In the following streamlined implementation, supplying defaults for all new stored properties means the subclass inherits its superclass’s initializers. The convenience initializer need only call a designated version and then perform any work based on the parameters supplied to it:

class MyControllerSubclass: UIViewController {
    var newVar: String = ""

    convenience init(newVar: String) {
        self.init(nibName: nil, bundle: nil)
        self.newVar = newVar
    }
}

Failable and Throwing Initializers

A failable initializer may produce a fully initialized instance, or it may return nil. This approach enables an initialization task to fail when it cannot establish a valid instance. A failable initializer is marked with a question mark (init?) and returns an optional value:

class MyFailableClass {
    init?() {
        // This initializer fails half the time
        if arc4random_uniform(2) > 0 {return nil}
    }
}

guard let test = MyFailableClass() else {return} // test is MyFailableClass?

The return nil command for failed construction is a bit of a semantic oddity. Initializers set up values; they don’t return them. You don’t return anything when the construction succeeds. At the time this book was being written, you needed to satisfy the init rules and initialize all properties before returning nil. Hopefully this issue will be addressed in a future Swift update.

Initializers can also throw, as you see in the following convenience initializer:

class MyThrowingClass {
    var value: String
    init() {
        value = "Initial value"
    }
    convenience init(string: String) throws {
        if string.isEmpty {throw Error()}
        self.init()
        value = string
    }
}

When working with throwing initializers, you must try to create them, just as you would with any other throwing functionality:

instance = try MyThrowingClass(string: "Initial StringValue")

Initializers fail because of missing resources (such as UIImage/NSImage initializers), because required services are not available, because of invalid parameter values, and for any other reason that prevents the class from setting up an instance properly. Prefer throwing initializers in cases where an instance cannot be properly constructed and use failable initializers in situations that don’t represent errors.

For example, choose init? when mapping a value to one of several enumeration variants and init throws for situations that should explain why an initialization failed. Cocoa provides numerous examples of the latter. For example, the following NSAttributedString constructor throws on failure:

init(URL url: NSURL,
   options options: [String : AnyObject],
   documentAttributes dict:
       AutoreleasingUnsafeMutablePointer<NSDictionary?>) throws

This is a Swift 2 redesign of a failable initializer call that previously populated an error when the initialization failed:

init?(
    fileURL url: NSURL!,
    options options: [NSObject : AnyObject]!,
    documentAttributes dict:
        AutoreleasingUnsafeMutablePointer<NSDictionary?>,
    error error: NSErrorPointer)

As a rule, combine strategies like guard or nil coalescing (that is, using the ?? operator) with failable init? initializers and try/throws with throwing ones. This enables you to ensure that only valid values move forward either with default backup values or through early exit.


Note

Each initializer signature must be unique. You cannot offer both a failable and a non-failable initializer using the same parameter labels and types.


Deinitializers

Use deinit to perform cleanup tasks before a class instance is deallocated. (Chapter 6, “Errors,” includes a discussion on using defer, which enables you to run similar tasks as you leave a scope.) Swift’s deinit support makes it simple to observe deallocation. You can throw a print statement into the implementation and watch when items go away:

deinit {
    print("Deallocating instance")
}

When you’re going to watch object life cycles, it helps to track which object you’re dealing with. Swift value types don’t have identifiers. There’s no “notion of identity” for structs, enums, functions, and tuples, since you create new items on each assignment. For classes, which are reference types rather than value types, you can use ObjectIdentifier to fetch a unique representation of each instance:

class MyClass {
    init () {
        print("Creating instance",
            ObjectIdentifier(self).uintValue)
    }

    deinit {
        print("Deallocating instance",
            ObjectIdentifier(self).uintValue)
    }
}

This approach is handy for tracking when items are instantiated and deallocated. Here’s an example that jumps through hoops to force an item past its natural life:

class MyClass {
    let message = "Got here"

    func test() {
        let ptr = unsafeBitCast(self, UnsafeMutablePointer<Void>.self)
        let handler = HandlerStruct(ptr: ptr)

        let numberOfSeconds = 2.0
        let delayTime = dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(numberOfSeconds * Double(NSEC_PER_SEC)))

        dispatch_after(delayTime,
            dispatch_get_main_queue()) {
                [self] // capture self
                handler.unsafeFunc()
        }
    }

    init () {
        print("Creating instance",
            ObjectIdentifier(self).uintValue)
    }

    deinit {
        print("Deallocating instance",
            ObjectIdentifier(self).uintValue)
        CFRunLoopStop(CFRunLoopGetCurrent())
    }
}

MyClass().test()
CFRunLoopRun()
print("Done")

When you run this example, you see the instance’s lifetime from start to end.

Property Observers

You monitor changes and add failsafe behavior to properties by implementing observers in your Swift types. Swift offers two variations of these: willSet, which is called just before a property updates, and didSet, which is called just after.

You use willSet to prepare for the new assignment, often cleaning things up. For example, you might remove gesture recognizers before assigning a new view property:

var view: UIView {
    willSet {
            if let view = view {
                view.removeGestureRecognizer(myTapGestureRecognizer)
            }
        }
    ...
}

In a similar vein, you might add a recognizer after a view is assigned:

didSet {
  if let view = view {
      view.addGestureRecognizer(myTapGestureRecognizer)
  }
}

didSet offers the opportunity to check for boundary conditions and to clamp values to appropriate settings:

public class ImageTiler : NSObject {
    public var hcount : Int = 1 {didSet {if hcount < 1 {hcount = 1}}}
    public var vcount : Int = 1 {didSet {if vcount < 1 {vcount = 1}}}
    ...
}

This example potentially updates the values of both properties. When you assign a property in its own observer, the new value replaces the one that was just set, but you do not trigger another round of observations.


Note

willSet and didSet, respectively, provide newValue and oldValue constant parameters. You can override these parameter names, although I cannot imagine why you’d want to.


Getters/Setters and Access-Level Modifiers

By default, a getter or a setter is established at the same access level as its parent property. Typically if you can read a property publicly, you can also write it publicly. You override this default behavior by adding private and internal keywords specific to get and set. Here’s an example of a public structure with two public properties. These level modifiers create nuanced read-only access. The status property uses a private access level modifier. It can be updated only within the same source code file. The result property’s set modifier is internal, so it can be modified within a module but not by external clients:

public enum Status {case Okay, Error}
public struct Attempt {
    public private(set) var status: Status = .Okay
    public internal(set) var result: String = "Nothing"
    public init() {}
    mutating public func execute() {
        let succeeded = arc4random_uniform(2) > 0
        switch succeeded {
        case true:
            status = .Okay
            result = "Attempt Succeeded"
        case false:
            status = .Error
            result = "Attempt Failed"
        }
    }
}


Note

Jared Sinclair wrote an excellent overview of Swift and the protected extension design pattern, which enforces overrides by subclasses. See http://blog.jaredsinclair.com/post/93992930295/for-subclass-eyes-only-swift.


Extensions and Overrides

Swift’s ability to extend types and, in the case of classes, override methods established by superclasses, introduces enormous development flexibility and power. Use the override keyword to mark any method that replaces a parent implementation. A compiler check for the keyword prevents you from replacing parent behavior by accident. Extensions add methods, initializers, subscripts, and computed properties to an existing type, letting you extend behavior beyond the original.

For example, consider Recipe 7-6. It extends CGRect, a workhorse structure for UIKit views. This implementation adds initializers for Swift doubles and integers, allows rectangles to be zeroed or centered around points, and provides a way to center one rectangle in another. These kinds of instant wins are emblematic of how joyful working with Swift can be.

Recipe 7-6 Extending CGRect


public extension CGRect {
    // Init with size
    public init(_ size: CGSize) {
        self.init(origin:CGPoint.zero, size:size)
    }

    // Init with origin
    public init(_ origin: CGPoint) {
        self.init(origin:origin, size:CGSize.zero)
    }

    // Init with x, y, w, h
    public init(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) {
        self.init(x:x, y:y, width:width, height:height)
    }

    // Init with doubles
    public init(_ x: Double, _ y: Double, _ width: Double, _ height: Double) {
        self.init(x:x, y:y, width:width, height:height)
    }

    // Init with integers
    public init(_ x: Int, _ y: Int, _ width: Int, _ height: Int) {
        self.init(x:x, y:y, width:width, height:height)
    }

    // Move origin to zero
    public var zeroedRect: CGRect {return CGRect(size)}

    // Move to center around new origin
    public func aroundCenter(center: CGPoint) -> CGRect {
        let origin = CGPoint(x: center.x - size.width / 2.0,
            y: center.y - size.height / 2)
        return CGRect(origin:origin, size:size)
    }
}


Lazy Evaluation

I had a hard time deciding where to put a discussion of lazy in this book and eventually ended up placing it into this types chapter. Lazy evaluation means deferring computation until a value is actually needed. Its opposite is eager, which is not a keyword or an official Swift term.

Lazy Sequences

In Swift, several things can be lazy. Sequences can be lazy, for example, when the work to create each value in a generator is performed only when a consumer requests a new value. You can use the lazy collection property to convert a collection to a lazy sequence. The following example prints Fetching Word each time its mapped functionality runs, enabling you to see when each stage executes:

let words = "Lorem ipsum dolor sit amet".characters.split(
    isSeparator:{$0 == " "}).lazy.map{
    characters -> String in print("Fetching Word"); return String(characters)}
    // words is now a lazy map collection

If this is the entire bit of code, nothing prints. The lazy evaluation ensures that the map does not execute until needed. If you add a single request, Fetching Word prints once:

words.first // prints Fetching Word once

When you iterate through the entire collection, the print request executes before each value is returned:

for word in words {
    print(word) // prints Fetching Word before each word
}

Lazy Properties

Stored properties can be lazy. Their initial evaluation can be deferred until their first use, enabling you to bypass that computation entirely if the property is never used. A lazy property must be a variable; constants must be assigned a value before initialization completes.

Consider Recipe 7-7. In this example, the lazy keyword ensures that item is not constructed until after at least three seconds have passed. If you remove the lazy keyword, the value stored in item’s string drops to near zero.

Recipe 7-7 Deferring Initialization with lazy


var initialTime = NSDate()
struct MyStruct {
    lazy var item: String = "(-initialTime.timeIntervalSinceNow)"
}

var instance = MyStruct()
sleep(3)
print(instance.item) // for example, 3.01083701848984



Note

Stored static type properties are lazily initialized, just like global variables.


Wrap-up

From terminology to practicalities, this chapter has explored many of Swift’s type features. You read about structures, classes, and enumerations, as well as some of the mechanisms that support them and the trade-offs in functionality. Swift is inherently a safe, flexible, and extensible language, and its multifaceted type system helps support those goals.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.172.195