13 Optional Types Exterminate Nil-Value Bugs 13 Structures Isolate Mutation 15 Enumerations with Associated Values 21 Choosing an Aggregate 24 3.. var requestString : String { return "PO
Trang 3David Ungar and Robert Dickerson
Extending Swift Value(s) to
the Server
Boston Farnham Sebastopol TokyoBeijing Boston Farnham Sebastopol Tokyo
Beijing
Trang 4[LSI]
Extending Swift Value(s) to the Server
by David Ungar and Robert Dickerson
Copyright © 2017 IBM Corporation All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use Online editions are also available for most titles (http://safaribooksonline.com) For more information, contact our corporate/institutional sales department:
800-998-9938 or corporate@oreilly.com.
Editors: Nan Barber and Susan Conant
Production Editor: Shiny Kalapurakkel
Copyeditor: Christina Edwards
Proofreader: Eliahu Sussman
Interior Designer: David Futato Cover Designer: Karen Montgomery
Illustrator: Rebecca Panzer
January 2017: First Edition
Revision History for the First Edition
2017-01-25: First Release
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc Extending Swift Value(s) to the Server, the cover image, and related trade dress are trademarks of
O’Reilly Media, Inc.
While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work Use of the information and instructions contained in this work is at your own risk If any code samples or other technology this work contains or describes is sub‐ ject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
Trang 5Table of Contents
Preface: Swift for the Rest of Your Application v
1 A Swift Introduction 1
Types and Type Inference 2
Syntax 2
Simple Enumerations 3
Tuples 3
Custom Operators 5
Closures 6
Object Orientation 7
Protocols Define Interfaces 7
Extending Classes, Structures, and Enumerations 9
2 Optional Types, Structures, & Enumerations 13
Optional Types Exterminate Nil-Value Bugs 13
Structures Isolate Mutation 15
Enumerations with Associated Values 21
Choosing an Aggregate 24
3 Swift Promises to Tame Asynchrony 29
Step 1: Chaining Synchronous Errors 30
Step 2: Linearizing Nested Callbacks 35
Step 3: Coping with Asynchronous Errors 40
4 Swift Package Manager 43
Semantic Versioning 44
Creating an Application 44
iii
Trang 6Importing a Library in Your Project 47
Developing in Xcode 50
Creating Your Own Library 51
Producing More Complex Projects 54
Using C Dynamic Libraries 55
5 Writing a To-Do List with Kitura 57
Servers and Routers 57
Creating a Web Service 59
Deploying Your Application to Bluemix 62
More Elaborate To-Do Items 65
Adding Authentication 68
Setting up the Database 69
Connecting to the Database 71
Conclusions 75
iv | Table of Contents
Trang 7Preface: Swift for the Rest of
Your Application
Q: Why did the human put on his boxing gloves?
A: He had to punch some cards.
Today’s applications do not run on a single platform Rather, someparts run on resource-limited devices, and other parts run on a vastand mysterious cloud of servers This separation has led to a schism
in how we build these applications because different platforms havedifferent requirements: the mobile portions must conserve batterypower, while the server portions must handle a large number ofrequests simultaneously Consequently, programmers use differentlanguages for different parts of applications—for instance, JavaScriptfor the browser, and Java for the server
However, constructing an application out of multiple languages isfraught with drawbacks: different teams in the same organizationspeak different languages—literally—and must master differentdeveloper ecosystems Precious time must be spent translating con‐cepts across language barriers and a few developers must know all ofthe languages in order to be effective Test cases and data modelsmust be replicated in different languages, introducing bugs andincurring future maintenance efforts Because third-party librariescannot be shared across groups, each team must learn different APIs
to obtain merely the same functionality
Swift was introduced by Apple in 2014 and replaced Objective-C asthe recommended language for all new applications running onApple devices Later, when Swift became open source in 2015, itspread to new platforms Currently, Swift is available on x86, ARM
v
Trang 8(including Raspberry Pi), and zOS architectures, as well asLinux, macOS, tvOS, watchOS, and iOS operating systems So, it isnow possible to write a whole end-to-end mobile application—front-end, middle, back, and even toaster—all in Swift That’s why
we wrote this book; we wanted to help you, the developer, who ismost likely writing in Java or JavaScript, to consider a switch toSwift
Why adopt Swift?
• The Swift language may well be better than what you are cur‐rently using
• You can develop and debug in a consistent environment Inte‐
grated development environments (IDEs) offer a tremendous
amount of functionality such as text editing, static analysis, codecompletion, debugging, profiling, and even source-control inte‐gration Switching back and forth between say, Eclipse andXcode is a bit like switching between French horn and electricguitar: neither easy nor productive
• You can reuse code When each bit of functionality is expressedexactly once, there is less work, more understanding, and fewerbugs
• You can leverage Swift’s features—such as optional types, valuetypes, and functional programming facilities—to detect manybugs at compile time that would otherwise be hard to find
• Since Swift uses the LLVM compiler toolchain for producingnative-code binaries, your applications have the potential forcompetitive performance in terms of speed, startup time, andmemory usage However, examination of performance is out‐side the scope of this book
• You will find an active and approachable community of Swiftdevelopers who are creating web posts, books, and videos In
2016, Swift was cited as the second “Most Loved” language in a
StackOverflow survey, and the third most upward trendingtechnology
This book will introduce you to the Swift language, illustrate some
of its most interesting features, and show you how to create anddeploy a simple web service Let’s get started!
vi | Preface: Swift for the Rest of Your Application
Trang 9Coding Style & Implementations
In the examples, the space constraints of this medium
have led us to indent, break lines, and place brackets
differently than we would in actual code In addition,
space has precluded the inclusion of full implementa‐
tions and blocks of code in this edition contain incon‐
sistencies in color and font If the inconsistencies
confuse you, please consult the repositories in
Table P-1
Table P-1 Where to find code examples
Repository name Referenced in
Book snippets Code snippets from the book
MiniPromiseKit Created in Chapter 3 ; used in Chapter 5
Pipes Used in Chapter 4
Kitura To-Do List Created in Chapter 5
Acknowledgments
This book would not have been possible without the support,encouragement, and guidance of the IBM Cloud and Swift@IBMleadership team, including Pat Bohrer, Eli Cleary, Jason Gart‐ner, Sandeep Gopisetty, Heiko Ludwig, Giovanni Pacifici, JohnPonzo, and Karl Weinmeister In addition, we want to extend ourthanks to the many IBM Swift engineers and Swift communitymembers working to bring Swift to the server—including Chris Bai‐ley, Hubertus Franke, David Grove, David Jones, and Shmuel Kall‐ner—for sharing their collective technical insights and creating thetools and libraries described herein The Swift community’s embrace
of Swift on the server reassured us that our contribution would bevalued The growing number of their instructive blog posts, videos,conference talks, and books have been of great help We would like
to thank our technical reviewers: Chris Devers, Shun Jiang, andAndrew Black Nan Barber and the O’Reilly team had the dauntingtask of editing our lengthy technical drafts and producing this book
We owe a huge debt of gratitude to the Apple Core Swift Team fortheir courage, intelligence, talent, wisdom, and generosity for bring‐ing a new language and ecosystem into existence and moving it toopen source Language design involves many difficult and complextradeoffs, and bringing a new language to the world requires a tre‐
Preface: Swift for the Rest of Your Application | vii
Trang 10mendous amount of work The rapid acceptance of Swift by devel‐opers is powerful testimony to the quality of the language.
Words fall short in plumbing the depths of our gratitude for thesupport and love of our sweethearts, Barbara Hurd and ValerieMagolan
viii | Preface: Swift for the Rest of Your Application
Trang 11CHAPTER 1
A Swift Introduction
Swift supports several different programming paradigms This chap‐ter provides a brief overview of the parts of the Swift language thatwill be familiar to a Java or JavaScript programmer Swift is not asmall language, and this chapter omits many of its conveniences,including argument labels, shorthand syntax for closures, stringinterpolation, array and dictionary literals, ranges, and scopingattributes Swift’s breadth lets you try Swift without changing yourprogramming 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 thecornucopia of features in the Swift language, since it gives you manyways to solve the same problem But taking the time to choose theright approach can often catch bugs, shorten, and clarify your code
For instance, value types help prevent unintended mutation of val‐ ues Paradigms borrowed from functional programming such
as generics, closures, and protocols provide ways to factor out not
only common code, but also variations on common themes As aresult, the underlying themes can be written once, used in varyingcontexts, and still be statically checked Your programs will be mucheasier to maintain and debug, especially as they grow larger
As you read this chapter, you may want to refer to the documenta‐tion, The Swift Programming Language (Swift 3 Edition)
1
Trang 12Types and Type Inference
Swift combines strong and static typing with powerful type inference
to keep code relatively concise Swift’s type system and compile-timeguarantees 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 easilyreadable 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 Swiftrequires 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, andprevention of mistakes
2 | Chapter 1: A Swift Introduction
Trang 13Simple Enumerations
In Swift, as in other languages, an enumeration represents somefixed, closed set of alternatives that might be assigned to some vari‐able Unlike enumerations in other languages, Swift’s come in threeflavors, each suited for a particular use The flavor of an enumera‐tion depends on how much information its values are specified toinclude
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 asso‐ ciated 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 exam‐ple:
enum StatusCode: Int {
func printRealValue (of : 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 aname and serial number:
func lookup (user: String) -> (String, Int) {
// compute n and sn
return (n, sn)
}
Simple Enumerations | 3
Trang 14Tuple members can be accessed by index:
let userInfo = lookup(user: "Washington" )
print( "name:" , userInfo , "serialNumber:" , userInfo )
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 tuplebehaves 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 notaffect the other This isolation enables you to reason about your pro‐gram 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 Conse‐
4 | Chapter 1: A Swift Introduction
Trang 15quently, these are the only types that allow shared access to mutablestate.
Tuples combine nicely with other language features: the standardbuilt-in method for iterating through a dictionary uses key-valuetuples Also, Swift’s switch statements become very concise anddescriptive 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"
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 leftinto 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:
Custom Operators | 5
Trang 161 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.
infix operator |> : LeftFunctionalApply
then provide a (generic) implementation:
func |> <In, Out> ( lhs: In, rhs: (In) throws -> Out )
different syntax for their definition Closures support functional pro‐ gramming, which can be particularly helpful in dealing with asyn‐
chrony (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 clar‐
ify 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 (pre‐
viously implicit) types:
6 | Chapter 1: A Swift Introduction
Trang 17Object Orientation
Swift includes full support for class-based object-oriented program‐ming, including classes, instances, constructors (called “initializ‐ers”), instance variables, static variables, computed virtual gettersand setters, inheritance, and final attributes When one method
overrides another, it must be annotated in the code This require‐
ment 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 theexpected 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 atype without specifying any implementation For example, the oper‐ations expected of any HTTP_Request might be that it can supply aURL and a requestString:
protocol HTTP_Request_Protocol {
var url : URL {get}
var requestString : String {get}
}
In other languages, Abstract_HTTP_Request might need an abstractrequestString 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
Object Orientation | 7
Trang 18var 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, whichcould not be used as a type As in other languages, protocols help toprevent errors as well as remove dependencies on the representa‐tion
Generic Protocols
Generic entities allow the same basic code to apply to differenttypes In addition to concrete types, Swift also allows protocols to begeneralized 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:
8 | Chapter 1: A Swift Introduction
Trang 19protocol 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 parame‐ter, 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 inplace of types, but only as generic constraints So, you cannot write adeclaration 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 con‐forms to ResponseProtocol:
func handleResponse <SomeResponseType: ResponseProtocol> ( response: SomeResponseType ) { … }
Because a generic protocol cannot be used as a type, it is often help‐ful to split up a generic protocol into generic and nongeneric proto‐cols A full discussion of these generic protocols is beyond the scope
of this book Generic protocols support generic functions, struc‐tures, and objects by providing a way to express requirements andimplementations that apply to entities that themselves generalizeover 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
Extending Classes, Structures, and Enumerations | 9
Trang 20protocols The last case is a bit different because protocol extensionssupply default behavior, just as method bodies in Java interfaces do.
As you might expect, Swift’s extension facility is especially usefulfor large programs because the entity you want to extend is likely tohave been defined in a separate library You might not even havesource code for it!
Less obviously, Swift’s extensions help in two other situations:
1 An extension can add a bit of specialized functionality that isonly visible within a single file Suppose that in some computa‐tion you find yourself squaring a number often, such as (a/b) *(a/b) + (c/d) * (c/d) You could add the following to the filecontaining 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 withoutcluttering up its namespace everywhere
2 You might have a set of classes where each performs the sameset of functions Extensions let you group the code by function
as opposed to class An extension need not be in the same file oreven the same module as the original definition For example,you might have classes for city and state that each perform acountry 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 { … } }
10 | Chapter 1: A Swift Introduction
Trang 21This lets you put functionality where it makes the most sense,whether in a type defined by a library, limited to the scope of a sin‐gle file, or together with similar functionality for different types.
Extending Classes, Structures, and Enumerations | 11
Trang 23CHAPTER 2 Optional Types, Structures, &
Enumerations
Programming is hard and debugging is harder, but maintaining anddebugging large programs that run asynchronously and concur‐rently is hardest It makes sense to place the burden of checking cer‐tain runtime properties of your program on the compiler rather
than the developer Swift’s optional types and structures let you tell
the compiler more about your program now so that you spend lesstime debugging later These features rely on a combination ofcompile-time checks and runtime techniques that, in most cases, donot reduce performance
Optional Types Exterminate Nil-Value Bugs
Programs represent uninitialized or absent values with nil (a.k.a.,
null) If your code fails to handle a nil value anywhere one can occur, bad things can happen So Swift incorporated the might-be-nil ver‐ sus can-never-be-nil distinction into its static type system These are
called “just” and “maybe” in Haskell For example, suppose you aretrying to extract the “Content-Type” entry from the header fields of
an HTTP request You have the header fields represented as a dictio‐nary with String keys and values:
let headerFields : [String: String] = …
Swift uses subscript notation to look up the value of a given key in adictionary:
13
Trang 24let contentType = headerFields[ "Content-Type" ]
and Swift will infer a type for contentType But that type is
not “String”! It is “String?” with a question mark, because Stringrepresents a value that can never be nil, whereas String? represents
a value that be either a String or nil The latter is called an optional type The dictionary lookup returns an optional type because the
dictionary might not contain a key for “Content-Type.” The typeString? is not the same type as String and Swift won’t let you usethe value of an optional type without an explicit check:
if contentType.hasPrefix("text" ) // ILLEGAL
There are many convenient ways to perform this check For exam‐ple, the if-let form checks if the value is nonnil If so, it assigns
the value to a new variable that is local to the then-part If the value
is nil, it executes the else-part, if any.
let contentType : String
if let ct = headerFields[ "Content-Type" ] {
let contentType = headerFields[ "Content-Type" ] ?? "none"
Swift’s treatment of nil values will significantly improve the quality
of your programs over many other languages Swift’s optionals addsecurity without inconvenience
Surprisingly Helpful, a Personal Note from David
For decades, I had programmed in languages that treat null as auniversal value of any type When I adopted Swift, I was quite sur‐prised by how much its treatment of nil improved the quality of my
code The unexpected benefit arose where a variable was not
optional because it was guaranteed to never be nil As a result, thatvariable was eliminated as a potential source of crashes
14 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 251Unless it is passed in-out, in which case the caller must mark it with an ampersand.
Structures Isolate Mutation
A Swift structure (struct) is like an instance of a class: it groups val‐
ues together, the values can be fixed or variable, and it includesmethods that operate on those values However, unlike an instance
of a class, a structure is a value type Assigning a structure to a vari‐
able creates a fresh copy, decoupling the assignee from the assignedvalue Whenever a struct is assigned to a new variable, as forinstance when passed as an argument to a function, the struct
is passed by value; in other words, the new variable receives a copy of
every value in the structure
Structure Mutation Guarantees
A structure provides two guarantees about its mutabil‐
ity:
1 When passed to another routine or placed in
another variable, the original structure is insulated
from any changes to the passed structure and vice
versa.1
2 When a structure is associated with an identifier
via a let statement, the value of the identifier may
not change
Using structures can prevent bugs Suppose you need to send arequest to a database to find out the current temperature in a givencity, and you also need to check and see how long the database took
You could use a class with an instance variable, startTime:
class TemperatureRequestClass {
let city : String
var startTime : Date? = nil // an optional Date
After creating a request object:
let request = TemperatureRequestClass(city: "Paris" )
Structures Isolate Mutation | 15
Trang 26you might hand it off to be processed:
request.startTime = Date.now
sendToDB(request: request, callback: receivedResponse)
and later print the time difference:
func receivedResponse (temperature: Int) {
let dbTime = Date.now.timeIntervalSince(request.startTime!) print( "It took" , dbTime,
"seconds to discover that the temperature in" , request.city, "is" , temperature)
… // Do lots of slow work to prepare to connection
request.startTime = Date.now //The BUG!
… // Send the request on the prepared connection
is corrupted.
Swift provides a better way—using a structure instead of a class:
16 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 272Swift does include a way to pass in a structure so that the callee can mutate it—in-out
parameters But the call site looks different when passing such a parameter, so that you need only inspect the local part of the code to see that there might be a possibility of mutation.
struct TemperatureRequestStruct {
let city : String
var startTime : Date? = nil
Because your calling code alters the startTime, which is contained
in a structure, it must put that structure in a var, not a let:
var request = TemperatureRequestStruct(city: "Paris" )
Now, the Swift compiler catches the error before the program caneven run!
func sendToDB (
request: TemperatureRequestStruct,
callback: (Int) -> Void
) {
… // Do lots of slow work to prepare to connection
request.startTime = Date.now // ILLEGAL: will not compile!!
… // Send the request on the prepared connection
}
Why is this assignment an error? Function parameters are lets bydefault, and the let-ness of the request parameter “flows down”into the startTime field
But what if you see the compile error and try a quick fix to get yourcode through the compiler?
var mutableRequest = request
Structures Isolate Mutation | 17
Trang 28Figure 2-2 Because structures are value types, the sendToDB function receives a whole new copy of the request The first attempt at mutation won’t even compile because parameters are lets If the request is assigned to a mutableRequest var, the mutation will compile but won’t corrupt the original.
Mutating Methods
Unlike any other value type, a structure can include methods that
mutate the contents of var fields in the structure As a helpful signal
to the programmer, such methods must be annotated with mutating This requirement highlights the places where side effects couldoccur:
struct TemperatureRequestStruct {
…
var startTime : Date? = nil
mutating func clearStartTime () { startTime = nil }
}
Since a let prohibits mutation, a mutating function may be invokedonly upon a var Unlike mutating an instance variable of a class, a mutation to a structure only changes the copy referenced by one
variable:
18 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 29let request1 = TemperatureRequestStruct(city: "Paris" )
var request2 = request1 // makes a copy because is a struct
request1.clearStartTime() // ILLEGAL: cannot mutate a let
request2.clearStartTime() // OK, but does not change request1
Structures Containing References to Objects
A structure may contain a reference to an instance of a
class, and vice versa If a structure contains such a ref‐
erence, some of the desirable properties of a value type
may be lost In particular, if a structure includes an
attribute that computes its result by querying a class
instance, the result may be subject to unexpected
changes This problem is transitive: it can occur even if
your value type contains a value type from a library
and that type contains a reference to a class instance
Despite its potential for havoc, it is the above feature
that enables the Swift standard library to provide one
of the most valuable features of Swift, collection
value-types: Swift’s Arrays, Dictionaries, and Sets are struc‐
tures, and therefore provide strong support for
mutation isolation and debugging Under the covers,
they use class instances to optimize mutation with the
goal of providing both performance and robust seman‐
tics For instance, if you write a function that passes an
array into a library, you can rest assured that the
library cannot change your array It behaves as if
copied, but can often avoid the actual expense of copy‐
ing
Default Implementations with Protocol Extensions
Swift classes can inherit from each other, but if you want to factorout behavior that two structures share, instead of creating a super-
class, you must use a protocol extension This technique is sometimes called protocol-oriented programming For example, suppose you
write two similar structures, a temperature request and an level request:
ozone-struct TemperatureRequest {
let city : String
let state : String
var startTime : Date? = nil
Structures Isolate Mutation | 19
Trang 30init(city: String, state: String) {
let city : String
let state : String
var startTime : Date? = nil
of the protocol Then the protocol extension adds the new cityAndState property to all objects that conform to the Request protocol
protocol Request {
var city : String {get}
var state : String {get}
}
extension Request {
var cityAndState : String { return city ", " state } }
struct TemperatureRequest: Request {
let city : String
let state : String
var startTime : Date? = nil
struct OzoneRequest: Request {
let city : String
let state : String
var startTime : Date? = nil
20 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 31init(city: String, state: String) {
ing—for example, there is no super.
Structures with protocols and protocol extensions provide one way
to replace a class hierarchy and limit mutation Swift includes yetanother: enumerations with associated values These can do an evenbetter job in the right situations
Structures are like instances of classes that are always copied whenbeing passed around Use structures wherever you don’t need sepa‐rate references to the same mutable state With structures, you don’thave to code the copy operation, you don’t have to clutter up thecode with copying, and in the case of larger structures, the compilercan optimize away much of the expense of copying Substituting astructure for an instance of a class changes the easiest thing to writefrom passing a reference to copying a value Bye-bye bugs
Enumerations with Associated Values
In Swift, there is usually more than one way to express a given con‐cept For instance, you can choose between a class and a structure.Moreover, Swift includes many features from the functional pro‐gramming school Judiciously exploited, these constructs can clarify,shorten, and generalize your code Like color TV, air conditioning,
or the smartphone, you may never have missed this next construct,yet may soon find you would not think of living without it One of
these is the enumeration with associated values.
As described in “Simple Enumerations” on page 3, a Swift enumera‐tion represents a value taken from a fixed set—for example, one of afixed set of HTTP requests:
Enumerations with Associated Values | 21
Trang 32enum HTTP_Request_Kind {
case get, post // other request types omitted for brevity
}
But Swift enumerations can do so much more because any case can
also include one or more associated values Such an enumeration is
more like a discriminated union than a Java or C enumeration Itcan replace a small class hierarchy or a group of structures thatimplement a common protocol
For example, suppose you need to implement HTTP requests with
an equality (==) operation You could create a class hierarchy, with
an abstract superclass containing common code and concrete sub‐classes for each kind of request But since you don’t want any asyn‐chronous code to mutate requests, you would do better to usestructures and a protocol The protocol would have to be generic(because a requirement of == references the associated type, Self)
However, recall that Swift disallows the use of a generic protocol as a
type:
protocol HTTP_Request {
static func == (a: Self, b: Self) -> Bool
}
struct Get: HTTP_Request { }
struct Post: HTTP_Request { }
let someRequest : HTTP_Request = // ILLEGAL
let requests : [HTTP_Request] = // also ILLEGAL
So you could neither declare a variable that could contain any kind
of request, nor an array that could contain a mixture of requests.However, since the set of requests is fixed, there is no need to allowothers to create new kinds of requests Thus, an enumeration withassociated values is the best fit
enum HTTP_Request {
case get ( destination: URL,
headerFields: [String: String] )
case post ( destination: URL,
headerFields: [String: String],
let = URLRequest(url: someURL)
22 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 33// also delete, put & patch
Now that an HTTP request is represented by an enumeration, there
is a single concrete type, HTTP_Request, that subsumes every kind ofrequest As a consequence, the same array can hold any kind ofHTTP_Request:
let requests : [HTTP_Request] = [
.get (destination: url1, headerFields: [:]),
.post(destination: url2, headerFields: [:], data: someData) ]
and you can test different kinds of requests for equality with eachother:
if aRequest == anotherRequest
Enumerations as Inside-Out Class Hierarchies
When you replace a class hierarchy with an enumeration, it’s liketurning the hierarchy inside out: the hierarchy first divides the code
Enumerations with Associated Values | 23
Trang 34into various types, such as the request types above; then, within eachclass the methods divide up the code by function But the methods
of an enumeration first divide up the code by function, and theswitches within each function then divide up the code by case.The enumeration isn’t always better than the hierarchy There aredrawbacks to using an enumeration:
by importers of your library
Awkward for common data
A superclass or protocol offers better support than an enumera‐tion for data attributes that are common to all the alternatives Itmay be better to use a structure holding the common attributesand an enumeration for the specifics
As you get used to Swift, you will find more and more uses forgeneric enumerations with associated values
An enumeration with associated values provides a single, concretetype for disparate cases Where the set of alternatives is fixed andhas few data attributes in common, an enumeration with associatedvalues can significantly improve the quality of your code
Choosing an Aggregate
Tuples, classes, structures, protocols, enumerations: Swift offersmany different aggregates Which to use in any given situation?
Figure 2-3 and the following subsections provide a simplified guide
It neglects two aspects: some constructs may run faster than others,and classes offer more implicitly dynamic dispatch of methods than
do other aggregates
24 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 35Figure 2-3 Issues to ponder when choosing an aggregate
Instantiate a Class for a Thing with an Identity
Use an instance of a class to represent a thing in the real world with
an identity that persists as its state changes For example, to model auser whose identity remains the same as his location changes, youuse a class:
Choosing an Aggregate | 25
Trang 36class User {
let name : String // the User’s name can not change
var location : String // the User’s location can change
Otherwise, Replace the Class with a Value Type
An instance of a class is passed by reference: any portion of your pro‐
gram with that reference can couple to any other portion with thatreference by changing a field in that instance In contrast, a value
type is passed by value: handing off a value type to another portion
of your program (or a library) does not create the potential forfuture interaction
The potential for interaction impedes the prevention and elimina‐tion of bugs Most bugs surface as undesired behavior resulting fromunexpected values in data structures Fixing the bug requires you to
find the statement that wrote the bad value But if the bad value occurs in an instance of a class, you must examine every place in the
program that could possibly refer to that instance Even if the faultturns out to be in a part of the program that is obviously related to
the bug, the mere chance that it could be in some unrelated part
imposes significant practical and psychological overhead in thesearch for the offending code
Value types help with both sorts of reverse reasoning They help toget from crash to offending code by reducing coupling With valuetypes, your program has fewer potential interconnections that youmust trace and rule out They also help with backtracking from code
to rationale because there are fewer things to be believed about avalue type than a reference type Once an instance of a value type is
referenced by a let, you don’t have to believe any details about howits mutation preserves invariants because there can be no furthermutations Whenever possible, replace a class with a value type,such as a structure or enumeration
26 | Chapter 2: Optional Types, Structures, & Enumerations
Trang 37Value Type Mutation Restrictions
• The values associated with an instance of an enu‐
meration cannot change.
• The immutability implied by a let constant hold‐
ing a tuple or structure flows down into its fields.
• Each time a tuple or structure is passed to another
variable it is copied The two variables are decou‐
pled unless passed via in-out, and in that case the
caller cannot pass a constant and must mark the
variable to be passed with an ampersand prefix
• Any member function that mutates a structure
must be specially marked and cannot be invoked
on a constant
If you are currently using a language with little support for optionaltypes or value types, your code will benefit from moving to Swiftand using them wherever you can The assurances they provideabout freedom from nil values, mutation, and coupling will help youwrite cleaner code This code will be easier to reason about, main‐tain, and debug
Choosing an Aggregate | 27
Trang 39CHAPTER 3 Swift Promises to Tame
Asynchrony
Server-side applications typically make requests to other web serv‐ices or databases, and these requests are usually asynchronous; theapplication does not stop to wait for the result to return Theserequests may succeed or fail, and often the result contains the input
to the next asynchronous request For example, consider an applica‐tion that can find the temperature at the user’s home The applica‐tion makes a request to a database to discover where the user lives,then makes a subsequent request to a weather service to get the tem‐perature in that location But if the database is missing an entry for auser, the subsequent request to the weather service must be skipped
In addition to sequencing the chain of asynchronous operations, anapplication must vary the sequence to cope with errors Therequired logic can be a challenge to program
This chapter shows how Swift’s support for first-class functions can
be harnessed to implement promises, one of the most effective con‐
structs for programming such chains After reading this chapter,you will understand how promises work, and why they are so bene‐ficial To illustrate this, we have implemented a very basic version ofPromiseKit called MiniPromiseKit that you can clone, refer to, anduse in your projects
This chapter explains promises in three incremental steps:
29
Trang 401 Synchronous operations are linked together in a chain, eachoperation depending on the previous one, and each potentiallythrowing an exception By using the Result enumeration, thedivergent control flow that results from handling exceptionsgets restructured into a simpler linear data flow that is easier tofollow.
2 The nested callbacks needed to chain asynchronous operationsare hard to read and maintain This section shows how to trans‐form the nest into a straightforward chain of BasicPromises
3 Steps 1 and 2 converge, combining Result and BasicPromise into Promise This synthesis simplifies and clarifies theconstruction of chains of asynchronous operations that copewith errors at any stage
Each of these steps takes advantage of Swift’s extensibility and staticchecking to create more concise and robust code
PromiseKit Conventions
Faced with many different names for the concepts in
this chapter, we chose the terminology used in Promis‐
eKit because it is one of the most popular libraries in
this domain It is a third-party, open-source library
written by Max Howell and available at promiseKit.org
or at https://github.com/mxcl/PromiseKit For
instance, we use the terms fulfill and reject instead
of the terms success, fail, or map, etc
Step 1: Chaining Synchronous Errors
To set the stage for programming chains of asynchronous requests, you encapsulate exceptions thrown by synchronous calls For exam‐
ple, suppose you need to print out the ambient temperature at auser’s home You are composing two calls: one that returns the citywhere a user lives, and another that returns the temperature of agiven city Each operation can fail by throwing an exception:
30 | Chapter 3: Swift Promises to Tame Asynchrony