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 generics, closures, 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
)
{
(
"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"
)
(
"name:"
,
userInfo
.
0
,
"serialNumber:"
,
userInfo
.
1
)
or can be unpacked simultaneously:
let
(
name
,
serialNumber
)
=
lookup
(
user
:
"Adams"
)
(
"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"
)
(
"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
(
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:
-
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 toInt
without cluttering up its namespace everywhere. -
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.
Get Extending Swift Value(s) to the Server now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.