1. Trang chủ
  2. » Công Nghệ Thông Tin

IT training extending swift values to the server khotailieu

86 45 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 86
Dung lượng 6,38 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 3

David 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 5

Table 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 6

Importing 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 7

Preface: 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 9

Coding 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 10

mendous 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 11

CHAPTER 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 12

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-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 13

Simple 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 14

Tuple 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 15

quently, 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 16

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.

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 17

Object 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 18

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, 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 19

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 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 20

protocols 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 21

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 sin‐gle file, or together with similar functionality for different types.

Extending Classes, Structures, and Enumerations | 11

Trang 23

CHAPTER 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 24

let 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 25

1Unless 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 26

you 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 27

2Swift 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 28

Figure 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 29

let 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 30

init(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 31

init(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 32

enum 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 34

into 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 35

Figure 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 36

class 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 37

Value 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 39

CHAPTER 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 40

1 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

Ngày đăng: 12/11/2019, 22:19