This is an introductory explanation of the Swift 3 programming language as it relates to practical real-life iOS programming, from my book, iOS 10 Programming Fundamentals with Swift. Copyright 2016 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 15 Programming Fundamentals with Swift and Programming iOS 14. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!
A variable is a named “shoebox” whose contained value must be of a single well-defined type. Every variable must be explicitly and formally declared. To put an object into the shoebox, thus causing the variable name to refer to that object, you assign the object to the variable. (As we know from Chapter 2, a function, too, has a type, and can be assigned to a variable.)
This chapter goes into detail about declaration and initialization of variables. It then discusses all the primary built-in Swift simple types. (I mean “simple” as opposed to collections; the primary built-in collection types are discussed at the end of Chapter 4.)
Aside from the convenience of giving a reference a name, a variable, by virtue of where it is declared, endows its referent with a particular scope (visibility) and lifetime; assigning something to a variable is a way of ensuring that it can be seen by code that needs to see it and that it persists long enough to serve its purpose.
In the structure of a Swift file (see Example 1.1), a variable can be declared just about anywhere. It will be useful to distinguish several levels of variable scope and lifetime:
A global variable, or simply a global, is a variable declared at the top level of a Swift file. A global variable lives as long as the file lives, which is as long as the program runs. A global variable is visible everywhere (that’s what “global” means). It is visible to all code within the same file, because it is at top level, so any other code in the same file must be at the same level or at a lower contained level of scope. Moreover, it is visible (by default) to all code within any other file in the same module, because Swift files in the same module can automatically see one another, and hence can see one another’s top levels. For example:
// File1: let globalVariable = "global" class Dog { func printGlobal() { print(globalVariable) // * } } // File2: class Cat { func printGlobal() { print(globalVariable) // * } }
A property is a variable declared at the top level of an object type declaration (an enum, struct, or class). There are two kinds of properties: instance properties and static/class properties.
static
or class
. (I’ll go into detail about those terms in Chapter 4.) Its lifetime is the same as the lifetime of the object type. If the object type is declared at the top level of a file, the property lives as long as the program runs.A property is visible to code inside the object declaration. For example, an object’s methods can see that object’s properties. Such code can refer to the property using dot-notation with self
, and I always do this as a matter of style, but self
can usually be omitted except for purposes of disambiguation. An instance property is also visible (by default) to other code, provided the other code has a reference to this instance; in that case, the property can be referred to through dot-notation with the instance reference. A static/class property is visible (by default) to other code that can see the name of this object type; in that case, it can be referred to through dot-notation with the object type. For example:
// File1: class Dog { static let staticProperty = "staticProperty" let instanceProperty = "instanceProperty" func printInstanceProperty() { print(self.instanceProperty) // * } } // File2: class Cat { func printDogStaticProperty() { print(Dog.staticProperty) // * } func printDogInstanceProperty() { let d = Dog() print(d.instanceProperty) // * } }
A local variable is a variable declared inside a function body. A local variable lives only as long as its surrounding curly-braces scope lives: it comes into existence when the path of execution passes into the scope and reaches the variable declaration, and it goes out of existence when the path of execution exits the scope. Local variables are sometimes called automatic, to signify that they come into and go out of existence automatically. A local variable can be seen only by subsequent code within the same scope (including a subsequent deeper scope within the same scope). For example:
class Dog { func printLocalVariable() { let localVariable = "local" print(localVariable) // * } }
A variable is declared with let
or var
:
let
, the variable becomes a constant — its value can never be changed after the first assignment of a value (initialization).var
, the variable is a true variable, and its value can be changed by subsequent assignment.A variable’s type, however, can never be changed. A variable declared with var
can be given a different value, but that value must conform to the variable’s type. Thus, when a variable is declared, it must be given a type, which it will have forever after. You can give a variable a type explicitly or implicitly:
After the variable’s name in the declaration, add a colon and the name of the type:
var x : Int
If you initialize the variable as part of the declaration, and if you provide no explicit type, Swift will infer its type, based on the value with which it is initialized:
var x = 1 // and now x is an Int
It is perfectly possible to declare a variable’s type explicitly and assign it an initial value, all in one move:
var x : Int = 1
In that example, the explicit type declaration is superfluous, because the type (Int) would have been inferred from the initial value. Sometimes, however, providing an explicit type, even while also assigning an initial value, is not superfluous. Here are the main situations where that’s the case:
A very common case in my own code is when I want to provide the initial value as a numeric literal. Swift will infer either Int or Double, depending on whether the literal contains a decimal point. But there are a lot of other numeric types! When I mean one of those, I will provide the type explicitly, like this:
let separator : CGFloat = 2.0
Sometimes, the type of the initial value is completely unknown to the compiler unless you tell it. A very common case involves option sets (discussed in Chapter 4). This won’t compile:
var opts = [.autoreverse, .repeat] // compile error
The problem is that the name .autoreverse
is a shortcut for UIViewAnimationOptions.autoreverse
(and so too for .repeat
), but Swift doesn’t know that unless we tell it. Explicitly typing the variable is an elegant way of doing that:
let opts : UIViewAnimationOptions = [.autoreverse, .repeat]
I frequently include a superfluous explicit type declaration as a kind of note to myself. Here’s an example from my own code:
let duration : CMTime = track.timeRange.duration
In that code, track
is an AVAssetTrack. Swift knows perfectly well that the duration
property of an AVAssetTrack’s timeRange
property is a CMTime. But I don’t! In order to remind myself of that fact, I’ve shown the type explicitly.
Because explicit variable typing is possible, a variable doesn’t have to be initialized when it is declared. It is legal to write this:
let x : Int
Now x
is an empty shoebox — an Int variable without an initial value. I strongly urge you, however, not to do that with a local variable if you can possibly avoid it. It isn’t a disaster — the Swift compiler will stop you from trying to use a variable that has never been assigned a value — but it’s not a good habit.
The exception that proves the rule is what we might call conditional initialization. Sometimes, we don’t know a variable’s initial value until we’ve performed some sort of conditional test. The variable itself, however, can be declared only once; so it must be declared in advance and conditionally initialized afterward. This sort of thing is not unreasonable (though there are other, possibly better ways to write it, to which I’ll come in due course):
let timed : Bool if val == 1 { timed = true } else { timed = false }
When a variable’s address is to be passed as argument to a function, the variable must be declared and initialized beforehand, even if the initial value is fake. Recall this example from Chapter 2:
var r : CGFloat = 0 var g : CGFloat = 0 var b : CGFloat = 0 var a : CGFloat = 0 c.getRed(&r, green: &g, blue: &b, alpha: &a)
After that code runs, our four CGFloat 0
values will have been replaced; they were just momentary placeholders, to satisfy the compiler.
On rare occasions, you’ll want to call a Cocoa method that returns a value immediately and later uses that value in a function passed to that same method. For example, Cocoa has a UIApplication instance method declared like this:
func beginBackgroundTask( expirationHandler handler: (() -> Void)? = nil) -> UIBackgroundTaskIdentifier
beginBackgroundTask(expirationHandler:)
returns a number (a UIBackgroundTaskIdentifier is just an Int), and will later call the expirationHandler:
function passed to it — a function in which you will want to use the number that was returned at the outset. Swift’s safety rules won’t let you declare the variable that holds this number and use it in an anonymous function all in the same statement:
let bti = UIApplication.shared.beginBackgroundTask { UIApplication.shared.endBackgroundTask(bti) } // error: variable used within its own initial value
Therefore, you need to declare the variable beforehand; but then Swift has another complaint:
var bti : UIBackgroundTaskIdentifier bti = UIApplication.shared.beginBackgroundTask { UIApplication.shared.endBackgroundTask(bti) } // error: variable captured by a closure before being initialized
The solution is to declare the variable beforehand and give it a fake initial value as a placeholder:
var bti : UIBackgroundTaskIdentifier = 0 bti = UIApplication.shared.beginBackgroundTask { UIApplication.shared.endBackgroundTask(bti) }
Instance properties of an object (at the top level of an enum, struct, or class declaration) can be initialized in the object’s initializer function rather than by assignment in their declaration. It is legal and common for both constant (let
) and variable (var
) instance properties to have an explicit type and no directly assigned initial value. I’ll have more to say about that in Chapter 4.
Sometimes, you’d like to run several lines of code in order to compute a variable’s initial value. A simple and compact way to express this is with an anonymous function that you call immediately (see Define-and-Call). I’ll illustrate by rewriting an earlier example:
let timed : Bool = { if val == 1 { return true } else { return false } }()
You can do the same thing when you’re initializing an instance property. In this class, there’s an image (a UIImage) that I’m going to need many times later on. It makes sense to create this image in advance as a constant instance property of the class. To create it means to draw it. That takes several lines of code. So I declare and initialize the property by defining and calling an anonymous function, like this (for my imageOfSize
utility, see Chapter 2):
class RootViewController : UITableViewController { let cellBackgroundImage : UIImage = { return imageOfSize(CGSize(width:320, height:44)) { // ... drawing goes here ... } }() // ... rest of class goes here ... }
Indeed, a define-and-call anonymous function is often the only legal way to compute an instance property’s initial value with multiple lines of code. The reason is that, when you’re initializing an instance property, you can’t call an instance method, because there is no instance yet — the instance, after all, is what you are in the process of creating.
The variables I’ve been describing so far in this chapter have all been stored variables. The shoebox analogy applies. The variable is a name, like a shoebox; a value can be put into the shoebox by assigning it to the variable, and it then sits there and can be retrieved later by referring to the variable, for as long the variable lives.
Alternatively, a variable can be computed. This means that the variable, instead of having a value, has functions. One function, the setter, is called when the variable is assigned to. The other function, the getter, is called when the variable is referred to. Here’s some code illustrating schematically the syntax for declaring a computed variable:
var now : String { ❶ get { ❷ return Date().description ❸ } set { ❹ print(newValue) ❺ } }
Here’s some code that illustrates the use of our computed variable. You don’t treat it any differently than any other variable! To assign to the variable, assign to it; to use the variable, use it. Behind the scenes, though, the setter and getter functions are called:
now = "Howdy" // Howdy ❶ print(now) // 2016-06-26 17:03:30 +0000 ❷
Assigning to | |
Fetching |
Observe that when we set now
to "Howdy"
in the first line, the string "Howdy"
wasn’t stored anywhere. It had no effect, for example, on the value of now
in the second line. A set
function can store a value, but it can’t store it in this computed variable; a computed variable isn’t storage! It’s a shorthand for calling its getter and setter functions.
There are a couple of variants on the basic syntax I’ve just illustrated:
The name of the set
function parameter doesn’t have to be newValue
. To specify a different name, put it in parentheses after the word set
, like this:
set (val) { // now you can use "val" inside the setter function body
There must always be a getter! However, if there is no setter, the word get
and the curly braces that follow it can be omitted. Thus, this is a legal declaration of a read-only variable:
var now : String { return Date().description }
A computed variable can be useful in many ways. Here are the ones that occur most frequently in my real programming life:
When a value can be readily calculated by a function each time it is needed, it often makes for simpler syntax to express it as a read-only calculated variable. Here’s an example from my own code:
var mp : MPMusicPlayerController { return MPMusicPlayerController.systemMusicPlayer() }
It’s no bother to call MPMusicPlayerController.systemMusicPlayer()
every time I want to refer to this object, but it’s more compact to refer to it by a simple name, mp
. And since mp
represents a thing, rather than the performance of an action, it’s nicer for mp
to appear as a variable, so that to all appearances it is the thing, rather than as a function, which returns the thing.
A computed variable can sit in front of one or more stored variables, acting as a gatekeeper on how those stored variables are set and fetched. This is comparable to an accessor method in Objective-C. In the extreme case, a public computed variable is backed by a private stored variable:
private var _p : String = "" var p : String { get { return self._p } set { self._p = newValue } }
That’s a silly example, because we’re not doing anything interesting with our accessors: we are just setting and getting the private stored variable directly, so there’s no effective difference between p
and _p
. But based on that template, you could now add functionality so that something useful happens during setting and getting.
As the preceding example demonstrates, a computed instance property function can refer to other instance properties; it can also call instance methods. This is important, because in general the initializer for a stored property can do neither of those things. The reason this is legal for a computed property is that its functions won’t be called until the instance actually exists.
Here’s a practical example of a computed variable used as a façade for storage. My class has an instance property myBigData
, holding a very large stored piece of data, which can alternatively be nil
(it’s an Optional, as I’ll explain later). When my app goes into the background, I want to reduce memory usage (because iOS kills backgrounded apps that use too much memory). So I plan to save the data of myBigData
as a file to disk, and then set the variable itself to nil
, thus releasing its data from memory. Now consider what should happen when my app comes back to the front and my code tries to fetch myBigData
. If it isn’t nil
, we just fetch its value. But if it is nil
, this might be because we saved its value to disk. So now I want to restore its value by reading it from disk, and then fetch its value. This is a perfect use of a computed variable façade:
private var _myBigData : Data! = nil var myBigData : Data! { set (newdata) { self._myBigData = newdata } get { if _myBigData == nil { // ... get a reference to file on disk, f ... if let d = try? Data(contentsOf:f) { self._myBigData = d // ... erase the file ... } } return self._myBigData } }
Computed variables are not needed as a stored variable façade as often as you might suppose. That’s because Swift has another feature, which lets you inject functionality into the setter of a stored variable — setter observers. These are functions that are called just before and just after other code sets a stored variable.
The syntax for declaring a variable with a setter observer is very similar to the syntax for declaring a computed variable; you can write a willSet
function, a didSet
function, or both:
var s = "whatever" { ❶ willSet { ❷ print(newValue) ❸ } didSet { ❹ print(oldValue) ❺ // self.s = "something else" } }
Setter observer functions are not called when the stored variable is initialized or when the didSet
function changes the stored variable’s value. That would be circular!
In practice, I find myself using setter observers, rather than a computed variable, in the vast majority of situations where I would have used a setter override in Objective-C. Here’s an example. This is an instance property of a view class. Every time this property changes, we need to change the interface to reflect it. Not only do we change the interface, but also we “clamp” the incoming value within a fixed limit:
var angle : CGFloat = 0 { didSet { // angle must not be smaller than 0 or larger than 5 if self.angle < 0 { self.angle = 0 } if self.angle > 5 { self.angle = 5 } // modify interface to match self.transform = CGAffineTransform(rotationAngle: self.angle) } }
A computed variable can’t have setter observers. But it doesn’t need them! There’s a setter function, so anything additional that needs to happen during setting can be programmed directly into that setter function.
The term lazy is not a pejorative ethical judgment; it’s a formal description of an important behavior. If a stored variable is assigned an initial value as part of its declaration, and if it uses lazy initialization, then the initial value is not actually evaluated and assigned until running code accesses the variable’s value.
There are three types of variable that can be initialized lazily in Swift:
dispatch_once
; this makes initialization both singular (it can happen only once) and thread-safe.
lazy
. This property must be declared with var
, not let
. The initializer for such a property might never be evaluated, namely if code assigns the property a value before any code fetches the property’s value.In Swift 3, dispatch_once
itself is unavailable; use a global or static variable to obtain the same behavior.
Lazy initialization is often used to implement singleton. Singleton is a pattern where all code is able to get access to a single shared instance of a certain class:
class MyClass { static let sharedSingleton = MyClass() }
Now other code can obtain a reference to MyClass’s singleton by saying MyClass.sharedSingleton
. The singleton instance is not created until the first time other code says this; subsequently, no matter how many times other code may say this, the instance returned is always that same instance. (Observe that that is not what would happen if this were a computed read-only property whose getter calls MyClass()
and returns that instance; do you see why?)
Now let’s talk about lazy initialization of instance properties. Why might you want this? One reason is obvious: the initial value might be expensive to generate, so you’d like to avoid generating it unless it is actually needed. But there’s another reason that turns out to be even more important: a lazy initializer can do things that a normal initializer can’t. In particular, it can refer to the instance. A normal initializer can’t do that, because the instance doesn’t yet exist at the time that a normal initializer would need to run (ex hypothesi, we’re in the middle of creating the instance, so it isn’t ready yet). A lazy initializer, by contrast, won’t run until some time after the instance has fully come into existence, so referring to the instance is fine. For example, this code would be illegal if the arrow
property weren’t declared lazy
:
class MyView : UIView { lazy var arrow : UIImage = self.arrowImage() func arrowImage () -> UIImage { // ... big image-generating code goes here ... } }
A very common idiom is to initialize a lazy instance property with a define-and-call anonymous function:
lazy var prog : UIProgressView = { let p = UIProgressView(progressViewStyle: .default) p.alpha = 0.7 p.trackTintColor = UIColor.clear p.progressTintColor = UIColor.black p.frame = CGRect(x:0, y:0, width:self.view.bounds.size.width, height:20) p.progress = 1.0 return p }()
There are some holes in the language. Lazy instance properties can’t have setter observers; and there’s no lazy let
, so you can’t readily make a lazy instance property read-only. Moreover, lazy instance properties aren’t atomic, so there’s a danger of multiple initialization under certain circumstances. (Global and static variables don’t have these issues: they are thread-safe and atomic.)
Every variable, and every value, must have a type. But what types are there? Up to this point, I’ve assumed the existence of some types, such as Int and String, without formally telling you about them. Here’s a survey of the primary simple types provided by Swift, along with some instance methods, global functions, and operators that apply to them. (Collection types will be discussed at the end of Chapter 4.)
The Bool object type (a struct) has only two values, commonly regarded as true and false (or yes and no). You can represent these values using the literal keywords true
and false
, and it is natural to think of a Bool value as being either true
or false
:
var selected : Bool = false
In that code, selected
is a Bool variable initialized to false
; it can subsequently be set to false
or true
, and to no other values. Because of its simple yes-or-no state, a Bool variable of this kind is often referred to as a flag.
Cocoa methods very often expect a Bool parameter or return a Bool value. For example, when your app launches, Cocoa calls a method in your code declared like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
You can do anything you like in that method; often, you will do nothing. But you must return a Bool! And in real life, that Bool will probably be true
. A minimal implementation thus looks like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool { return true }
A Bool is useful in conditions; as I’ll explain in Chapter 5, when you say if
something
, the something
is the condition, and is a Bool — or an expression that evaluates to a Bool. For example, when you compare two values with the equality comparison operator ==
, the result is a Bool — true
if they are equal to each other, false
if they are not:
if meaningOfLife == 42 { // ...
(I’ll talk more about equality comparison in a moment, when we come to discuss types that can be compared, such as Int and String.)
When preparing a condition, you will sometimes find that it enhances clarity to store the Bool value in a variable beforehand:
let comp = self.traitCollection.horizontalSizeClass == .compact if comp { // ...
Observe that, when employing that idiom, we use the Bool variable directly as the condition. There is no need to test explicitly whether a Bool equals true
or false
; the conditional expression itself is already testing that. It is silly — and arguably wrong — to say if comp == true
, because if comp
already means “if comp
is true
.”
Since a Bool can be used as a condition, a call to a function that returns a Bool can be used as a condition. Here’s an example from my own code. I’ve declared a function that returns a Bool to say whether the cards the user has selected constitute a correct answer to the puzzle:
func evaluate(_ cells:[CardCell]) -> Bool { // ...
Thus, elsewhere I can say this:
if self.evaluate(cellsToTest) { // ...
Unlike many computer languages, nothing else in Swift is implicitly coerced to or treated as a Bool. In C, for example, a boolean is actually a number, and 0
is false. But in Swift, nothing is false but false
, and nothing is true but true
.
The type name, Bool, comes from the English mathematician George Boole; Boolean algebra provides operations on logical values. Bool values are subject to these same operations:
!
!
unary operator reverses the truth value of the Bool to which it is applied as a prefix. If ok
is true
, !ok
is false
— and vice versa.&&
true
only if both operands are true
; otherwise, returns false
. If the first operand is false
, the second operand is not even evaluated (thus avoiding possible side effects).||
true
if either operand is true
; otherwise, returns false
. If the first operand is true
, the second operand is not even evaluated (thus avoiding possible side effects).If a logical operation is complicated or elaborate, parentheses around subexpressions can help clarify both the logic and the order of operations.
The main numeric types are Int and Double, meaning that, left to your own devices, these are the types you’ll use. Other numeric types exist mostly for compatibility with the C and Objective-C APIs that Swift needs to be able to talk to when you’re programming iOS.
The Int object type (a struct) represents an integer between Int.max
and Int.min
inclusive. The actual values of those limits might depend on the platform and architecture under which the app runs, so don’t count on them to be absolute; in my testing at this moment, they are 263-1 and -263 respectively (64-bit words).
The easiest way to represent an Int value is as a numeric literal. A simple numeric literal without a decimal point is taken as an Int by default. Internal underscores are legal; this is useful for making long numbers readable. Leading zeroes are legal; this is useful for padding and aligning values in your code.
You can write an Int literal using binary, octal, or hexadecimal digits. To do so, start the literal with 0b
, 0o
, or 0x
respectively. Thus, for example, 0x10
is decimal 16.
The Double object type (a struct) represents a floating-point number to a precision of about 15 decimal places (64-bit storage).
The easiest way to represent a Double value is as a numeric literal. Any numeric literal containing a decimal point is taken as a Double by default. Internal underscores and leading zeroes are legal.
A Double literal may not begin with a decimal point (unlike C and Objective-C). If the value to be represented is between 0 and 1, start the literal with a leading 0
.
You can write a Double literal using scientific notation. Everything after the letter e
is the exponent of 10. You can omit the decimal point if the fractional digits would be zero. For example, 3e2
is 3 times 102 (300).
You can write a Double literal using hexadecimal digits. To do so, start the literal with 0x
. You can use exponentiation here too (and again, you can omit the decimal point); everything after the letter p
is the exponent of 2. For example, 0x10p2
is decimal 64, because you are multiplying 16 by 22.
There’s a static property Double.infinity
and an instance property isZero
, among others.
Coercion is the conversion of a value from one type to another, and numeric coercion is the conversion of a value from one numeric type to another. Swift doesn’t really have explicit coercion, but it has something that serves the same purpose — instantiation. To convert an Int explicitly into a Double, instantiate Double with an Int in the parentheses. To convert a Double explicitly into an Int, instantiate Int with a Double in the parentheses; this will truncate the original value (everything after the decimal point will be thrown away):
let i = 10 let x = Double(i) print(x) // 10.0, a Double let y = 3.8 let j = Int(y) print(j) // 3, an Int
When numeric values are assigned to variables or passed as arguments to a function, Swift will perform implicit coercion of literals only. This code is legal:
let d : Double = 10
But this code is not legal, because what you’re assigning is a variable (not a literal) of a different type; the compiler will stop you:
let i = 10 let d : Double = i // compile error
The solution is to coerce explicitly as you assign or pass the variable:
let i = 10 let d : Double = Double(i)
The same rule holds when numeric values are combined by an arithmetic operation. Swift will perform implicit coercion of literals only. The usual situation is an Int combined with a Double; the Int is treated as a Double:
let x = 10/3.0 print(x) // 3.33333333333333
But variables of different numeric types must be coerced explicitly so that they are the same type if you want to combine them in an arithmetic operation. Thus, for example:
let i = 10 let n = 3.0 let x = i / n // compile error; you need to say Double(i)
These rules are evidently a consequence of Swift’s strict typing; but (as far as I am aware) they constitute very unusual treatment of numeric values for a modern computer language, and will probably drive you mad in short order. The examples I’ve given so far were easily solved, but things can become more complicated if an arithmetic expression is longer, and the problem is compounded by the existence of other numeric types that are needed for compatibility with Cocoa, as I shall now proceed to explain.
If you weren’t programming iOS — if you were using Swift in some isolated, abstract world — you could probably do all necessary arithmetic with Int and Double alone. Unfortunately, to program iOS you need Cocoa, which is full of other numeric types; and Swift has types that match every one of them. Thus, in addition to Int, there are signed integers of various sizes — Int8, Int16, Int32, Int64 — plus the unsigned integer UInt along with UInt8, UInt16, UInt32, and UInt64. In addition to Double, there is the lower-precision Float (32-bit storage, about 6 or 7 decimal places of precision) and the extended-precision Float80; plus, in the Core Graphics framework, CGFloat (whose size can be that of Float or Double, depending on the bitness of the architecture).
You may also encounter a C numeric type when trying to interface with a C API. These types, as far as Swift is concerned, are just type aliases, meaning that they are alternate names for another type; for example, a CDouble (corresponding to C’s double
) is just a Double by another name, a CLong (C’s long
) is an Int, and so on. Many other numeric type aliases will arise in various Cocoa frameworks; for example, TimeInterval (Objective-C NSTimeInterval) is merely a type alias for Double.
Here’s the problem. I have just finished telling you that you can’t assign, pass, or combine values of different numeric types using variables; you have to coerce those values explicitly to the correct type. But now it turns out that you’re being flooded by Cocoa with numeric values of many types! Cocoa will often hand you a numeric value that is neither an Int nor a Double — and you won’t necessarily realize this, until the compiler stops you dead in your tracks for some sort of type mismatch. You must then figure out what you’ve done wrong and coerce everything to the same type.
Here’s a typical example from one of my apps. A slider, in the interface, is a UISlider, whose minimumValue
and maximumValue
are Floats. In this code, s
is a UISlider, g
is a UIGestureRecognizer, and we’re trying to use the gesture recognizer to move the slider’s “thumb” to wherever the user tapped within the slider:
let pt = g.location(in:s) ❶ let percentage = pt.x / s.bounds.size.width ❷ let delta = percentage * (s.maximumValue - s.minimumValue) // compile error ❸
That won’t compile. Here’s why:
| |
Luckily, | |
We now try to combine |
This sort of thing is not an issue in C or Objective-C, where there is implicit coercion; but in Swift, a CGFloat can’t be combined with Floats. We must coerce explicitly:
let delta = Float(percentage) * (s.maximumValue - s.minimumValue)
The good news here is that if you can get enough of your code to compile, Xcode’s Quick Help feature will tell you what type Swift has inferred for a variable (Figure 3.1). This can assist you in tracking down your issues with numeric types.
In the rare circumstance where you need to assign or pass an integer type where another integer type is expected and you don’t actually know what that other integer type is, you can get Swift to coerce dynamically by calling the global numericCast(_:)
function. For example, if i
and j
are previously declared variables of different integer types, i = numericCast(j)
coerces j
to the integer type of i
.
Swift’s arithmetic operators are as you would expect; they are familiar from other computer languages as well as from real arithmetic:
+
-
*
/
As in C, division of one Int by another Int yields an Int; any remaining fraction is stripped away. 10/3
is 3, not 3-and-one-third.
%
Integer types can be treated as binary bitfields and subjected to binary bitwise operations:
&
|
^
~
<<
>>
Technically, the shift operators perform a logical shift if the integer is unsigned, and an arithmetic shift if the integer is signed.
Integer overflow or underflow — for example, adding two Int values so as to exceed Int.max
— is a runtime error (your app will crash). In simple cases the compiler will stop you, but you can get away with it easily enough:
let i = Int.max - 2 let j = i + 12/2 // crash
Under certain circumstances you might want to force such an operation to succeed, so special overflow/underflow methods are supplied. These methods return a tuple; I’ll show you an example even though I haven’t discussed tuples yet:
let i = Int.max - 2 let (j, over) = Int.addWithOverflow(i,12/2)
Now j
is Int.min + 3
(because the value has wrapped around from Int.max
to Int.min
) and over
is true
(to report the overflow).
If you don’t care to hear about whether or not there was an overflow/underflow, special arithmetic operators let you suppress the error: &+
, &-
, &*
.
You will frequently want to combine the value of an existing variable arithmetically with another value and store the result in the same variable. Remember that to do so, you will need to have declared the variable as a var
:
var i = 1 i = i + 7
As a shorthand, operators are provided that perform the arithmetic operation and the assignment all in one move:
var i = 1 i += 7
The shorthand (compound) assignment arithmetic operators are +=
, -=
, *=
, /=
, %=
, &=
, |=
, ^=
, <<=
, >>=
.
Operation precedence is largely intuitive: for example, *
has a higher precedence than +
, so x+y*z
multiplies y
by z
first, and then adds the result to x
. Use parentheses to disambiguate when in doubt; for example, (x+y)*z
performs the addition first.
Global functions include abs
(absolute value), max
, and min
:
let i = -7 let j = 6 print(abs(i)) // 7 print(max(i,j)) // 6
New in Swift 3, many mathematical functions, such as square roots and rounding, are implemented natively as methods; thus you can say 2.squareRoot()
or 5.5.rounded()
.
Global mathematical functions, such as trigonometry and random numbers, come from the C standard libraries that are visible because you’ve imported UIKit. You still have to be careful about numeric types. For example, arc4random_uniform
takes and returns a UInt32. So if n
is an Int and you want to get a random Int between between 0 and n-1
, you can’t say arc4random_uniform(n)
; you have to coerce the argument and the result, saying Int(arc4random_uniform(UInt32(n)))
.
Numbers are compared using the comparison operators, which return a Bool. For example, the expression i==j
tests whether i
and j
are equal; when i
and j
are numbers, “equal” means numerically equal. So i==j
is true
only if i
and j
are “the same number,” in exactly the sense you would expect.
The comparison operators are:
==
true
if its operands are equal.!=
false
if its operands are equal.<
true
if the first operand is less than the second operand.<=
true
if the first operand is less than or equal to the second operand.>
true
if the first operand is greater than the second operand.>=
true
if the first operand is greater than or equal to the second operand.Keep in mind that, because of the way computers store numbers, equality comparison of Double values may not succeed where you would expect. To test whether two Doubles are effectively equal, it can be more reliable to compare the difference between them to a very small value (usually called an epsilon):
let isEqual = abs(x - y) < 0.000001
The String object type (a struct) represents text. The easiest way to represent a String value is with a literal, which is delimited by double quotes:
let greeting = "hello"
A Swift string is thoroughly modern; under the hood, it’s Unicode, and you can include any character directly in a string literal. If you don’t want to bother typing a Unicode character whose codepoint you know, use the notation \u{...}
, where what’s between the curly braces is up to eight hex digits:
let leftTripleArrow = "\u{21DA}"
The backslash in that string representation is the escape character; it means, “I’m not really a backslash; I indicate that the next character gets special treatment.” Various nonprintable and ambiguous characters are entered as escaped characters; the most important are:
\n
\t
\"
\\
One of Swift’s coolest features is string interpolation. This permits you to embed any value that can be output with print
inside a literal string as a string, even if it is not itself a string. The notation is escaped parentheses: \(...)
. For example:
let n = 5 let s = "You have \(n) widgets."
Now s
is the string "You have 5 widgets."
The example is not very compelling, because we know what n
is and could have typed 5
directly into our string; but imagine that we don’t know what n
is! Moreover, the stuff in escaped parentheses doesn’t have to be the name of a variable; it can be almost any expression that evaluates as legal Swift. If you don’t know how to add, this example is more compelling:
let m = 4 let n = 5 let s = "You have \(m + n) widgets."
To combine (concatenate) two strings, the simplest approach is to use the +
operator:
let s = "hello" let s2 = " world" let greeting = s + s2
This convenient notation is possible because the +
operator is overloaded: it does one thing when the operands are numbers (numeric addition) and another when the operands are strings (concatenation). As I’ll explain in Chapter 5, all operators can be overloaded, and you can overload them to operate in some appropriate way on your own types.
The +
operator comes with a +=
assignment shortcut; naturally, the variable on the left side must have been declared with var
:
var s = "hello" let s2 = " world" s += s2
As an alternative to +=
, you can call the append(_:)
instance method:
var s = "hello" let s2 = " world" s.append(s2)
Another way of concatenating strings is with the joined(separator:)
method. You start with an array (yes, I know we haven’t gotten to arrays yet) of strings to be concatenated, and hand it the string that is to be inserted between all of them:
let s = "hello" let s2 = "world" let space = " " let greeting = [s,s2].joined(separator:space)
The comparison operators are also overloaded so that they all work with String operands. Two String values are equal (==
) if they are, in the natural sense of the words, “the same text.” A String is less than another if it is alphabetically prior.
A few additional convenient instance methods and properties are provided. isEmpty
returns a Bool reporting whether this string is the empty string (""
). hasPrefix(_:)
and hasSuffix(_:)
report whether this string starts or ends with another string; for example, "hello".hasPrefix("he")
is true
. The uppercased
and lowercased
methods provide uppercase and lowercase versions of the original string.
Coercion between a String and an Int is possible. To make a string that represents an Int, it is sufficient to use string interpolation; alternatively, use the Int as a String initializer, just as if you were coercing between numeric types:
let i = 7 let s = String(i) // "7"
Your string can also represent an Int in some other base; supply a radix:
argument expressing the base:
let i = 31 let s = String(i, radix:16) // "1f"
A String that might represent a number can be coerced to a numeric type; an integer type will accept a radix:
argument expressing the base. The coercion might fail, though, because the String might not represent a number of the specified type; so the result is not a number but an Optional wrapping a number (I haven’t talked about Optionals yet, so you’ll have to trust me for now; failable initializers are discussed in Chapter 4):
let s = "31" let i = Int(s) // Optional(31) let s2 = "1f" let i2 = Int(s2, radix:16) // Optional(31)
Coercion to String is in fact the basis of string interpolation, and of representation in the console with print
. You can make any object coercible to String, by making it conform to any of three protocols: Streamable, CustomStringConvertible, and CustomDebugStringConvertible. I’ll give an example when I explain what a protocol is, in Chapter 4.
The length of a String, in characters, is given by the count
property of its characters
property:
let s = "hello" let length = s.characters.count // 5
Why isn’t there simply a length
property of a String? It’s because a String doesn’t really have a simple length. The String is stored as a sequence of Unicode codepoints, but multiple Unicode codepoints can combine to form a character; so, in order to know how many characters are represented by such a sequence, we actually have to walk through the sequence and resolve it into the characters that it represents.
You, too, can walk through a String’s characters. The simplest way is with the for...in
construct (see Chapter 5). What you get when you do this are Character objects; I’ll talk more about Character objects later:
let s = "hello" for c in s.characters { print(c) // print each Character on its own line }
At an even deeper level, you can decompose a String into its UTF-8 codepoints or its UTF-16 codepoints, using the utf8
and utf16
properties:
let s = "\u{BF}Qui\u{E9}n?" for i in s.utf8 { print(i) // 194, 191, 81, 117, 105, 195, 169, 110, 63 } for i in s.utf16 { print(i) // 191, 81, 117, 105, 233, 110, 63 }
There is also a unicodeScalars
property representing a collection (a String.UnicodeScalarView
) of the String’s UTF-32 codepoints expressed as UnicodeScalar structs. To illustrate, here’s a utility function that turns a two-letter country abbreviation into an emoji representation of its flag:
func flag(country:String) -> String { let base : UInt32 = 127397 var s = "" for v in country.unicodeScalars { s.unicodeScalars.append(UnicodeScalar(base + v.value)!) } return String(s) } // and here's how to use it: let s = flag(country:"DE")
The curious thing is that there aren’t more methods for standard string manipulation. How, for example, do you capitalize a string, or find out whether a string contains a given substring? Most modern programming languages have a compact, convenient way of doing things like that; Swift doesn’t. The reason appears to be that missing features are provided by the Foundation framework, to which you’ll always be linked in real life (importing UIKit imports Foundation). A Swift String is bridged to a Foundation NSString. This means that, to a large extent, Foundation NSString properties and methods magically spring to life whenever you are using a Swift String. For example:
let s = "hello world" let s2 = s.capitalized // "Hello World"
The capitalized
property comes from the Foundation framework; it’s provided by Cocoa, not by Swift. It’s an NSString property; it appears tacked on to String “for free.” Similarly, here’s how to locate a substring of a string:
let s = "hello" let range = s.range(of:"ell") // Optional(Range(1..<4))
I haven’t explained yet what an Optional is or what a Range is (I’ll talk about them later in this chapter), but that innocent-looking code has made a remarkable round-trip from Swift to Cocoa and back again: the Swift String s
becomes an NSString, an NSString method is called, a Foundation NSRange struct is returned, and the NSRange is converted to a Swift Range and wrapped up in an Optional.
It will often happen, however, that you don’t want this round-trip conversion. For various reasons, you might want to stay in the Foundation world and receive the answer as a Foundation NSRange. To accomplish that, you have to cast your string explicitly to an NSString, using the as
operator (I’ll discuss casting formally in Chapter 4):
let s = "hello" let range = (s as NSString).range(of:"ell") // (1,3), an NSRange
Here’s another example, also involving NSRange. Suppose you want to derive the string "ell"
from "hello"
by its range — the second, third, and fourth characters. Foundation’s NSString method substring(with:)
requires that you supply a range — meaning an NSRange. You can readily form the NSRange, but when you do, your code doesn’t compile:
let s = "hello" let ss = s.substring(with:NSRange(location:1, length:3)) // compile error
The reason for the compile error is that Swift has absorbed NSString’s substring(with:)
, and expects you to supply a Swift Range here. I’ll explain in a moment how to do that, but you may find it simpler to tell Swift to stay in the Foundation world, by casting:
let s = "hello" let ss = (s as NSString).substring(with:NSRange(location: 1, length:3))
The Character object type (a struct) represents a single Unicode grapheme cluster — what you would naturally think of as one character of a string. A String object can be decomposed into a sequence of Character objects by taking its characters
property. Formally, this is a String.CharacterView
struct; but I’ll call it simply a character sequence. As I mentioned earlier, you can walk through a character sequence with for...in
to obtain the String’s Characters, one by one:
let s = "hello" for c in s.characters { print(c) // print each Character on its own line }
It isn’t common to encounter Character objects outside of some character sequence of which they are a part. There isn’t even a way to write a literal Character. To make a Character from scratch, initialize it from a single-character String:
let c = Character("h")
By the same token, you can initialize a String from a Character:
let c = Character("h") let s = (String(c)).uppercased()
Characters can be compared for equality; “less than” means what you would expect it to mean.
A character sequence has many properties and methods that can come in handy. By virtue of being a Collection, it has a first
and last
property; these are Optionals, because the string might be empty:
let s = "hello" let c1 = s.characters.first // Optional("h") let c2 = s.characters.last // Optional("o")
The index(of:)
method locates the first occurrence of a given character within the sequence and returns its index. Again, this is an Optional, because the character might be absent:
let s = "hello" let firstL = s.characters.index(of:"l") // Optional(2)
All Swift indexes are numbered starting with 0
, so 2
means the third character. The index value here, however, is not an Int; I’ll explain in a moment what it is and what it’s good for.
By virtue of being a Sequence, a character sequence has a contains(_:)
method that returns a Bool, reporting whether a certain character is present:
let s = "hello" let ok = s.characters.contains("o") // true
Alternatively, contains(_:)
can take a function that takes a Character and returns a Bool. (The index(of:)
method has an alternative form, index(where:)
, that can do this too.) This code reports whether the target string contains a vowel:
let s = "hello" let ok = s.characters.contains {"aeiou".characters.contains($0)} // true
The filter(_:)
method takes a function that takes a Character and returns a Bool, effectively eliminating those characters for which false
is returned. The result is a character sequence, but you can coerce that to a String. Thus, here’s how to delete all nonvowels from a String:
let s = "hello" let s2 = String(s.characters.filter {"aeiou".characters.contains($0)}) // "eo"
The dropFirst
and dropLast
methods return (in effect) a new character sequence without the first or last character, respectively:
let s = "hello" let s2 = String(s.characters.dropFirst()) // "ello"
prefix(_:)
and suffix(_:)
extract the character sequence of a given length from the start or end of the original character sequence:
let s = "hello" let s2 = String(s.characters.prefix(4)) // "hell"
split(_:)
breaks a character sequence up into an array, according to a function that takes a Character and returns a Bool. In this example, I obtain the words of a String, where a “word” is simplemindedly defined as a run of Characters other than a space:
let s = "hello world" let arr = s.characters.split{$0 == " "}
The result, once again, is an array of character sequences; to get String objects, we can apply the map(_:)
function and coerce them all to Strings. I’ll talk about map(_:)
in Chapter 4, so you’ll have to trust me for now:
let s = "hello world" let arr = s.characters.split{$0 == " "}.map{String($0)} // ["hello", "world"]
A String — in reality, its underlying character sequence — can also be manipulated similarly to an array. For example, you can use subscripting to obtain the character at a certain position. Unfortunately, this isn’t as easy as it might be. For example, what’s the second character of "hello"
? This doesn’t compile:
let s = "hello" let c = s[1] // compile error
The reason is that the indexes on a String (which are actually indexes on its character sequence) are a special nested type, a String.Index
(which is actually a type alias for String.CharacterView.Index
). To make an object of this type is rather tricky. Start with a String’s (or a character sequence’s) startIndex
or endIndex
, or with the return value from the index(of:)
method; you can then call the index(_:offsetBy:)
method to derive the index you want:
let s = "hello" let ix = s.startIndex let ix2 = s.index(ix, offsetBy:1) let c = s[ix2] // "e"
The reason for this clumsy circumlocution is that Swift doesn’t know where the characters of a character sequence actually are until it walks the sequence; calling index(_:offsetBy:)
is how you make Swift do that.
To offset an index by a single position, you can obtain the next or preceding index value with the index(after:)
and index(before:)
methods. Thus, I could have written the preceding example like this:
let s = "hello" let ix = s.startIndex let c = s[s.index(after:ix)] // "e"
Once you’ve obtained a desired character index value, you can use it to modify the String. For example, the insert(contentsOf:at:)
method inserts a character sequence — not a String! — into a String:
var s = "hello" let ix = s.index(s.startIndex, offsetBy:1) s.insertContentsOf("ey, h".characters, at: ix) // s is now "hey, hello"
Similarly, remove(at:)
deletes a single character, and also returns that character. (Manipulations involving longer character stretches require use of a Range, which is the subject of the next section.)
Note that a character sequence can be coerced directly to an Array of Character objects — for example, Array("hello".characters)
. It could be worth your while to do that, because array indexes are Ints, and are thus easy to work with. Once you’ve manipulated the array of Characters, you can coerce it directly to a String. I’ll give an example in the next section (and I’ll discuss arrays, and say more about collections and sequences, in Chapter 4).
The Range object type (a struct) represents a pair of endpoints. There are two operators for forming a Range literal; you supply a start value and an end value, with one of the Range operators between them:
...
a...b
means “everything from a
up to b
, including b
.”..<
a..<b
means “everything from a
up to but not including b
.”Spaces around a Range operator are legal.
The types of a Range’s endpoints will typically be some kind of number — most often, Ints:
let r = 1...3
If the end value is a negative literal, it has to be enclosed in parentheses or preceded by whitespace:
let r = -1000 ... -1
A very common use of a Range is to loop through numbers with for...in
:
for ix in 1...3 { print(ix) // 1, then 2, then 3 }
There are no reverse Ranges: the start value of a Range can’t be greater than the end value (the compiler won’t stop you, but you’ll crash at runtime). In practice, you can use Range’s reversed()
method to iterate from a higher value to a lower one:
for ix in (1...3).reversed() { print(ix) // 3, then 2, then 1 }
In Chapter 5 I’ll show how to create a custom operator that effectively generates a reverse Range.
You can also use a Range’s contains(_:)
instance method to test whether a value falls within given limits:
let ix = // ... an Int ... if (1...3).contains(ix) { // ...
For purposes of testing containment, a Range’s endpoints can be Doubles:
let d = // ... a Double ... if (0.1...0.9).contains(d) { // ...
There are also methods for learning whether two ranges overlap, and for clamping one range to another.
Another common use of a Range is to index into a sequence. For example, here’s one way to get the second, third, and fourth characters of a String. As I suggested at the end of the preceding section, we coerce the String’s characters
to an Array; we can then use an Int Range as an index into that array, and coerce back to a String:
let s = "hello" let arr = Array(s.characters) let result = arr[1...3] let s2 = String(result) // "ell"
Alternatively, you can use a Range to index directly into a String (or its underlying character sequence), but then it has to be a Range of String.Index
, which, as I’ve already pointed out, is rather clumsy to obtain. One way to get one is to let Swift convert the NSRange that you get back from a Cocoa method call into a Swift Range for you:
let s = "hello" let r = s.range(of:"ell") // a Swift Range (wrapped in an Optional)
You can also generate your Range endpoints as index values. Once you have a Range of the proper type, you can extract a substring by subscripting:
let s = "hello" let ix1 = s.index(s.startIndex, offsetBy:1) let ix2 = s.index(ix1, offsetBy:2) let s2 = s[ix1...ix2] // "ell"
The replaceSubrange(_:with:)
method splices into a range, thus modifying the string:
var s = "hello" let ix = s.startIndex let r = s.index(ix, offsetBy:1)...s.index(ix, offsetBy:3) s.replaceSubrange(r, with: "ipp") // s is now "hippo"
Similarly, you can delete a stretch of characters with the removeSubrange(_:)
method:
var s = "hello" let ix = s.startIndex let r = s.index(ix, offsetBy:1)...s.index(ix, offsetBy:3) s.removeSubrange(r) // s is now "ho"
A Swift Range and a Cocoa NSRange are constructed very differently from one another. A Swift Range is defined by two endpoints. A Cocoa NSRange is defined by a starting point and a length. But you can coerce a Swift Range whose endpoints are Ints to an NSRange, and you can convert from an NSRange to a Swift Range with the toRange
method (which returns an Optional wrapping a Range).
Sometimes, Swift goes even further. For example, when we say "hello".range(of:"ell")
, Swift bridges between Range and NSRange for us, correctly taking account of the fact that Swift and Cocoa interpret characters and string length differently, as well as the fact that an NSRange’s values are Ints, while the endpoints of a Range describing a Swift substring are String.Index
.
A tuple is a lightweight custom ordered collection of multiple values. As a type, it is expressed by surrounding the types of the contained values with parentheses and separating them by commas. For example, here’s a declaration for a variable whose type is a tuple of an Int and a String:
var pair : (Int, String)
The literal value of a tuple is expressed in the same way — the contained values, surrounded with parentheses and separated by commas:
var pair : (Int, String) = (1, "Two")
Those types can be inferred, so there’s no need for the explicit type in the declaration:
var pair = (1, "Two")
Tuples are a pure Swift language feature; they are not compatible with Cocoa and Objective-C, so you’ll use them only for values that Cocoa never sees. Within Swift, however, they have many uses. For example, a tuple is an obvious solution to the problem that a function can return only one value; a tuple is one value, but it contains multiple values, so using a tuple as the return type of a function permits that function to return multiple values.
Tuples come with numerous linguistic conveniences. You can assign to a tuple of variable names as a way of assigning to multiple variables simultaneously:
var ix: Int var s: String (ix, s) = (1, "Two")
That’s such a convenient thing to do that Swift lets you do it in one line, declaring and initializing multiple variables simultaneously:
var (ix, s) = (1, "Two") // can use let or var here
To ignore one of the assigned values, use an underscore to represent it in the receiving tuple:
let pair = (1, "Two") let (_, s) = pair // now s is "Two"
Assigning variable values to one another through a tuple swaps them safely:
var s1 = "hello" var s2 = "world" (s1, s2) = (s2, s1) // now s1 is "world" and s2 is "hello"
There’s also a global function swap
that swaps values in a more general way.
The enumerated
method lets you walk a sequence with for...in
and receive, on each iteration, each successive element’s index number along with the element itself; this double result comes to you as — you guessed it — a tuple:
let s = "hello" for (ix,c) in s.characters.enumerated() { print("character \(ix) is \(c)") }
I also pointed out earlier that numeric instance methods such as addWithOverflow
return a tuple.
You can refer to the individual elements of a tuple directly, in two ways. The first way is by index number, using a literal number (not a variable value) as the name of a message sent to the tuple with dot-notation:
let pair = (1, "Two") let ix = pair.0 // now ix is 1
If you have a var
reference to a tuple, you can assign into it by the same means:
var pair = (1, "Two") pair.0 = 2 // now pair is (2, "Two")
The second way to access tuple elements is to give them labels. The notation is like that of function parameters, and must appear as part of the explicit or implicit type declaration. Thus, here’s one way to establish tuple element labels:
let pair : (first:Int, second:String) = (1, "Two")
And here’s another way:
let pair = (first:1, second:"Two")
The labels are now part of the type of this value, and travel with it through subsequent assignments. You can then use them as literal messages, just like (and together with) the numeric literals:
var pair = (first:1, second:"Two") let x = pair.first // 1 pair.first = 2 let y = pair.0 // 2
You can assign from a tuple without labels into a corresponding tuple with labels (and vice versa):
let pair = (1, "Two") let pairWithNames : (first:Int, second:String) = pair let ix = pairWithNames.first // 1
You can also pass, or return from a function, a tuple without labels where a corresponding tuple with labels is expected:
func tupleMaker() -> (first:Int, second:String) { return (1, "Two") // no labels here } let ix = tupleMaker().first // 1
If you’re going to be using a certain type of tuple consistently throughout your program, it might be useful to give it a type name. To do so, use Swift’s typealias
keyword. For example, in my LinkSame app I have a Board class describing and manipulating the game layout. The board is a grid of Piece objects. I needed a way to describe positions of the grid. That’s a pair of integers, so I define my own type as a tuple:
class Board { typealias Point = (x:Int, y:Int) // ... }
The advantage of that notation is that it now becomes easy to use Points throughout my code. For example, given a Point, I can fetch the corresponding Piece:
func piece(at p:Point) -> Piece? { let (i,j) = p // ... error-checking goes here ... return self.grid[i][j] }
The Optional object type (an enum) wraps another object of any type. What makes an Optional optional is this: it might wrap another object, but then again it might not. Think of an Optional as being itself a kind of shoebox — a shoebox which can quite legally be empty.
Let’s start by creating an Optional that does wrap an object. Suppose we want an Optional wrapping the String "howdy"
. One way to create it is with the Optional initializer:
var stringMaybe = Optional("howdy")
If we log stringMaybe
to the console with print
, we’ll see an expression identical to the corresponding initializer: Optional("howdy")
.
After that declaration and initialization, stringMaybe
is typed, not as a String, nor as an Optional plain and simple, but as an Optional wrapping a String. This means that any other Optional wrapping a String can be assigned to it — but not an Optional wrapping some other type. This code is legal:
var stringMaybe = Optional("howdy") stringMaybe = Optional("farewell")
This code, however, is not legal:
var stringMaybe = Optional("howdy") stringMaybe = Optional(123) // compile error
Optional(123)
is an Optional wrapping an Int, and you can’t assign an Optional wrapping an Int where an Optional wrapping a String is expected.
Optionals are so important to Swift that special syntax for working with them is baked into the language. The usual way to make an Optional is not to use the Optional initializer (though you can certainly do that), but to assign or pass a value of some type to a reference that is already typed as an Optional wrapping that type. This seems as if it should not be legal — but it is. For example, once stringMaybe
is typed as an Optional wrapping a String, it is legal to assign a String directly to it. The outcome is that the assigned String is wrapped in an Optional for us, automatically:
var stringMaybe = Optional("howdy") stringMaybe = "farewell" // now stringMaybe is Optional("farewell")
We also need a way of typing something explicitly as an Optional wrapping a String. Otherwise, we cannot declare a variable or parameter with an Optional type. Formally, an Optional is a generic, so an Optional wrapping a String is an Optional<String>
(I’ll explain that syntax in Chapter 4). However, you don’t have to write that. The Swift language supports syntactic sugar for expressing an Optional type: use the name of the wrapped type followed by a question mark. For example:
var stringMaybe : String?
Thus I don’t need to use the Optional initializer at all. I can type the variable as an Optional wrapping a String and assign a String into it for wrapping, all in one move:
var stringMaybe : String? = "howdy"
That, in fact, is the normal way to make an Optional in Swift.
Once you’ve got an Optional wrapping a particular type, you can use it wherever an Optional wrapping that type is expected — just like any other value. If a function expects an Optional wrapping a String as its parameter, you can pass stringMaybe
as argument to that parameter:
func optionalExpecter(_ s:String?) {} let stringMaybe : String? = "howdy" optionalExpecter(stringMaybe)
Moreover, where an Optional wrapping a certain type of value is expected, you can pass a value of that wrapped type instead. That’s because parameter passing is just like assignment: an unwrapped value will be wrapped implicitly for you. For example, if a function expects an Optional wrapping a String, you can pass a String argument, which will be wrapped into an Optional in the received parameter:
func optionalExpecter(_ s:String?) { // ... here, s will be an Optional wrapping a String ... print(s) } optionalExpecter("howdy") // console prints: Optional("howdy")
But you cannot do the opposite — you cannot use an Optional wrapping a type where the wrapped type is expected. This won’t compile:
func realStringExpecter(_ s:String) {} let stringMaybe : String? = "howdy" realStringExpecter(stringMaybe) // compile error
The error message reads: “Value of optional type String?
not unwrapped; did you mean to use !
or ?
?” You’re going to be seeing that sort of message a lot in Swift, so get used to it! As that message suggests, if you want to use an Optional where the type of thing it wraps is expected, you must unwrap the Optional — that is, you must reach inside it and retrieve the actual thing that it wraps. Now I’m going to talk about how to do that.
We have seen more than one way to wrap an object in an Optional. But what about the opposite procedure? How do we unwrap an Optional to get at the object wrapped inside it? One way is to use the unwrap operator (or forced unwrap operator), which is a postfixed exclamation mark. For example:
func realStringExpecter(_ s:String) {} let stringMaybe : String? = "howdy" realStringExpecter(stringMaybe!)
In that code, the stringMaybe!
syntax expresses the operation of reaching inside the Optional stringMaybe
, grabbing the wrapped value, and substituting it at that point. Since stringMaybe
is an Optional wrapping a String, the thing inside it is a String. That is exactly what the realStringExpecter
function wants as its parameter! stringMaybe
is an Optional wrapping the String "howdy"
, but stringMaybe!
is the String "howdy"
.
If an Optional wraps a certain type, you cannot send it a message expected by that type. You must unwrap it first. For example, let’s try to get an uppercase version of stringMaybe
:
let stringMaybe : String? = "howdy" let upper = stringMaybe.uppercased() // compile error
The solution is to unwrap stringMaybe
to get at the String inside it. We can do this directly, in place, using the unwrap operator:
let stringMaybe : String? = "howdy" let upper = stringMaybe!.uppercased()
If an Optional is to be used several times where the unwrapped type is expected, and if you’re going to be unwrapping it with the unwrap operator each time, your code can quickly start to look like the dialog from a 1960s Batman comic. For example, in iOS programming, an app’s window is an Optional UIWindow property of the app delegate (self.window
):
// self.window is an Optional wrapping a UIWindow self.window!.rootViewController = RootViewController() self.window!.backgroundColor = UIColor.white self.window!.makeKeyAndVisible()
That sort of thing soon gets old (or silly). One obvious alternative is to assign the unwrapped value once to a variable of the wrapped type and then use that variable:
// self.window is an Optional wrapping a UIWindow let window = self.window! // now window (not self.window) is a UIWindow, not an Optional window.rootViewController = RootViewController() window.backgroundColor = UIColor.white window.makeKeyAndVisible()
However, there’s another way, as I shall now explain.
Swift provides another way of using an Optional where the wrapped type is expected: you can declare the Optional type as being implicitly unwrapped. An implicitly unwrapped Optional is an Optional, but the compiler permits some special magic associated with it: its value can be used directly where the wrapped type is expected. You can unwrap an implicitly unwrapped Optional explicitly, but you don’t have to, because it will be unwrapped for you, automatically, if you try to use it where the wrapped type is expected. As with Optional, Swift provides syntactic sugar for expressing an implicitly unwrapped Optional type. Just as an Optional wrapping a String can be expressed as String?
, an implicitly unwrapped Optional wrapping a String can be expressed as String!
. For example:
func realStringExpecter(_ s:String) {} var stringMaybe : String! = "howdy" realStringExpecter(stringMaybe) // no problem
Bear in mind that an implicitly unwrapped Optional is still an Optional. It’s just a convenience. By declaring something as an implicitly unwrapped Optional, you are asking the compiler, if you happen to use this value where the wrapped type is expected, to forgive you and to unwrap the value for you.
As far as their values are concerned, a normal Optional wrapping a certain type (such as a String?
) and an implicitly unwrapped Optional wrapping that same type (such as a String!
) are considered interchangeable: you can pass either one where either one is expected.
In Swift 3, however, an implicitly unwrapped Optional type is not really a distinct type; it is merely an Optional marked in a special way that allows it to be used where the unwrapped type is expected. As a result, certain behaviors that were previously legal are no longer permitted. For example, implicit unwrapping does not propagate by assignment. Here’s a case in point.
If self
is a UIViewController, then self.view
is typed as UIView!
. As a result, this expression is legal:
self.view.addSubview(v)
But this is not legal:
let mainview = self.view mainview.addSubview(v) // compile error
The problem is that, although self.view
is an implicitly unwrapped Optional wrapping a UIView, mainview
is a normal Optional wrapping a UIView, and so it would have to be unwrapped explicitly before you could send it the addSubview
message. Alternatively, you could unwrap the implicitly unwrapped Optional explicitly at the outset:
let mainview = self.view! mainview.addSubview(v)
I have talked so far about Optionals that contain a wrapped value. But what about an Optional that doesn’t contain any wrapped value? Such an Optional is, as I’ve already said, a perfectly legal entity; that, indeed, is the whole point of Optionals.
You are going to need a way to ask whether an Optional contains a wrapped value, and a way to specify an Optional without a wrapped value. Swift makes both of those things easy, through the use of a special keyword, nil
:
nil
. If the test succeeds, the Optional is empty. An empty Optional is also reported in the console as nil
.nil
where the Optional type is expected. The result is an Optional of the expected type, containing no wrapped value.For example:
var stringMaybe : String? = "Howdy" print(stringMaybe) // Optional("Howdy") if stringMaybe == nil { print("it is empty") // does not print } stringMaybe = nil print(stringMaybe) // nil if stringMaybe == nil { print("it is empty") // prints }
The magic word nil
lets you express the concept, “an Optional wrapping the appropriate type, but not actually containing any object of that type.” Clearly, that’s very convenient magic; you’ll want to take advantage of it. It is very important to understand, however, that it is magic: nil
in Swift is not a thing and is not a value. It is a shorthand. It is natural to think and speak as if this shorthand were real. For example, I will say that something “is nil
.” But in reality, nothing “is nil
”; nil
isn’t a thing. What I really mean is that this thing is equatable with nil
, because it is an Optional not wrapping anything. (I’ll explain in Chapter 4 how nil
, and Optionals in general, really work.)
Because a variable typed as an Optional can be nil
, Swift follows a special initialization rule: a variable (var
) typed as an Optional is nil
, automatically. This is legal:
func optionalExpecter(_ s:String?) {} var stringMaybe : String? optionalExpecter(stringMaybe)
That code is interesting because it looks as if it should be illegal. We declared a variable stringMaybe
, but we never assigned it a value. Nevertheless we are now passing it around as if it were an actual thing. That’s because it is an actual thing. This variable has been implicitly initialized — to nil
. A variable (var
) typed as an Optional is the only sort of variable that gets implicit initialization in Swift.
We come now to perhaps the most important rule in all of Swift: You cannot unwrap an Optional containing nothing (an Optional equatable with nil
). Such an Optional contains nothing; there’s nothing to unwrap. Like Oakland, there’s no there there. In fact, explicitly unwrapping an Optional containing nothing will crash your program at runtime:
var stringMaybe : String? let s = stringMaybe! // crash
The crash message reads: “Fatal error: unexpectedly found nil
while unwrapping an Optional value.” Get used to it, because you’re going to be seeing it a lot. This is an easy mistake to make. Unwrapping an Optional that contains no value is, in fact, probably the most common way to crash a Swift program. You should look upon this kind of crash as a blessing. Very often, in fact, you will want to crash if your Optional contains no value, because it should contain a value, and the fact that it doesn’t indicates that you’ve made a mistake elsewhere.
In the long run, however, crashing is bad. To eliminate this kind of crash, you need to ensure that your Optional contains a value, and don’t unwrap it if it doesn’t! Ensuring that an Optional contains a value before attempting to unwrap it is clearly a very important thing to do. Accordingly, Swift provides several convenient ways of doing it. I’ll describe some of them now, and I’ll discuss others in Chapter 5.
One obvious approach is to test your Optional against nil
explicitly before you unwrap it:
var stringMaybe : String? // ... stringMaybe might be assigned a real value here ... if stringMaybe != nil { let s = stringMaybe! // ... }
But there’s a more elegant way, as I shall now explain.
A common situation is that you want to send a message to the value wrapped inside an Optional. To do so, you can unwrap the Optional in place. I gave an example earlier:
let stringMaybe : String? = "howdy" let upper = stringMaybe!.uppercased()
That form of code is called an Optional chain. In the middle of a chain of dot-notation, you have unwrapped an Optional.
You cannot send a message to an Optional without unwrapping it. Optionals themselves don’t respond to any messages. (Well, they do respond to some messages, but very few, and you are unlikely to use them — and in any case they are not the messages to which the thing inside them responds.) If you try to send to an Optional a message intended for the thing inside it, you will get an error message from the compiler:
let stringMaybe : String? = "howdy" let upper = stringMaybe.uppercased() // compile error
We have already seen, however, that if you unwrap an Optional that contains no wrapped object, you’ll crash. So what if you’re not sure whether this Optional contains a wrapped object? How can you send a message to an Optional in that situation? Swift provides a special shorthand for exactly this purpose. To send a message safely to an Optional that might be empty, you can unwrap the Optional optionally. To do so, unwrap the Optional with the question mark postfix operator instead of the exclamation mark:
var stringMaybe : String? // ... stringMaybe might be assigned a real value here ... let upper = stringMaybe?.uppercased()
That’s an Optional chain in which you used a question mark to unwrap the Optional. By using that notation, you have unwrapped the Optional optionally — meaning conditionally. The condition in question is one of safety; a test for nil
is performed for us. Our code means: “If stringMaybe
contains a String, unwrap it and send it the uppercased
message. If it doesn’t (that is, if it equates to nil
), do not unwrap it and do not send it any messages!”
Such code is a double-edged sword. On the one hand, if stringMaybe
is nil
, you won’t crash at runtime. On the other hand, if stringMaybe
is nil
, that line of code won’t do anything useful — you won’t get any uppercase string.
But now there’s a new question. In that code, we initialized a variable upper
to an expression that involves sending the uppercased
message. Now it turns out that the uppercased
message might not even be sent. So what, exactly, is upper
initialized to?
To handle this situation, Swift has a special rule. If an Optional chain contains an optionally unwrapped Optional, and if this Optional chain produces a value, that value is itself wrapped in an Optional. Thus, upper
is typed as an Optional wrapping a String. This works brilliantly, because it covers both possible cases. Let’s say, first, that stringMaybe
contains a String:
var stringMaybe : String? stringMaybe = "howdy" let upper = stringMaybe?.uppercased() // upper is a String?
After that code, upper
is not a String; it is not "HOWDY"
. It is an Optional wrapping "HOWDY"
! On the other hand, if the attempt to unwrap the Optional fails, the Optional chain can return nil
instead:
var stringMaybe : String? let upper = stringMaybe?.uppercased() // upper is a nil String?
Unwrapping an Optional optionally in this way is elegant and safe; even if stringMaybe
is nil
, we won’t crash at runtime. On the other hand, we’ve ended up with yet another Optional on our hands! upper
is typed as an Optional wrapping a String, and in order to use that String, we’re going to have to unwrap upper
. And we don’t know whether upper
is nil
, so we have exactly the same problem we had before — we need to make sure that we unwrap upper
safely, and that we don’t accidentally unwrap an empty Optional.
Longer Optional chains are legal. No matter how many Optionals are unwrapped in the course of the chain, if any of them is unwrapped optionally, the entire expression produces an Optional wrapping the type it would have produced if the Optionals were unwrapped normally, and is free to fail safely at any point along the way. For example:
// self is a UIViewController let f = self.view.window?.rootViewController?.view.frame
The frame
property of a view is a CGRect. But after that code, f
is not a CGRect. It’s an Optional wrapping a CGRect. If any of the optional unwrapping along the chain fails (because the Optional we propose to unwrap is nil
), f
will be nil
to indicate failure.
(Observe that the preceding code does not end up nesting Optionals; it doesn’t produce a CGRect wrapped in an Optional wrapped in an Optional, merely because there are two Optionals being optionally unwrapped in the chain! However, it is possible, for other reasons, to end up with an Optional wrapped in an Optional. I’ll give an example in Chapter 4.)
If a function call returns an Optional, you can unwrap the result and use it. You don’t necessarily have to capture the result in order to do that; you can unwrap it in place, by putting an exclamation mark or a question mark after the function call (that is, after the closing parenthesis). That’s really no different from what we’ve been doing all along, except that instead of an Optional property or variable, this is a function call that returns an Optional. For example:
class Dog { var noise : String? func speak() -> String? { return self.noise } } let d = Dog() let bigname = d.speak()?.uppercased()
After that, don’t forget, bigname
is not a String — it’s an Optional wrapping a String.
You can also assign safely into an Optional chain. If any of the optionally unwrapped Optionals in the chain turns out to be nil
, nothing happens:
// self is a UIViewController self.navigationController?.hidesBarsOnTap = true
A view controller might or might not have a navigation controller, so its navigationController
property is an Optional. In that code, we are setting our navigation controller’s hidesBarsOnTap
property safely; if we happen to have no navigation controller, no harm is done — because nothing happens.
When assigning into an Optional chain, if you also want to know whether the assignment succeeded, you can capture the result of the assignment as an Optional wrapping a Void and test it for nil
. For example:
let ok : Void? = self.navigationController?.hidesBarsOnTap = true
Now, if ok
is not nil
, self.navigationController
was safely unwrapped and the assignment succeeded.
The !
and ?
postfix operators, which respectively unconditionally and conditionally unwrap an Optional, have basically nothing to do with the !
and ?
used with type names as syntactic sugar for expressing Optional types (such as String?
to mean an Optional wrapping a String, and String!
to mean an implicitly unwrapped Optional wrapping a String). The outward similarity has confused many a beginner.
In an equality comparison with something other than nil
, an Optional gets special treatment: the wrapped value, not the Optional itself, is compared. So, for example, this works:
let s : String? = "Howdy" if s == "Howdy" { // ... they _are_ equal!
That shouldn’t work — how can an Optional be the same as a String? — but it does. Instead of comparing the Optional itself with "Howdy"
, Swift automagically (and safely) compares its wrapped value (if there is one) with "Howdy"
, and the comparison succeeds. If the wrapped value is not "Howdy"
, the comparison fails. If there is no wrapped value (s
is nil
), the comparison fails too — safely! Thus, you can compare s
to nil
or to a String, and the comparison works correctly in all cases.
The same, however, is not true for an inequality comparison, using the greater-than and less-than operators:
let i : Int? = 2 if i < 3 { // compile error
Such code was legal in Swift 2 and before, but in Swift 3 it isn’t, partly because the implementation was buggy, and partly because such an expression might represent a programming mistake that the compiler couldn’t catch. The loss is not a serious one, as you can always unwrap safely and perform the comparison directly on the unwrapped value:
if i != nil && i! < 3 { // ... it _is_ less
Do not compare an implicitly unwrapped Optional with anything; you can crash at runtime (and, as of this writing, you can even crash the compiler).
Now that you know how to use an Optional, you are probably wondering why to use an Optional. Why does Swift have Optionals at all? What are they good for?
One very important purpose of Optionals is to provide interchange of object values with Objective-C. In Objective-C, any object reference can be nil
. You thus need a way to send nil
to Objective-C and to receive nil
from Objective-C. Swift Optionals provide your only way to do that.
Swift will typically assist you by a judicious use of appropriate types in the Cocoa APIs. For example, consider a UIView’s backgroundColor
property. It’s a UIColor, but it can be nil
, and you are allowed to set it to nil
. Thus, it is typed as a UIColor?
. You don’t need to work directly with Optionals in order to set such a value! Remember, assigning the wrapped type to an Optional is legal, as the assigned value will be wrapped for you. Thus, you can set myView.backgroundColor
to a UIColor — or to nil
. But if you get a UIView’s backgroundColor
, you now have an Optional wrapping a UIColor, and you must be conscious of this fact, for all the reasons I’ve already discussed: if you’re not, surprising things can happen:
let v = UIView() let c = v.backgroundColor let c2 = c.withAlphaComponent(0.5) // compile error
You’re trying to send the withAlphaComponent
message to c
, as if it were a UIColor. It isn’t a UIColor. It’s an Optional wrapping a UIColor. Xcode will try to help you in this situation; if you use code completion to enter the name of the withAlphaComponent
method, Xcode will insert a question mark after c
, thus (optionally) unwrapping the Optional and giving you legal code:
let v = UIView() let c = v.backgroundColor let c2 = c?.withAlphaComponent(0.5)
In the vast majority of situations, however, a Cocoa object type will not be marked as an Optional. That’s because, although in theory it could be nil
(because any Objective-C object reference can be nil
), in practice it won’t be. Swift thus saves you a step by treating the value as the object type itself. This magic is performed by hand-tweaking the Cocoa APIs (also called auditing). In the very first public version of Swift (in June of 2014), all object values received from Cocoa were in fact typed as Optionals (usually implicitly unwrapped Optionals). But then Apple embarked on the massive project of hand-tweaking the APIs to eliminate Optionals that didn’t need to be Optionals, and in Swift 3 and Xcode 8, that project is essentially complete.
Another important use of Optionals is to defer initialization of an instance property. If a variable (declared with var
) is typed as an Optional, it has a value even if you don’t initialize it — namely nil
. That comes in very handy in situations where you know something will have a value, but not right away. A typical example in real-life iOS programming is an outlet, which is a reference to something in your interface such as a button:
class ViewController: UIViewController { @IBOutlet var myButton: UIButton! // ... }
Ignore, for now, the @IBOutlet
designation, which is an internal hint to Xcode. The important thing is that this property, myButton
, won’t have a value when our ViewController instance first comes into existence, but shortly thereafter the view controller’s view will be loaded and myButton
will be set so that it points to an actual UIButton object in the interface. Therefore, the variable is typed as an implicitly unwrapped Optional. It’s an Optional because we need a placeholder value (namely nil
) for myButton
when the ViewController instance first comes into existence. It’s implicitly unwrapped so that in our code, once self.myButton
has been assigned a UIButton value, we can treat it as a reference to an actual UIButton, passing through the Optional without noticing that it is an Optional.
A closely related situation is when a variable, again typically an instance property, represents data that will take time to acquire. For example, in my Albumen app, as we launch, I create an instance of my root view controller. I also want to gather a bunch of data about the user’s music library and store that data in instance properties of the root view controller instance. But gathering that data will take time. Therefore I must instantiate the root view controller first and gather the data later, because if we pause to gather the data before instantiating the root view controller, the app will take too long to launch — the delay will be perceptible, and we might even crash (because iOS forbids long launch times). Therefore the data properties are all typed as Optionals; they are nil
until the data are gathered, at which time they are assigned their “real” values:
class RootViewController : UITableViewController { var albums : [MPMediaItemCollection]! // initialized to nil // ...
Finally, one of the most important uses of Optionals is to permit a value to be marked as empty or erroneous. The preceding code is a good illustration. When my Albumen app launches, it displays a table listing all the user’s music albums. At launch time, however, that data has not yet been gathered. My table-display code tests albums
to see whether it’s nil
and, if it is, displays an empty table. After gathering the data, I tell my table to display its data again. This time, the table-display code finds that albums
is not nil
, but rather consists of actual data — and it now displays that data. The use of an Optional allows one and the same value, albums
, to store the data or to state that there is no data.
Many built-in Swift functions use an Optional in a similar way. For example:
let arr = [1,2,3] let ix = arr.index(of:4) if ix == nil { // ...
Swift’s index(of:)
method returns an Optional because the object sought might not be present, in which case it has no index at all. The type returned cannot be an Int, because there is no Int value that can be taken to mean, “I didn’t find this object at all.” Returning an Optional solves the problem neatly: nil
means “I didn’t find the object,” and otherwise the actual Int result is sitting there wrapped up in the Optional.
Swift is cleverer than Objective-C in this regard. If a reference is an object, Objective-C can return nil
to report failure; but not everything in Objective-C is an object. Thus, many important Cocoa methods do return a special value to indicate failure, and you have to know this and remember to test for it.
For example, if you call NSArray’s index(of:)
(as opposed to Swift Array’s index(of:)
), the result is an Int, not an Optional wrapping an Int. To indicate that the object wasn’t found at all, Cocoa must fall back on returning an Int with a special meaning — namely, NSNotFound
, which is actually just a very large negative number. And you have to remember to test for this:
let arr = [1,2,3] let ix = (arr as NSArray).indexOfObject(4) if ix == NSNotFound { // ...
In some cases, Swift will help you by bridging intelligently from Objective-C. For example, NSString’s range(of:)
might not find the given substring in the target string; in that case, it returns an NSRange whose length
is zero and whose location
is (you guessed it) NSNotFound. Fortunately, a knowledge of this fact is built into the Swift bridge to the Cocoa API: Swift types the returned value as an Optional wrapping a Range, and if range(of:)
does return an NSRange whose location
is NSNotFound
, Swift neatly expresses it as nil
.