O'Reilly logo

Extending Swift Value(s) to the Server by Robert Dickerson, David Ungar

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. A Swift Introduction

Swift supports several different programming paradigms. This chapter provides a brief overview of the parts of the Swift language that will be familiar to a Java or JavaScript programmer. Swift is not a small language, and this chapter omits many of its conveniences, including argument labels, shorthand syntax for closures, string interpolation, array and dictionary literals, ranges, and scoping attributes. Swift’s breadth lets you try Swift without changing your programming style while you master its basics. Later, when ready, you can exploit the additional paradigms it offers.

A beginning Swift developer may initially be overwhelmed by the cornucopia of features in the Swift language, since it gives you many ways to solve the same problem. But taking the time to choose the right approach can often catch bugs, shorten, and clarify your code. For instance, value types help prevent unintended mutation of values. Paradigms borrowed from functional programming such as genericsclosures, and protocols provide ways to factor out not only common code, but also variations on common themes. As a result, the underlying themes can be written once, used in varying contexts, and still be statically checked. Your programs will be much easier to maintain and debug, especially as they grow larger.

As you read this chapter, you may want to refer to the documentation, The Swift Programming Language (Swift 3 Edition).

Types and Type Inference

Swift combines strong and static typing with powerful type inference to keep code relatively concise. Swift’s type system and compile-time guarantees help improve the reliability of nontrivial code.

Deciphering Type Errors in Long Statements

If your program won’t compile, you can often clarify a type error by breaking up an assignment statement into smaller ones with explicit type declarations for each intermediate result.

Syntax

Swift’s syntax borrows enough from other languages to be easily readable.  Here’s a trivial example:

let aHost = "someMachine.com"
aHost = "anotherMachine.com" // ILLEGAL: can't change a constant

aHost is inferred by Swift to be of type String. It is a constant, and Swift will not compile any code that changes a constant after it has been initialized. (Throughout this book, ILLEGAL means “will not compile.”) This constant is initialized at its declaration, but Swift requires only that a constant be initialized before being used.

var aPath = "something"
aPath = "myDatabase" // OK

aPath is also a String, but is a mutable variable. Swift functions use keywords to prevent mixing up arguments at a call site. For example, here is a function:

func combine(host: String, withPath path: String) -> String {
    return host + "/" + path
}

and here is a call to it:

// returns "someMachine.com/myDatabase"
combine(host: aHost, withPath: aPath)

Swift’s syntax combines ease of learning, convenience of use, and prevention of mistakes.

Simple Enumerations

In Swift, as in other languages, an enumeration represents some fixed, closed set of alternatives that might be assigned to some variable. Unlike enumerations in other languages, Swift’s come in three flavors, each suited for a particular use. The flavor of an enumeration depends on how much information its values are specified to include.

An enumeration may be specified by 1) only a set of cases, 2)  a set of cases, each with a fixed value, or 3) a set of cases, each with a set of assignable values. (The last flavor is covered in Chapter 2.)

The simplest flavor merely associates a unique identifier with each case. For example:

enum Validity { case valid, invalid }

The second flavor of enumeration provides for each case to be associated with a value that is always the same for that case. Such a value must be expressed as a literal value, such as 17 or "abc". For example:

enum StatusCode: Int {
    case ok = 200
    case created = 201
     // more cases go here
    case badRequest = 400
    case unauthorized = 401
}

The value of this enumeration can be accessed via the rawValue attribute:

func printRealValue(of e: StatusCode) {
    print ("real value is", e.rawValue)
}

Tuples

As in some other languages, a Swift tuple simply groups multiple values together. For example, here’s a function that returns both a name and serial number:

func lookup(user: String) -> (String, Int) {
    // compute n and sn
    return (n, sn)
}

Tuple members can be accessed by index:

let userInfo = lookup(user: "Washington")
print( "name:", userInfo.0, "serialNumber:", userInfo.1 )

or can be unpacked simultaneously:

let (name, serialNumber) = lookup(user: "Adams")
print( "name:", name, "serialNumber:", serialNumber )

Members can be named in the tuple type declaration:

func lookup(user: String)
    -> (name: String, serialNumber: Int)
{
    // compute n and sn
    return (n, sn)
}

and then accessed by name:

let userInfo = lookup(user: "Washington")
print("name:",         userInfo.name,
      "serialNumber:", userInfo.serialNumber) 

When an identifier is declared with let, every element of the tuple behaves as if it were declared with a let:

let second = lookup(user: "Adams")
second.name = "Gomez Adams" // ILLEGAL: u is a let
var anotherSecond = lookup(user: "Adams")
anotherSecond.name = "Gomez Adams" // Legal: x is a var
print(anotherSecond.name) // prints Gomez Adams

When you assign a tuple to a new variable, it gets a fresh copy:

var first = lookup(user: "Washington")
var anotherFirst = first
first.name        // returns "George Washington"
anotherFirst.name // returns "George Washington" as expected
first.name = "George Jefferson"
first.name        // was changed, so returns "George Jefferson"
anotherFirst.name // returns "George Washington" because
                  // anotherFirst is an unchanged copy

first and anotherFirst are decoupled; changes to one do not affect the other. This isolation enables you to reason about your program one small chunk at a time. Swift has other constructs with this tidy property; they are all lumped into the category of value types. (See Chapter 2.) The opposite of a value type is a reference type. The only reference types are instances-of-classes and closures. Consequently, these are the only types that allow shared access to mutable state.

Tuples combine nicely with other language features: the standard built-in method for iterating through a dictionary uses key-value tuples. Also, Swift’s switch statements become very concise and descriptive by switching on a tuple:

enum  PegShape { case roundPeg,  squarePeg }
enum HoleShape { case roundHole, squareHole, triangularHole }

func howDoes( _ peg: PegShape,  fitInto hole: HoleShape )
    -> String
{
    switch (peg, hole) { // switches on a tuple
    case (.roundPeg, .roundHole):
        return "fits any orientation"
    case (.squarePeg, .squareHole):
        return "fits four ways"
    default:
        return "does not fit"
    }
}

Tuples are a convenient way to aggregate a few constant fields that have no meaningful type apart from the types of the fields.

Custom Operators

As in some other statically-typed languages, Swift allows you to define your own operators based on static types.

One custom operator we’ll be using in our examples later is apply, which we’ll denote as |>. Like a Unix pipe, it feeds a result on the left into a function on the right:

9.0 |> sqrt // returns 3

This lets us read code from left to right, in the order of execution. For example:

send(compress(getImage())) 

can be rewritten as:

getImage() |> compress |> send

To define this custom operator, you first tell Swift about the syntax:

precedencegroup LeftFunctionalApply {
    associativity: left
    higherThan: AssignmentPrecedence
    lowerThan: TernaryPrecedence
}
infix operator |> : LeftFunctionalApply

then provide a (generic) implementation:

func |> <In, Out> ( lhs: In,  rhs: (In) throws -> Out )
    rethrows -> Out {
    return try rhs(lhs)
}

Judiciously used, custom operators can help make code clear and concise.

Closures

Swift includes closures: anonymous functions defined inline, which can read and write to their enclosing lexical scope.1  A closure is a first-class entity, a reference type which can be assigned to constants and variables, passed as arguments, and returned as results. Functions and methods are just special cases of closures with a slightly different syntax for their definition. Closures support functional programming, which can be particularly helpful in dealing with asynchrony (Chapter 3).

Deciphering Types Errors in Closures

Most of the time, the Swift compiler can infer the types of closure arguments and results. When it cannot, or when the code includes a type error, the error message from the compiler can be obscure. You can often clarify type errors by adding types to a closure that are not strictly necessary. In the example below, if the compiler were to complain about a type, you could add the (previously implicit) types:

func makeChannel()
    -> ( send: (String) -> Void,  receive: () -> String )
{
    var message: String = ""
    return (
        send:    { (s: String) -> Void   in message = s    },
        receive: { (_: Void  ) -> String in return message }
    )
}

Object Orientation

Swift includes full support for class-based object-oriented programming, including classes, instances, constructors (called “initializers”), instance variables, static variables, computed virtual getters and setters, inheritance, and final attributes. When one method overrides another, it must be annotated in the code. This requirement prevents some errors. Experienced Swift programmers tend to reserve objects for things requiring shared access to a mutable state. (See Chapter 2.)

Protocols Define Interfaces

As with other typed languages, Swift includes a notion that the expected behavior of an entity is separate from any particular embodiment of that entity. The former is called a protocol and the latter a concrete type—think interface versus class if you’re a Java programmer. A Swift protocol lets you define what is expected of a type without specifying any implementation. For example, the operations expected of any HTTP_Request might be that it can supply a URL and a requestString:

protocol HTTP_Request_Protocol {
    var url: URL              {get}
    var requestString: String {get}
}

In other languages, Abstract_HTTP_Request might need an abstract requestString. But in Swift, the protocol serves that purpose:

class Abstract_HTTP_Request {
    let url: URL // A constant instance variable
    init(url: URL) {  self.url = url  }
}
class Get_HTTP_Request:
    Abstract_HTTP_Request, HTTP_Request_Protocol
{
    var requestString: String { return "GET" }
}
class Post_HTTP_Request:
    Abstract_HTTP_Request, HTTP_Request_Protocol
{
    var requestString: String { return "POST" }
    var data: String
    init( url: URL,  data: String ) {
        self.data = data
        super.init(url: url)
    }
}

When declaring a variable to hold a request, instead of using the type Abstract_HTTP_Request, use the type HTTP_Request_Protocol:

let aRequest: HTTP_Request_Protocol
    = Get_HTTP_Request(url:  /* some URL */)
aRequest.requestString // returns "GET"

In the following, you’ll see an example of a generic protocol, which could not be used as a type. As in other languages, protocols help to prevent errors as well as remove dependencies on the representation.

Generic Protocols

Generic entities allow the same basic code to apply to different types. In addition to concrete types, Swift also allows protocols to be generalized to different types, although the mechanism differs.

For example, suppose you have two responses, a TemperatureResponse and a FavoriteFoodResponse:

struct TemperatureResponse {
    let city:   String
    let answer: Int
}
struct FavoriteFoodResponse {
    let city:   String
    let answer: String
}

Even though each answer is a different type, they can share the same description by adopting a common protocol:

protocol ResponseProtocol {
    associatedtype Answer
    var city:   String {get}
    var answer: Answer {get}
}
struct TemperatureResponse:  ResponseProtocol {
    let city:   String
    let answer: Int
}
struct FavoriteFoodResponse: ResponseProtocol {
    let city:   String
    let answer: String
}

The associatedtype in the protocol works a bit like a type parameter, except that instead of being passed in, it is inferred from context. Any protocol with an associated type is considered to be generic. Self, which denotes the concrete type conforming to the protocol, is also considered to be an associated type.

Unfortunately, generic protocols such as this one are more difficult to use than nongeneric ones. Specifically, they cannot be used in place of types, but only as generic constraints. So, you cannot write a declaration to hold a value that conforms to ResponseProtocol:

var someResponse: ResponseProtocol // ILLEGAL 

But you can write a function that will work on any type that conforms to ResponseProtocol:

func handleResponse <SomeResponseType: ResponseProtocol>
    ( response: SomeResponseType )  {  }

Because a generic protocol cannot be used as a type, it is often helpful to split up a generic protocol into generic and nongeneric protocols. A full discussion of these generic protocols is beyond the scope of this book. Generic protocols support generic functions, structures, and objects by providing a way to express requirements and implementations that apply to entities that themselves generalize over the types of their arguments, results, or constituents.

Extending Classes, Structures, and Enumerations

Like many other languages, Swift allows you to add new behavior to a preexisting construct. In Swift, this capability is called an extension, and can be used with classes, structures, enumerations, and protocols. The last case is a bit different because protocol extensions supply default behavior, just as method bodies in Java interfaces do.

As you might expect, Swift’s extension facility is especially useful for large programs because the entity you want to extend is likely to have been defined in a separate library. You might not even have source code for it!

Less obviously, Swift’s extensions help in two other situations:

  1. An extension can add a bit of specialized functionality that is only visible within a single file. Suppose that in some computation you find yourself squaring a number often, such as (a/b) * (a/b) + (c/d) * (c/d). You could add the following to the file containing that code:

    private extension Int {
        var squared: Int { return self * self }
    }

    Now you can rewrite the above as (a/b).squared + (c/d).squared. The extension adds a new member to Int without cluttering up its namespace everywhere.

  2. You might have a set of classes where each performs the same set of functions. Extensions let you group the code by function as opposed to class. An extension need not be in the same file or even the same module as the original definition. For example, you might have classes for city and state that each perform a country lookup:

    class City {
        let name: String
        init(name: String)  { self.name = name }
        func lookupCountry() -> String {  }
    }
    class State {
        let name: String
        init(name: String)  { self.name = name }
        func lookupCountry() -> String {  }
    }

    Extensions let you group the lookup functions together:

    extension City  { func lookupCountry() -> String {  } }
    extension State { func lookupCountry() -> String {  } }

This lets you put functionality where it makes the most sense, whether in a type defined by a library, limited to the scope of a single file, or together with similar functionality for different types.

1 Unlike Smalltalk, Swift closures cannot return from the home method’s scope (a.k.a., nonlocal return), so they cannot be used to extend the built-in control structures.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required