Initial commit
This commit is contained in:
1487
skills/programming-swift/LanguageGuide/AccessControl.md
Normal file
1487
skills/programming-swift/LanguageGuide/AccessControl.md
Normal file
File diff suppressed because it is too large
Load Diff
1572
skills/programming-swift/LanguageGuide/AdvancedOperators.md
Normal file
1572
skills/programming-swift/LanguageGuide/AdvancedOperators.md
Normal file
File diff suppressed because it is too large
Load Diff
1523
skills/programming-swift/LanguageGuide/AutomaticReferenceCounting.md
Normal file
1523
skills/programming-swift/LanguageGuide/AutomaticReferenceCounting.md
Normal file
File diff suppressed because it is too large
Load Diff
1242
skills/programming-swift/LanguageGuide/BasicOperators.md
Normal file
1242
skills/programming-swift/LanguageGuide/BasicOperators.md
Normal file
File diff suppressed because it is too large
Load Diff
721
skills/programming-swift/LanguageGuide/ClassesAndStructures.md
Normal file
721
skills/programming-swift/LanguageGuide/ClassesAndStructures.md
Normal file
@@ -0,0 +1,721 @@
|
||||
# Structures and Classes
|
||||
|
||||
Model custom types that encapsulate data.
|
||||
|
||||
*Structures* and *classes* are general-purpose,
|
||||
flexible constructs that become the building blocks of your program's code.
|
||||
You define properties and methods to add functionality to your structures and classes
|
||||
using the same syntax you use to define constants, variables, and functions.
|
||||
|
||||
Unlike other programming languages,
|
||||
Swift doesn't require you to create separate interface and implementation files
|
||||
for custom structures and classes.
|
||||
In Swift, you define a structure or class in a single file,
|
||||
and the external interface to that class or structure is
|
||||
automatically made available for other code to use.
|
||||
|
||||
> Note: An instance of a class is traditionally known as an *object*.
|
||||
> However, Swift structures and classes
|
||||
> are much closer in functionality than in other languages,
|
||||
> and much of this chapter describes functionality that applies to
|
||||
> instances of *either* a class or a structure type.
|
||||
> Because of this, the more general term *instance* is used.
|
||||
|
||||
## Comparing Structures and Classes
|
||||
|
||||
Structures and classes in Swift have many things in common.
|
||||
Both can:
|
||||
|
||||
- Define properties to store values
|
||||
- Define methods to provide functionality
|
||||
- Define subscripts to provide access to their values using subscript syntax
|
||||
- Define initializers to set up their initial state
|
||||
- Be extended to expand their functionality beyond a default implementation
|
||||
- Conform to protocols to provide standard functionality of a certain kind
|
||||
|
||||
For more information, see
|
||||
<doc:Properties>, <doc:Methods>, <doc:Subscripts>, <doc:Initialization>,
|
||||
<doc:Extensions>, and <doc:Protocols>.
|
||||
|
||||
Classes have additional capabilities that structures don't have:
|
||||
|
||||
- Inheritance enables one class to inherit the characteristics of another.
|
||||
- Type casting enables you to check and interpret the type of a class instance at runtime.
|
||||
- Deinitializers enable an instance of a class to free up any resources it has assigned.
|
||||
- Reference counting allows more than one reference to a class instance.
|
||||
|
||||
For more information, see
|
||||
<doc:Inheritance>, <doc:TypeCasting>, <doc:Deinitialization>,
|
||||
and <doc:AutomaticReferenceCounting>.
|
||||
|
||||
The additional capabilities that classes support
|
||||
come at the cost of increased complexity.
|
||||
As a general guideline,
|
||||
prefer structures because they're easier to reason about,
|
||||
and use classes when they're appropriate or necessary.
|
||||
In practice, this means most of the custom types you define
|
||||
will be structures and enumerations.
|
||||
For a more detailed comparison,
|
||||
see [Choosing Between Structures and Classes](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes).
|
||||
|
||||
> Note: Classes and actors share many of the same characteristics and behaviors.
|
||||
> For information about actors, see <doc:Concurrency>.
|
||||
|
||||
### Definition Syntax
|
||||
|
||||
Structures and classes have a similar definition syntax.
|
||||
You introduce structures with the `struct` keyword
|
||||
and classes with the `class` keyword.
|
||||
Both place their entire definition within a pair of braces:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
// structure definition goes here
|
||||
}
|
||||
class SomeClass {
|
||||
// class definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> struct SomeStructure {
|
||||
// structure definition goes here
|
||||
}
|
||||
-> class SomeClass {
|
||||
// class definition goes here
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
> Note: Whenever you define a new structure or class,
|
||||
> you define a new Swift type.
|
||||
> Give types `UpperCamelCase` names
|
||||
> (such as `SomeStructure` and `SomeClass` here)
|
||||
> to match the capitalization of standard Swift types
|
||||
> (such as `String`, `Int`, and `Bool`).
|
||||
> Give properties and methods `lowerCamelCase` names
|
||||
> (such as `frameRate` and `incrementCount`)
|
||||
> to differentiate them from type names.
|
||||
|
||||
Here's an example of a structure definition and a class definition:
|
||||
|
||||
```swift
|
||||
struct Resolution {
|
||||
var width = 0
|
||||
var height = 0
|
||||
}
|
||||
class VideoMode {
|
||||
var resolution = Resolution()
|
||||
var interlaced = false
|
||||
var frameRate = 0.0
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> struct Resolution {
|
||||
var width = 0
|
||||
var height = 0
|
||||
}
|
||||
-> class VideoMode {
|
||||
var resolution = Resolution()
|
||||
var interlaced = false
|
||||
var frameRate = 0.0
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The example above defines a new structure called `Resolution`,
|
||||
to describe a pixel-based display resolution.
|
||||
This structure has two stored properties called `width` and `height`.
|
||||
Stored properties are constants or variables that are bundled up and stored
|
||||
as part of the structure or class.
|
||||
These two properties are inferred to be of type `Int`
|
||||
by setting them to an initial integer value of `0`.
|
||||
|
||||
The example above also defines a new class called `VideoMode`,
|
||||
to describe a specific video mode for video display.
|
||||
This class has four variable stored properties.
|
||||
The first, `resolution`, is initialized with a new `Resolution` structure instance,
|
||||
which infers a property type of `Resolution`.
|
||||
For the other three properties,
|
||||
new `VideoMode` instances will be initialized with
|
||||
an `interlaced` setting of `false` (meaning “noninterlaced video”),
|
||||
a playback frame rate of `0.0`,
|
||||
and an optional `String` value called `name`.
|
||||
The `name` property is automatically given a default value of `nil`,
|
||||
or “no `name` value”, because it's of an optional type.
|
||||
|
||||
### Structure and Class Instances
|
||||
|
||||
The `Resolution` structure definition and the `VideoMode` class definition
|
||||
only describe what a `Resolution` or `VideoMode` will look like.
|
||||
They themselves don't describe a specific resolution or video mode.
|
||||
To do that, you need to create an instance of the structure or class.
|
||||
|
||||
The syntax for creating instances is very similar for both structures and classes:
|
||||
|
||||
```swift
|
||||
let someResolution = Resolution()
|
||||
let someVideoMode = VideoMode()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> let someResolution = Resolution()
|
||||
-> let someVideoMode = VideoMode()
|
||||
```
|
||||
-->
|
||||
|
||||
Structures and classes both use initializer syntax for new instances.
|
||||
The simplest form of initializer syntax uses the type name of the class or structure
|
||||
followed by empty parentheses, such as `Resolution()` or `VideoMode()`.
|
||||
This creates a new instance of the class or structure,
|
||||
with any properties initialized to their default values.
|
||||
Class and structure initialization is described in more detail
|
||||
in <doc:Initialization>.
|
||||
|
||||
<!--
|
||||
TODO: note that you can only use the default constructor if you provide default values
|
||||
for all properties on a structure or class.
|
||||
-->
|
||||
|
||||
### Accessing Properties
|
||||
|
||||
You can access the properties of an instance using *dot syntax*.
|
||||
In dot syntax, you write the property name immediately after the instance name,
|
||||
separated by a period (`.`), without any spaces:
|
||||
|
||||
```swift
|
||||
print("The width of someResolution is \(someResolution.width)")
|
||||
// Prints "The width of someResolution is 0".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> print("The width of someResolution is \(someResolution.width)")
|
||||
<- The width of someResolution is 0
|
||||
```
|
||||
-->
|
||||
|
||||
In this example,
|
||||
`someResolution.width` refers to the `width` property of `someResolution`,
|
||||
and returns its default initial value of `0`.
|
||||
|
||||
You can drill down into subproperties,
|
||||
such as the `width` property in the `resolution` property of a `VideoMode`:
|
||||
|
||||
```swift
|
||||
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
// Prints "The width of someVideoMode is 0".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> print("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
<- The width of someVideoMode is 0
|
||||
```
|
||||
-->
|
||||
|
||||
You can also use dot syntax to assign a new value to a variable property:
|
||||
|
||||
```swift
|
||||
someVideoMode.resolution.width = 1280
|
||||
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
// Prints "The width of someVideoMode is now 1280".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> someVideoMode.resolution.width = 1280
|
||||
-> print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
<- The width of someVideoMode is now 1280
|
||||
```
|
||||
-->
|
||||
|
||||
### Memberwise Initializers for Structure Types
|
||||
|
||||
All structures have an automatically generated *memberwise initializer*,
|
||||
which you can use to initialize the member properties of new structure instances.
|
||||
Initial values for the properties of the new instance
|
||||
can be passed to the memberwise initializer by name:
|
||||
|
||||
```swift
|
||||
let vga = Resolution(width: 640, height: 480)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> let vga = Resolution(width: 640, height: 480)
|
||||
```
|
||||
-->
|
||||
|
||||
Unlike structures, class instances don't receive a default memberwise initializer.
|
||||
Initializers are described in more detail in <doc:Initialization>.
|
||||
|
||||
<!--
|
||||
- test: `classesDontHaveADefaultMemberwiseInitializer`
|
||||
|
||||
```swifttest
|
||||
-> class C { var x = 0, y = 0 }
|
||||
-> let c = C(x: 1, y: 1)
|
||||
!$ error: argument passed to call that takes no arguments
|
||||
!! let c = C(x: 1, y: 1)
|
||||
!! ^~~~~~~~~~~~
|
||||
!!-
|
||||
```
|
||||
-->
|
||||
|
||||
## Structures and Enumerations Are Value Types
|
||||
|
||||
A *value type* is a type whose value is *copied*
|
||||
when it's assigned to a variable or constant,
|
||||
or when it's passed to a function.
|
||||
|
||||
<!--
|
||||
Alternate definition:
|
||||
A type has value semantics when
|
||||
mutation of one variable of that type
|
||||
can never be observed through a different variable of the same type.
|
||||
-->
|
||||
|
||||
You've actually been using value types extensively throughout the previous chapters.
|
||||
In fact, all of the basic types in Swift ---
|
||||
integers, floating-point numbers, Booleans, strings, arrays and dictionaries ---
|
||||
are value types, and are implemented as structures behind the scenes.
|
||||
|
||||
All structures and enumerations are value types in Swift.
|
||||
This means that any structure and enumeration instances you create ---
|
||||
and any value types they have as properties ---
|
||||
are always copied when they're passed around in your code.
|
||||
|
||||
> Note: Collections defined by the Swift standard library
|
||||
> like arrays, dictionaries, and strings
|
||||
> use an optimization to reduce the performance cost of copying.
|
||||
> Instead of making a copy immediately,
|
||||
> these collections share the memory where the elements are stored
|
||||
> between the original instance and any copies.
|
||||
> If one of the copies of the collection is modified,
|
||||
> the elements are copied just before the modification.
|
||||
> The behavior you see in your code
|
||||
> is always as if a copy took place immediately.
|
||||
|
||||
Consider this example, which uses the `Resolution` structure from the previous example:
|
||||
|
||||
```swift
|
||||
let hd = Resolution(width: 1920, height: 1080)
|
||||
var cinema = hd
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> let hd = Resolution(width: 1920, height: 1080)
|
||||
-> var cinema = hd
|
||||
```
|
||||
-->
|
||||
|
||||
This example declares a constant called `hd`
|
||||
and sets it to a `Resolution` instance initialized with
|
||||
the width and height of full HD video
|
||||
(1920 pixels wide by 1080 pixels high).
|
||||
|
||||
It then declares a variable called `cinema`
|
||||
and sets it to the current value of `hd`.
|
||||
Because `Resolution` is a structure,
|
||||
a *copy* of the existing instance is made,
|
||||
and this new copy is assigned to `cinema`.
|
||||
Even though `hd` and `cinema` now have the same width and height,
|
||||
they're two completely different instances behind the scenes.
|
||||
|
||||
Next, the `width` property of `cinema` is amended to be
|
||||
the width of the slightly wider 2K standard used for digital cinema projection
|
||||
(2048 pixels wide and 1080 pixels high):
|
||||
|
||||
```swift
|
||||
cinema.width = 2048
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> cinema.width = 2048
|
||||
```
|
||||
-->
|
||||
|
||||
Checking the `width` property of `cinema`
|
||||
shows that it has indeed changed to be `2048`:
|
||||
|
||||
```swift
|
||||
print("cinema is now \(cinema.width) pixels wide")
|
||||
// Prints "cinema is now 2048 pixels wide".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> print("cinema is now \(cinema.width) pixels wide")
|
||||
<- cinema is now 2048 pixels wide
|
||||
```
|
||||
-->
|
||||
|
||||
However, the `width` property of the original `hd` instance
|
||||
still has the old value of `1920`:
|
||||
|
||||
```swift
|
||||
print("hd is still \(hd.width) pixels wide")
|
||||
// Prints "hd is still 1920 pixels wide".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> print("hd is still \(hd.width) pixels wide")
|
||||
<- hd is still 1920 pixels wide
|
||||
```
|
||||
-->
|
||||
|
||||
When `cinema` was given the current value of `hd`,
|
||||
the *values* stored in `hd` were copied into the new `cinema` instance.
|
||||
The end result was two completely separate instances
|
||||
that contained the same numeric values.
|
||||
However, because they're separate instances,
|
||||
setting the width of `cinema` to `2048`
|
||||
doesn't affect the width stored in `hd`,
|
||||
as shown in the figure below:
|
||||
|
||||

|
||||
|
||||
The same behavior applies to enumerations:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case north, south, east, west
|
||||
mutating func turnNorth() {
|
||||
self = .north
|
||||
}
|
||||
}
|
||||
var currentDirection = CompassPoint.west
|
||||
let rememberedDirection = currentDirection
|
||||
currentDirection.turnNorth()
|
||||
|
||||
print("The current direction is \(currentDirection)")
|
||||
print("The remembered direction is \(rememberedDirection)")
|
||||
// Prints "The current direction is north".
|
||||
// Prints "The remembered direction is west".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> enum CompassPoint {
|
||||
case north, south, east, west
|
||||
mutating func turnNorth() {
|
||||
self = .north
|
||||
}
|
||||
}
|
||||
-> var currentDirection = CompassPoint.west
|
||||
-> let rememberedDirection = currentDirection
|
||||
-> currentDirection.turnNorth()
|
||||
|
||||
-> print("The current direction is \(currentDirection)")
|
||||
-> print("The remembered direction is \(rememberedDirection)")
|
||||
<- The current direction is north
|
||||
<- The remembered direction is west
|
||||
```
|
||||
-->
|
||||
|
||||
When `rememberedDirection` is assigned the value of `currentDirection`,
|
||||
it's actually set to a copy of that value.
|
||||
Changing the value of `currentDirection` thereafter doesn't affect
|
||||
the copy of the original value that was stored in `rememberedDirection`.
|
||||
|
||||
<!--
|
||||
TODO: Should I give an example of passing a value type to a function here?
|
||||
-->
|
||||
|
||||
## Classes Are Reference Types
|
||||
|
||||
Unlike value types, *reference types* are *not* copied
|
||||
when they're assigned to a variable or constant,
|
||||
or when they're passed to a function.
|
||||
Rather than a copy, a reference to the same existing instance is used.
|
||||
|
||||
Here's an example, using the `VideoMode` class defined above:
|
||||
|
||||
```swift
|
||||
let tenEighty = VideoMode()
|
||||
tenEighty.resolution = hd
|
||||
tenEighty.interlaced = true
|
||||
tenEighty.name = "1080i"
|
||||
tenEighty.frameRate = 25.0
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> let tenEighty = VideoMode()
|
||||
-> tenEighty.resolution = hd
|
||||
-> tenEighty.interlaced = true
|
||||
-> tenEighty.name = "1080i"
|
||||
-> tenEighty.frameRate = 25.0
|
||||
```
|
||||
-->
|
||||
|
||||
This example declares a new constant called `tenEighty`
|
||||
and sets it to refer to a new instance of the `VideoMode` class.
|
||||
The video mode is assigned a copy of the HD resolution of `1920` by `1080` from before.
|
||||
It's set to be interlaced,
|
||||
its name is set to `"1080i"`,
|
||||
and its frame rate is set to `25.0` frames per second.
|
||||
|
||||
Next, `tenEighty` is assigned to a new constant, called `alsoTenEighty`,
|
||||
and the frame rate of `alsoTenEighty` is modified:
|
||||
|
||||
```swift
|
||||
let alsoTenEighty = tenEighty
|
||||
alsoTenEighty.frameRate = 30.0
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> let alsoTenEighty = tenEighty
|
||||
-> alsoTenEighty.frameRate = 30.0
|
||||
```
|
||||
-->
|
||||
|
||||
Because classes are reference types,
|
||||
`tenEighty` and `alsoTenEighty` actually both refer to the *same* `VideoMode` instance.
|
||||
Effectively, they're just two different names for the same single instance,
|
||||
as shown in the figure below:
|
||||
|
||||

|
||||
|
||||
Checking the `frameRate` property of `tenEighty`
|
||||
shows that it correctly reports the new frame rate of `30.0`
|
||||
from the underlying `VideoMode` instance:
|
||||
|
||||
```swift
|
||||
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
// Prints "The frameRate property of tenEighty is now 30.0".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
<- The frameRate property of tenEighty is now 30.0
|
||||
```
|
||||
-->
|
||||
|
||||
This example also shows how reference types can be harder to reason about.
|
||||
If `tenEighty` and `alsoTenEighty` were far apart in your program's code,
|
||||
it could be difficult to find all the ways that the video mode is changed.
|
||||
Wherever you use `tenEighty`,
|
||||
you also have to think about the code that uses `alsoTenEighty`,
|
||||
and vice versa.
|
||||
In contrast, value types are easier to reason about
|
||||
because all of the code that interacts with the same value
|
||||
is close together in your source files.
|
||||
|
||||
Note that `tenEighty` and `alsoTenEighty` are declared as *constants*,
|
||||
rather than variables.
|
||||
However, you can still change `tenEighty.frameRate` and `alsoTenEighty.frameRate` because
|
||||
the values of the `tenEighty` and `alsoTenEighty` constants themselves don't actually change.
|
||||
`tenEighty` and `alsoTenEighty` themselves don't “store” the `VideoMode` instance ---
|
||||
instead, they both *refer* to a `VideoMode` instance behind the scenes.
|
||||
It's the `frameRate` property of the underlying `VideoMode` that's changed,
|
||||
not the values of the constant references to that `VideoMode`.
|
||||
|
||||
<!--
|
||||
TODO: reiterate here that arrays and dictionaries are value types rather than reference types,
|
||||
and demonstrate what that means for the values they store
|
||||
when they themselves are value types or reference types.
|
||||
Also make a note about what this means for key copying,
|
||||
as per the swift-discuss email thread "Dictionaries and key copying"
|
||||
started by Alex Migicovsky on Mar 1 2014.
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Add discussion about how
|
||||
a struct that has a member of some reference type
|
||||
is itself actually a reference type,
|
||||
and about how you can make a class that's a value type.
|
||||
-->
|
||||
|
||||
### Identity Operators
|
||||
|
||||
Because classes are reference types,
|
||||
it's possible for multiple constants and variables to refer to
|
||||
the same single instance of a class behind the scenes.
|
||||
(The same isn't true for structures and enumerations,
|
||||
because they're always copied when they're assigned to a constant or variable,
|
||||
or passed to a function.)
|
||||
|
||||
<!--
|
||||
- test: `structuresDontSupportTheIdentityOperators`
|
||||
|
||||
```swifttest
|
||||
-> struct S { var x = 0, y = 0 }
|
||||
-> let s1 = S()
|
||||
-> let s2 = S()
|
||||
-> if s1 === s2 { print("s1 === s2") } else { print("s1 !== s2") }
|
||||
!$ error: argument type 'S' expected to be an instance of a class or class-constrained type
|
||||
!! if s1 === s2 { print("s1 === s2") } else { print("s1 !== s2") }
|
||||
!! ^
|
||||
!$ error: argument type 'S' expected to be an instance of a class or class-constrained type
|
||||
!! if s1 === s2 { print("s1 === s2") } else { print("s1 !== s2") }
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `enumerationsDontSupportTheIdentityOperators`
|
||||
|
||||
```swifttest
|
||||
-> enum E { case a, b }
|
||||
-> let e1 = E.a
|
||||
-> let e2 = E.b
|
||||
-> if e1 === e2 { print("e1 === e2") } else { print("e1 !== e2") }
|
||||
!$ error: argument type 'E' expected to be an instance of a class or class-constrained type
|
||||
!! if e1 === e2 { print("e1 === e2") } else { print("e1 !== e2") }
|
||||
!! ^
|
||||
!$ error: argument type 'E' expected to be an instance of a class or class-constrained type
|
||||
!! if e1 === e2 { print("e1 === e2") } else { print("e1 !== e2") }
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
It can sometimes be useful to find out whether two constants or variables refer to
|
||||
exactly the same instance of a class.
|
||||
To enable this, Swift provides two identity operators:
|
||||
|
||||
- Identical to (`===`)
|
||||
- Not identical to (`!==`)
|
||||
|
||||
Use these operators to check whether two constants or variables refer to the same single instance:
|
||||
|
||||
```swift
|
||||
if tenEighty === alsoTenEighty {
|
||||
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
|
||||
}
|
||||
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `ClassesAndStructures`
|
||||
|
||||
```swifttest
|
||||
-> if tenEighty === alsoTenEighty {
|
||||
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
|
||||
}
|
||||
<- tenEighty and alsoTenEighty refer to the same VideoMode instance.
|
||||
```
|
||||
-->
|
||||
|
||||
Note that *identical to* (represented by three equal signs, or `===`)
|
||||
doesn't mean the same thing as *equal to* (represented by two equal signs, or `==`).
|
||||
*Identical to* means that
|
||||
two constants or variables of class type refer to exactly the same class instance.
|
||||
*Equal to* means that
|
||||
two instances are considered equal or equivalent in value,
|
||||
for some appropriate meaning of *equal*, as defined by the type's designer.
|
||||
|
||||
When you define your own custom structures and classes,
|
||||
it's your responsibility to decide what qualifies as two instances being equal.
|
||||
The process of defining your own implementations of the `==` and `!=` operators
|
||||
is described in <doc:AdvancedOperators#Equivalence-Operators>.
|
||||
|
||||
<!--
|
||||
- test: `classesDontGetEqualityByDefault`
|
||||
|
||||
```swifttest
|
||||
-> class C { var x = 0, y = 0 }
|
||||
-> let c1 = C()
|
||||
-> let c2 = C()
|
||||
-> if c1 == c2 { print("c1 == c2") } else { print("c1 != c2") }
|
||||
!$ error: binary operator '==' cannot be applied to two 'C' operands
|
||||
!! if c1 == c2 { print("c1 == c2") } else { print("c1 != c2") }
|
||||
!! ~~ ^ ~~
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `structuresDontGetEqualityByDefault`
|
||||
|
||||
```swifttest
|
||||
-> struct S { var x = 0, y = 0 }
|
||||
-> let s1 = S()
|
||||
-> let s2 = S()
|
||||
-> if s1 == s2 { print("s1 == s2") } else { print("s1 != s2") }
|
||||
!$ error: binary operator '==' cannot be applied to two 'S' operands
|
||||
!! if s1 == s2 { print("s1 == s2") } else { print("s1 != s2") }
|
||||
!! ~~ ^ ~~
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: This needs clarifying with regards to function references.
|
||||
-->
|
||||
|
||||
### Pointers
|
||||
|
||||
If you have experience with C, C++, or Objective-C,
|
||||
you may know that these languages use *pointers* to refer to addresses in memory.
|
||||
A Swift constant or variable that refers to an instance of some reference type
|
||||
is similar to a pointer in C,
|
||||
but isn't a direct pointer to an address in memory,
|
||||
and doesn't require you to write an asterisk (`*`)
|
||||
to indicate that you are creating a reference.
|
||||
Instead, these references are defined like any other constant or variable in Swift.
|
||||
The Swift standard library provides pointer and buffer types
|
||||
that you can use if you need to interact with pointers directly ---
|
||||
see [Manual Memory Management](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management).
|
||||
|
||||
<!--
|
||||
TODO: functions aren't "instances". This needs clarifying.
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Add a justification here to say why this is a good thing.
|
||||
-->
|
||||
|
||||
<!--
|
||||
QUESTION: what's the deal with tuples and reference types / value types?
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
1344
skills/programming-swift/LanguageGuide/Closures.md
Normal file
1344
skills/programming-swift/LanguageGuide/Closures.md
Normal file
File diff suppressed because it is too large
Load Diff
1494
skills/programming-swift/LanguageGuide/CollectionTypes.md
Normal file
1494
skills/programming-swift/LanguageGuide/CollectionTypes.md
Normal file
File diff suppressed because it is too large
Load Diff
1636
skills/programming-swift/LanguageGuide/Concurrency.md
Normal file
1636
skills/programming-swift/LanguageGuide/Concurrency.md
Normal file
File diff suppressed because it is too large
Load Diff
2361
skills/programming-swift/LanguageGuide/ControlFlow.md
Normal file
2361
skills/programming-swift/LanguageGuide/ControlFlow.md
Normal file
File diff suppressed because it is too large
Load Diff
253
skills/programming-swift/LanguageGuide/Deinitialization.md
Normal file
253
skills/programming-swift/LanguageGuide/Deinitialization.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Deinitialization
|
||||
|
||||
Release resources that require custom cleanup.
|
||||
|
||||
A *deinitializer* is called immediately before a class instance is deallocated.
|
||||
You write deinitializers with the `deinit` keyword,
|
||||
similar to how initializers are written with the `init` keyword.
|
||||
Deinitializers are only available on class types.
|
||||
|
||||
## How Deinitialization Works
|
||||
|
||||
Swift automatically deallocates your instances when they're no longer needed,
|
||||
to free up resources.
|
||||
Swift handles the memory management of instances through
|
||||
*automatic reference counting* (*ARC*),
|
||||
as described in <doc:AutomaticReferenceCounting>.
|
||||
Typically you don't need to perform manual cleanup when your instances are deallocated.
|
||||
However, when you are working with your own resources,
|
||||
you might need to perform some additional cleanup yourself.
|
||||
For example, if you create a custom class to open a file and write some data to it,
|
||||
you might need to close the file before the class instance is deallocated.
|
||||
|
||||
Class definitions can have at most one deinitializer per class.
|
||||
The deinitializer doesn't take any parameters
|
||||
and is written without parentheses:
|
||||
|
||||
```swift
|
||||
deinit {
|
||||
// perform the deinitialization
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
>> class Test {
|
||||
-> deinit {
|
||||
// perform the deinitialization
|
||||
}
|
||||
>> }
|
||||
```
|
||||
-->
|
||||
|
||||
Deinitializers are called automatically, just before instance deallocation takes place.
|
||||
You aren't allowed to call a deinitializer yourself.
|
||||
Superclass deinitializers are inherited by their subclasses,
|
||||
and the superclass deinitializer is called automatically at the end of
|
||||
a subclass deinitializer implementation.
|
||||
Superclass deinitializers are always called,
|
||||
even if a subclass doesn't provide its own deinitializer.
|
||||
|
||||
Because an instance isn't deallocated until after its deinitializer is called,
|
||||
a deinitializer can access all properties of the instance it's called on
|
||||
and can modify its behavior based on those properties
|
||||
(such as looking up the name of a file that needs to be closed).
|
||||
|
||||
## Deinitializers in Action
|
||||
|
||||
Here's an example of a deinitializer in action.
|
||||
This example defines two new types, `Bank` and `Player`, for a simple game.
|
||||
The `Bank` class manages a made-up currency,
|
||||
which can never have more than 10,000 coins in circulation.
|
||||
There can only ever be one `Bank` in the game,
|
||||
and so the `Bank` is implemented as a class with type properties and methods
|
||||
to store and manage its current state:
|
||||
|
||||
```swift
|
||||
class Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
|
||||
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receive(coins: Int) {
|
||||
coinsInBank += coins
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
-> class Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
|
||||
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receive(coins: Int) {
|
||||
coinsInBank += coins
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
`Bank` keeps track of the current number of coins it holds with its `coinsInBank` property.
|
||||
It also offers two methods --- `distribute(coins:)` and `receive(coins:)` ---
|
||||
to handle the distribution and collection of coins.
|
||||
|
||||
The `distribute(coins:)` method checks that there are enough coins in the bank before distributing them.
|
||||
If there aren't enough coins,
|
||||
`Bank` returns a smaller number than the number that was requested
|
||||
(and returns zero if no coins are left in the bank).
|
||||
It returns an integer value to indicate the actual number of coins that were provided.
|
||||
|
||||
The `receive(coins:)` method simply adds the received number of coins back into the bank's coin store.
|
||||
|
||||
The `Player` class describes a player in the game.
|
||||
Each player has a certain number of coins stored in their purse at any time.
|
||||
This is represented by the player's `coinsInPurse` property:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var coinsInPurse: Int
|
||||
init(coins: Int) {
|
||||
coinsInPurse = Bank.distribute(coins: coins)
|
||||
}
|
||||
func win(coins: Int) {
|
||||
coinsInPurse += Bank.distribute(coins: coins)
|
||||
}
|
||||
deinit {
|
||||
Bank.receive(coins: coinsInPurse)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
-> class Player {
|
||||
var coinsInPurse: Int
|
||||
init(coins: Int) {
|
||||
coinsInPurse = Bank.distribute(coins: coins)
|
||||
}
|
||||
func win(coins: Int) {
|
||||
coinsInPurse += Bank.distribute(coins: coins)
|
||||
}
|
||||
deinit {
|
||||
Bank.receive(coins: coinsInPurse)
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Each `Player` instance is initialized with a starting allowance of
|
||||
a specified number of coins from the bank during initialization,
|
||||
although a `Player` instance may receive fewer than that number
|
||||
if not enough coins are available.
|
||||
|
||||
The `Player` class defines a `win(coins:)` method,
|
||||
which retrieves a certain number of coins from the bank
|
||||
and adds them to the player's purse.
|
||||
The `Player` class also implements a deinitializer,
|
||||
which is called just before a `Player` instance is deallocated.
|
||||
Here, the deinitializer simply returns all of the player's coins to the bank:
|
||||
|
||||
```swift
|
||||
var playerOne: Player? = Player(coins: 100)
|
||||
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
// Prints "A new player has joined the game with 100 coins".
|
||||
print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// Prints "There are now 9900 coins left in the bank".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
-> var playerOne: Player? = Player(coins: 100)
|
||||
-> print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
<- A new player has joined the game with 100 coins
|
||||
-> print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
<- There are now 9900 coins left in the bank
|
||||
```
|
||||
-->
|
||||
|
||||
A new `Player` instance is created, with a request for 100 coins if they're available.
|
||||
This `Player` instance is stored in an optional `Player` variable called `playerOne`.
|
||||
An optional variable is used here, because players can leave the game at any point.
|
||||
The optional lets you track whether there's currently a player in the game.
|
||||
|
||||
Because `playerOne` is an optional, it's qualified with an exclamation point (`!`)
|
||||
when its `coinsInPurse` property is accessed to print its default number of coins,
|
||||
and whenever its `win(coins:)` method is called:
|
||||
|
||||
```swift
|
||||
playerOne!.win(coins: 2_000)
|
||||
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
|
||||
// Prints "PlayerOne won 2000 coins & now has 2100 coins".
|
||||
print("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
// Prints "The bank now only has 7900 coins left".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
-> playerOne!.win(coins: 2_000)
|
||||
-> print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
|
||||
<- PlayerOne won 2000 coins & now has 2100 coins
|
||||
-> print("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
<- The bank now only has 7900 coins left
|
||||
```
|
||||
-->
|
||||
|
||||
Here, the player has won 2,000 coins.
|
||||
The player's purse now contains 2,100 coins,
|
||||
and the bank has only 7,900 coins left.
|
||||
|
||||
```swift
|
||||
playerOne = nil
|
||||
print("PlayerOne has left the game")
|
||||
// Prints "PlayerOne has left the game".
|
||||
print("The bank now has \(Bank.coinsInBank) coins")
|
||||
// Prints "The bank now has 10000 coins".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `deinitializer`
|
||||
|
||||
```swifttest
|
||||
-> playerOne = nil
|
||||
-> print("PlayerOne has left the game")
|
||||
<- PlayerOne has left the game
|
||||
-> print("The bank now has \(Bank.coinsInBank) coins")
|
||||
<- The bank now has 10000 coins
|
||||
```
|
||||
-->
|
||||
|
||||
The player has now left the game.
|
||||
This is indicated by setting the optional `playerOne` variable to `nil`,
|
||||
meaning “no `Player` instance.”
|
||||
At the point that this happens,
|
||||
the `playerOne` variable's reference to the `Player` instance is broken.
|
||||
No other properties or variables are still referring to the `Player` instance,
|
||||
and so it's deallocated in order to free up its memory.
|
||||
Just before this happens, its deinitializer is called automatically,
|
||||
and its coins are returned to the bank.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
865
skills/programming-swift/LanguageGuide/Enumerations.md
Normal file
865
skills/programming-swift/LanguageGuide/Enumerations.md
Normal file
@@ -0,0 +1,865 @@
|
||||
# Enumerations
|
||||
|
||||
Model custom types that define a list of possible values.
|
||||
|
||||
An *enumeration* defines a common type for a group of related values
|
||||
and enables you to work with those values in a type-safe way within your code.
|
||||
|
||||
If you are familiar with C,
|
||||
you will know that C enumerations assign related names to a set of integer values.
|
||||
Enumerations in Swift are much more flexible,
|
||||
and don't have to provide a value for each case of the enumeration.
|
||||
If a value (known as a *raw* value) is provided for each enumeration case,
|
||||
the value can be a string, a character,
|
||||
or a value of any integer or floating-point type.
|
||||
|
||||
Alternatively, enumeration cases can specify
|
||||
associated values of *any* type to be stored along with each different case value,
|
||||
much as unions or variants do in other languages.
|
||||
You can define a common set of related cases as part of one enumeration,
|
||||
each of which has a different set of values of appropriate types associated with it.
|
||||
|
||||
Enumerations in Swift are first-class types in their own right.
|
||||
They adopt many features traditionally supported only by classes,
|
||||
such as computed properties to provide additional information about
|
||||
the enumeration's current value,
|
||||
and instance methods to provide functionality related to
|
||||
the values the enumeration represents.
|
||||
Enumerations can also define initializers to provide an initial case value;
|
||||
can be extended to expand their functionality beyond their original implementation;
|
||||
and can conform to protocols to provide standard functionality.
|
||||
|
||||
For more about these capabilities, see
|
||||
<doc:Properties>, <doc:Methods>, <doc:Initialization>,
|
||||
<doc:Extensions>, and <doc:Protocols>.
|
||||
|
||||
<!--
|
||||
TODO: this chapter should probably mention that enums without associated values
|
||||
are hashable and equatable by default (and what that means in practice)
|
||||
-->
|
||||
|
||||
## Enumeration Syntax
|
||||
|
||||
You introduce enumerations with the `enum` keyword
|
||||
and place their entire definition within a pair of braces:
|
||||
|
||||
```swift
|
||||
enum SomeEnumeration {
|
||||
// enumeration definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> enum SomeEnumeration {
|
||||
// enumeration definition goes here
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Here's an example for the four main points of a compass:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case north
|
||||
case south
|
||||
case east
|
||||
case west
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> enum CompassPoint {
|
||||
case north
|
||||
case south
|
||||
case east
|
||||
case west
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The values defined in an enumeration
|
||||
(such as `north`, `south`, `east`, and `west`)
|
||||
are its *enumeration cases*.
|
||||
You use the `case` keyword to introduce new enumeration cases.
|
||||
|
||||
> Note: Swift enumeration cases don't have an integer value set by default,
|
||||
> unlike languages like C and Objective-C.
|
||||
> In the `CompassPoint` example above,
|
||||
> `north`, `south`, `east` and `west`
|
||||
> don't implicitly equal
|
||||
> `0`, `1`, `2` and `3`.
|
||||
> Instead, the different enumeration cases are values in their own right,
|
||||
> with an explicitly defined type of `CompassPoint`.
|
||||
|
||||
Multiple cases can appear on a single line, separated by commas:
|
||||
|
||||
```swift
|
||||
enum Planet {
|
||||
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> enum Planet {
|
||||
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Each enumeration definition defines a new type.
|
||||
Like other types in Swift, their names
|
||||
(such as `CompassPoint` and `Planet`)
|
||||
start with a capital letter.
|
||||
Give enumeration types singular rather than plural names,
|
||||
so that they read as self-evident:
|
||||
|
||||
```swift
|
||||
var directionToHead = CompassPoint.west
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> var directionToHead = CompassPoint.west
|
||||
```
|
||||
-->
|
||||
|
||||
The type of `directionToHead` is inferred
|
||||
when it's initialized with one of the possible values of `CompassPoint`.
|
||||
Once `directionToHead` is declared as a `CompassPoint`,
|
||||
you can set it to a different `CompassPoint` value using a shorter dot syntax:
|
||||
|
||||
```swift
|
||||
directionToHead = .east
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> directionToHead = .east
|
||||
```
|
||||
-->
|
||||
|
||||
The type of `directionToHead` is already known,
|
||||
and so you can drop the type when setting its value.
|
||||
This makes for highly readable code when working with explicitly typed enumeration values.
|
||||
|
||||
## Matching Enumeration Values with a Switch Statement
|
||||
|
||||
You can match individual enumeration values with a `switch` statement:
|
||||
|
||||
```swift
|
||||
directionToHead = .south
|
||||
switch directionToHead {
|
||||
case .north:
|
||||
print("Lots of planets have a north")
|
||||
case .south:
|
||||
print("Watch out for penguins")
|
||||
case .east:
|
||||
print("Where the sun rises")
|
||||
case .west:
|
||||
print("Where the skies are blue")
|
||||
}
|
||||
// Prints "Watch out for penguins".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> directionToHead = .south
|
||||
-> switch directionToHead {
|
||||
case .north:
|
||||
print("Lots of planets have a north")
|
||||
case .south:
|
||||
print("Watch out for penguins")
|
||||
case .east:
|
||||
print("Where the sun rises")
|
||||
case .west:
|
||||
print("Where the skies are blue")
|
||||
}
|
||||
<- Watch out for penguins
|
||||
```
|
||||
-->
|
||||
|
||||
You can read this code as:
|
||||
|
||||
“Consider the value of `directionToHead`.
|
||||
In the case where it equals `.north`,
|
||||
print `"Lots of planets have a north"`.
|
||||
In the case where it equals `.south`,
|
||||
print `"Watch out for penguins"`.”
|
||||
|
||||
…and so on.
|
||||
|
||||
As described in <doc:ControlFlow>,
|
||||
a `switch` statement must be exhaustive when considering an enumeration's cases.
|
||||
If the `case` for `.west` is omitted,
|
||||
this code doesn't compile,
|
||||
because it doesn't consider the complete list of `CompassPoint` cases.
|
||||
Requiring exhaustiveness ensures that enumeration cases aren't accidentally omitted.
|
||||
|
||||
When it isn't appropriate to provide a `case` for every enumeration case,
|
||||
you can provide a `default` case to cover any cases that aren't addressed explicitly:
|
||||
|
||||
```swift
|
||||
let somePlanet = Planet.earth
|
||||
switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
// Prints "Mostly harmless".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> let somePlanet = Planet.earth
|
||||
-> switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
<- Mostly harmless
|
||||
```
|
||||
-->
|
||||
|
||||
## Iterating over Enumeration Cases
|
||||
|
||||
For some enumerations,
|
||||
it's useful to have a collection of all of that enumeration's cases.
|
||||
You enable this by
|
||||
writing `: CaseIterable` after the enumeration's name.
|
||||
Swift exposes a collection of all the cases
|
||||
as an `allCases` property of the enumeration type.
|
||||
Here's an example:
|
||||
|
||||
```swift
|
||||
enum Beverage: CaseIterable {
|
||||
case coffee, tea, juice
|
||||
}
|
||||
let numberOfChoices = Beverage.allCases.count
|
||||
print("\(numberOfChoices) beverages available")
|
||||
// Prints "3 beverages available".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> enum Beverage: CaseIterable {
|
||||
case coffee, tea, juice
|
||||
}
|
||||
-> let numberOfChoices = Beverage.allCases.count
|
||||
-> print("\(numberOfChoices) beverages available")
|
||||
<- 3 beverages available
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
you write `Beverage.allCases` to access a collection
|
||||
that contains all of the cases of the `Beverage` enumeration.
|
||||
You can use `allCases` like any other collection ---
|
||||
the collection's elements are instances of the enumeration type,
|
||||
so in this case they're `Beverage` values.
|
||||
The example above counts how many cases there are,
|
||||
and the example below uses a `for`-`in` loop to iterate over all the cases.
|
||||
|
||||
```swift
|
||||
for beverage in Beverage.allCases {
|
||||
print(beverage)
|
||||
}
|
||||
// coffee
|
||||
// tea
|
||||
// juice
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> for beverage in Beverage.allCases {
|
||||
print(beverage)
|
||||
}
|
||||
<< coffee
|
||||
<< tea
|
||||
<< juice
|
||||
// coffee
|
||||
// tea
|
||||
// juice
|
||||
```
|
||||
-->
|
||||
|
||||
The syntax used in the examples above
|
||||
marks the enumeration as conforming to the
|
||||
[`CaseIterable`](https://developer.apple.com/documentation/swift/caseiterable) protocol.
|
||||
For information about protocols, see <doc:Protocols>.
|
||||
|
||||
## Associated Values
|
||||
|
||||
The examples in the previous section show how the cases of an enumeration are
|
||||
a defined (and typed) value in their own right.
|
||||
You can set a constant or variable to `Planet.earth`,
|
||||
and check for this value later.
|
||||
However, it's sometimes useful to be able to store
|
||||
values of other types alongside these case values.
|
||||
This additional information is called an *associated value*,
|
||||
and it varies each time you use that case as a value in your code.
|
||||
|
||||
You can define Swift enumerations to store associated values of any given type,
|
||||
and the value types can be different for each case of the enumeration if needed.
|
||||
Enumerations similar to these are known as
|
||||
*discriminated unions*, *tagged unions*, or *variants*
|
||||
in other programming languages.
|
||||
|
||||
For example, suppose an inventory tracking system needs to
|
||||
track products by two different types of barcode.
|
||||
Some products are labeled with 1D barcodes in UPC format,
|
||||
which uses the numbers `0` to `9`.
|
||||
Each barcode has a number system digit,
|
||||
followed by five manufacturer code digits and five product code digits.
|
||||
These are followed by a check digit to verify that the code has been scanned correctly:
|
||||
|
||||

|
||||
|
||||
Other products are labeled with 2D barcodes in QR code format,
|
||||
which can use any ISO 8859-1 character
|
||||
and can encode a string up to 2,953 characters long:
|
||||
|
||||

|
||||
|
||||
It's convenient for an inventory tracking system to store UPC barcodes
|
||||
as a tuple of four integers,
|
||||
and QR code barcodes as a string of any length.
|
||||
|
||||
In Swift, an enumeration to define product barcodes of either type might look like this:
|
||||
|
||||
```swift
|
||||
enum Barcode {
|
||||
case upc(Int, Int, Int, Int)
|
||||
case qrCode(String)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> enum Barcode {
|
||||
case upc(Int, Int, Int, Int)
|
||||
case qrCode(String)
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This can be read as:
|
||||
|
||||
“Define an enumeration type called `Barcode`,
|
||||
which can take either a value of `upc`
|
||||
with an associated value of type (`Int`, `Int`, `Int`, `Int`),
|
||||
or a value of `qrCode` with an associated value of type `String`.”
|
||||
|
||||
This definition doesn't provide any actual `Int` or `String` values ---
|
||||
it just defines the *type* of associated values
|
||||
that `Barcode` constants and variables can store
|
||||
when they're equal to `Barcode.upc` or `Barcode.qrCode`.
|
||||
|
||||
You can then create new barcodes using either type:
|
||||
|
||||
```swift
|
||||
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> var productBarcode = Barcode.upc(8, 85909, 51226, 3)
|
||||
```
|
||||
-->
|
||||
|
||||
This example creates a new variable called `productBarcode`
|
||||
and assigns it a value of `Barcode.upc`
|
||||
with an associated tuple value of `(8, 85909, 51226, 3)`.
|
||||
|
||||
You can assign the same product a different type of barcode:
|
||||
|
||||
```swift
|
||||
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
-->
|
||||
|
||||
At this point,
|
||||
the original `Barcode.upc` and its integer values are replaced by
|
||||
the new `Barcode.qrCode` and its string value.
|
||||
Constants and variables of type `Barcode` can store either a `.upc` or a `.qrCode`
|
||||
(together with their associated values),
|
||||
but they can store only one of them at any given time.
|
||||
|
||||
You can check the different barcode types using a switch statement,
|
||||
similar to the example in
|
||||
<doc:Enumerations#Matching-Enumeration-Values-with-a-Switch-Statement>.
|
||||
This time, however,
|
||||
the associated values are extracted as part of the switch statement.
|
||||
You extract each associated value as a constant (with the `let` prefix)
|
||||
or a variable (with the `var` prefix)
|
||||
for use within the `switch` case's body:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case .upc(let numberSystem, let manufacturer, let product, let check):
|
||||
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case .qrCode(let productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// Prints "QR code: ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> switch productBarcode {
|
||||
case .upc(let numberSystem, let manufacturer, let product, let check):
|
||||
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case .qrCode(let productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
<- QR code: ABCDEFGHIJKLMNOP.
|
||||
```
|
||||
-->
|
||||
|
||||
If all of the associated values for an enumeration case
|
||||
are extracted as constants, or if all are extracted as variables,
|
||||
you can place a single `let` or `var` annotation before the case name, for brevity:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case let .upc(numberSystem, manufacturer, product, check):
|
||||
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case let .qrCode(productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// Prints "QR code: ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `enums`
|
||||
|
||||
```swifttest
|
||||
-> switch productBarcode {
|
||||
case let .upc(numberSystem, manufacturer, product, check):
|
||||
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case let .qrCode(productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
<- QR code: ABCDEFGHIJKLMNOP.
|
||||
```
|
||||
-->
|
||||
|
||||
When you're matching just one case of an enumeration ---
|
||||
for example,
|
||||
to extract its associated value ---
|
||||
you can use an `if`-`case` statement
|
||||
instead of writing a full switch statement.
|
||||
Here's what it looks like:
|
||||
|
||||
```swift
|
||||
if case .qrCode(let productCode) = productBarcode {
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
```
|
||||
|
||||
Just like in the switch statement earlier,
|
||||
the `productBarcode` variable is matched against
|
||||
the pattern `.qrCode(let productCode)` here.
|
||||
And as in the switch case,
|
||||
writing `let` extracts the associated value as a constant.
|
||||
For more information about `if`-`case` statements,
|
||||
see <doc:ControlFlow#Patterns>.
|
||||
|
||||
## Raw Values
|
||||
|
||||
The barcode example in <doc:Enumerations#Associated-Values>
|
||||
shows how cases of an enumeration can declare that they store
|
||||
associated values of different types.
|
||||
As an alternative to associated values,
|
||||
enumeration cases can come prepopulated with default values
|
||||
(called *raw values*),
|
||||
which are all of the same type.
|
||||
|
||||
Here's an example that stores raw ASCII values alongside named enumeration cases:
|
||||
|
||||
```swift
|
||||
enum ASCIIControlCharacter: Character {
|
||||
case tab = "\t"
|
||||
case lineFeed = "\n"
|
||||
case carriageReturn = "\r"
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> enum ASCIIControlCharacter: Character {
|
||||
case tab = "\t"
|
||||
case lineFeed = "\n"
|
||||
case carriageReturn = "\r"
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Here, the raw values for an enumeration called `ASCIIControlCharacter`
|
||||
are defined to be of type `Character`,
|
||||
and are set to some of the more common ASCII control characters.
|
||||
`Character` values are described in <doc:StringsAndCharacters>.
|
||||
|
||||
Raw values can be
|
||||
strings, characters, or any of the integer or floating-point number types.
|
||||
Each raw value must be unique within its enumeration declaration.
|
||||
|
||||
Although you can use both raw values and associated values
|
||||
to give an enumeration an additional value,
|
||||
it's important to understand the difference between them.
|
||||
You pick the raw value for an enumeration case
|
||||
when you define that enumeration case in your code,
|
||||
such as the three ASCII codes above.
|
||||
The raw value for a particular enumeration case is always the same.
|
||||
In contrast,
|
||||
you pick associated values when you create a new constant or variable
|
||||
using one of the enumeration's cases,
|
||||
and you can pick a different value each time you do so.
|
||||
|
||||
### Implicitly Assigned Raw Values
|
||||
|
||||
When you're working with enumerations that store integer or string raw values,
|
||||
you don't have to explicitly assign a raw value for each case.
|
||||
When you don't, Swift automatically assigns the values for you.
|
||||
|
||||
For example, when integers are used for raw values,
|
||||
the implicit value for each case is one more than the previous case.
|
||||
If the first case doesn't have a value set, its value is `0`.
|
||||
|
||||
The enumeration below is a refinement of the earlier `Planet` enumeration,
|
||||
with integer raw values to represent each planet's order from the sun:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> enum Planet: Int {
|
||||
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
`Planet.mercury` has an explicit raw value of `1`,
|
||||
`Planet.venus` has an implicit raw value of `2`, and so on.
|
||||
|
||||
When strings are used for raw values,
|
||||
the implicit value for each case is the text of that case's name.
|
||||
|
||||
The enumeration below is a refinement of the earlier `CompassPoint` enumeration,
|
||||
with string raw values to represent each direction's name:
|
||||
|
||||
```swift
|
||||
enum CompassPoint: String {
|
||||
case north, south, east, west
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> enum CompassPoint: String {
|
||||
case north, south, east, west
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
`CompassPoint.south` has an implicit raw value of `"south"`, and so on.
|
||||
|
||||
You access the raw value of an enumeration case with its `rawValue` property:
|
||||
|
||||
```swift
|
||||
let earthsOrder = Planet.earth.rawValue
|
||||
// earthsOrder is 3
|
||||
|
||||
let sunsetDirection = CompassPoint.west.rawValue
|
||||
// sunsetDirection is "west"
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> let earthsOrder = Planet.earth.rawValue
|
||||
/> earthsOrder is \(earthsOrder)
|
||||
</ earthsOrder is 3
|
||||
|
||||
-> let sunsetDirection = CompassPoint.west.rawValue
|
||||
/> sunsetDirection is \"\(sunsetDirection)\"
|
||||
</ sunsetDirection is "west"
|
||||
```
|
||||
-->
|
||||
|
||||
### Initializing from a Raw Value
|
||||
|
||||
If you define an enumeration with a raw-value type,
|
||||
the enumeration automatically receives an initializer
|
||||
that takes a value of the raw value's type (as a parameter called `rawValue`)
|
||||
and returns either an enumeration case or `nil`.
|
||||
You can use this initializer to try to create a new instance of the enumeration.
|
||||
|
||||
This example identifies Uranus from its raw value of `7`:
|
||||
|
||||
```swift
|
||||
let possiblePlanet = Planet(rawValue: 7)
|
||||
// possiblePlanet is of type Planet? and equals Planet.uranus
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> let possiblePlanet = Planet(rawValue: 7)
|
||||
>> print(type(of: possiblePlanet))
|
||||
<< Optional<Planet>
|
||||
>> assert(possiblePlanet == .uranus)
|
||||
// possiblePlanet is of type Planet? and equals Planet.uranus
|
||||
```
|
||||
-->
|
||||
|
||||
Not all possible `Int` values will find a matching planet, however.
|
||||
Because of this, the raw value initializer always returns an *optional* enumeration case.
|
||||
In the example above, `possiblePlanet` is of type `Planet?`,
|
||||
or “optional `Planet`.”
|
||||
|
||||
> Note: The raw value initializer is a failable initializer,
|
||||
> because not every raw value will return an enumeration case.
|
||||
> For more information, see <doc:Declarations#Failable-Initializers>.
|
||||
|
||||
If you try to find a planet with a position of `11`,
|
||||
the optional `Planet` value returned by the raw value initializer will be `nil`:
|
||||
|
||||
```swift
|
||||
let positionToFind = 11
|
||||
if let somePlanet = Planet(rawValue: positionToFind) {
|
||||
switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
} else {
|
||||
print("There isn't a planet at position \(positionToFind)")
|
||||
}
|
||||
// Prints "There isn't a planet at position 11".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `rawValues`
|
||||
|
||||
```swifttest
|
||||
-> let positionToFind = 11
|
||||
-> if let somePlanet = Planet(rawValue: positionToFind) {
|
||||
switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
} else {
|
||||
print("There isn't a planet at position \(positionToFind)")
|
||||
}
|
||||
<- There isn't a planet at position 11
|
||||
```
|
||||
-->
|
||||
|
||||
This example uses optional binding to try to access a planet with a raw value of `11`.
|
||||
The statement `if let somePlanet = Planet(rawValue: 11)` creates an optional `Planet`,
|
||||
and sets `somePlanet` to the value of that optional `Planet` if it can be retrieved.
|
||||
In this case, it isn't possible to retrieve a planet with a position of `11`,
|
||||
and so the `else` branch is executed instead.
|
||||
|
||||
<!--
|
||||
TODO: Switch around the order of this chapter so that all of the non-union stuff
|
||||
is together, and the union bits (aka Associated Values) come last.
|
||||
-->
|
||||
|
||||
## Recursive Enumerations
|
||||
|
||||
A *recursive enumeration* is an enumeration
|
||||
that has another instance of the enumeration
|
||||
as the associated value for one or more of the enumeration cases.
|
||||
You indicate that an enumeration case is recursive
|
||||
by writing `indirect` before it,
|
||||
which tells the compiler to insert the necessary layer of indirection.
|
||||
|
||||
For example, here is an enumeration that stores simple arithmetic expressions:
|
||||
|
||||
```swift
|
||||
enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
indirect case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `recursive-enum-intro`
|
||||
|
||||
```swifttest
|
||||
-> enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
indirect case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
You can also write `indirect` before the beginning of the enumeration
|
||||
to enable indirection for all of the enumeration's cases that have an associated value:
|
||||
|
||||
```swift
|
||||
indirect enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `recursive-enum`
|
||||
|
||||
```swifttest
|
||||
-> indirect enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This enumeration can store three kinds of arithmetic expressions:
|
||||
a plain number,
|
||||
the addition of two expressions,
|
||||
and the multiplication of two expressions.
|
||||
The `addition` and `multiplication` cases have associated values
|
||||
that are also arithmetic expressions ---
|
||||
these associated values make it possible to nest expressions.
|
||||
For example, the expression `(5 + 4) * 2`
|
||||
has a number on the right-hand side of the multiplication
|
||||
and another expression on the left-hand side of the multiplication.
|
||||
Because the data is nested,
|
||||
the enumeration used to store the data also needs to support nesting ---
|
||||
this means the enumeration needs to be recursive.
|
||||
The code below shows the `ArithmeticExpression` recursive enumeration
|
||||
being created for `(5 + 4) * 2`:
|
||||
|
||||
```swift
|
||||
let five = ArithmeticExpression.number(5)
|
||||
let four = ArithmeticExpression.number(4)
|
||||
let sum = ArithmeticExpression.addition(five, four)
|
||||
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `recursive-enum`
|
||||
|
||||
```swifttest
|
||||
-> let five = ArithmeticExpression.number(5)
|
||||
-> let four = ArithmeticExpression.number(4)
|
||||
-> let sum = ArithmeticExpression.addition(five, four)
|
||||
-> let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
|
||||
```
|
||||
-->
|
||||
|
||||
A recursive function is a straightforward way
|
||||
to work with data that has a recursive structure.
|
||||
For example, here's a function that evaluates an arithmetic expression:
|
||||
|
||||
```swift
|
||||
func evaluate(_ expression: ArithmeticExpression) -> Int {
|
||||
switch expression {
|
||||
case let .number(value):
|
||||
return value
|
||||
case let .addition(left, right):
|
||||
return evaluate(left) + evaluate(right)
|
||||
case let .multiplication(left, right):
|
||||
return evaluate(left) * evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
print(evaluate(product))
|
||||
// Prints "18".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `recursive-enum`
|
||||
|
||||
```swifttest
|
||||
-> func evaluate(_ expression: ArithmeticExpression) -> Int {
|
||||
switch expression {
|
||||
case let .number(value):
|
||||
return value
|
||||
case let .addition(left, right):
|
||||
return evaluate(left) + evaluate(right)
|
||||
case let .multiplication(left, right):
|
||||
return evaluate(left) * evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
-> print(evaluate(product))
|
||||
<- 18
|
||||
```
|
||||
-->
|
||||
|
||||
This function evaluates a plain number
|
||||
by simply returning the associated value.
|
||||
It evaluates an addition or multiplication
|
||||
by evaluating the expression on the left-hand side,
|
||||
evaluating the expression on the right-hand side,
|
||||
and then adding them or multiplying them.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
993
skills/programming-swift/LanguageGuide/ErrorHandling.md
Normal file
993
skills/programming-swift/LanguageGuide/ErrorHandling.md
Normal file
@@ -0,0 +1,993 @@
|
||||
# Error Handling
|
||||
|
||||
Respond to and recover from errors.
|
||||
|
||||
*Error handling* is the process of responding to
|
||||
and recovering from error conditions in your program.
|
||||
Swift provides first-class support for
|
||||
throwing, catching, propagating, and manipulating
|
||||
recoverable errors at runtime.
|
||||
|
||||
Some operations
|
||||
aren't guaranteed to always complete execution or produce a useful output.
|
||||
Optionals are used to represent the absence of a value,
|
||||
but when an operation fails,
|
||||
it's often useful to understand what caused the failure,
|
||||
so that your code can respond accordingly.
|
||||
|
||||
As an example, consider the task of reading and processing data from a file on disk.
|
||||
There are a number of ways this task can fail, including
|
||||
the file not existing at the specified path,
|
||||
the file not having read permissions, or
|
||||
the file not being encoded in a compatible format.
|
||||
Distinguishing among these different situations
|
||||
allows a program to resolve some errors
|
||||
and to communicate to the user any errors it can't resolve.
|
||||
|
||||
> Note: Error handling in Swift interoperates with error handling patterns
|
||||
> that use the `NSError` class in Cocoa and Objective-C.
|
||||
> For more information about this class,
|
||||
> see [Handling Cocoa Errors in Swift](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift).
|
||||
|
||||
## Representing and Throwing Errors
|
||||
|
||||
In Swift, errors are represented by
|
||||
values of types that conform to the `Error` protocol.
|
||||
This empty protocol indicates that a type
|
||||
can be used for error handling.
|
||||
|
||||
Swift enumerations are particularly well suited to modeling
|
||||
a group of related error conditions,
|
||||
with associated values allowing for additional information
|
||||
about the nature of an error to be communicated.
|
||||
For example, here's how you might represent the error conditions
|
||||
of operating a vending machine inside a game:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: Error {
|
||||
case invalidSelection
|
||||
case insufficientFunds(coinsNeeded: Int)
|
||||
case outOfStock
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throw-enum-error`
|
||||
|
||||
```swifttest
|
||||
-> enum VendingMachineError: Error {
|
||||
case invalidSelection
|
||||
case insufficientFunds(coinsNeeded: Int)
|
||||
case outOfStock
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Throwing an error lets you indicate that something unexpected happened
|
||||
and the normal flow of execution can't continue.
|
||||
You use a `throw` statement to throw an error.
|
||||
For example,
|
||||
the following code throws an error to indicate
|
||||
that five additional coins are needed by the vending machine:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throw-enum-error`
|
||||
|
||||
```swifttest
|
||||
-> throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
xx fatal error
|
||||
```
|
||||
-->
|
||||
|
||||
## Handling Errors
|
||||
|
||||
When an error is thrown,
|
||||
some surrounding piece of code must be responsible
|
||||
for handling the error ---
|
||||
for example, by correcting the problem,
|
||||
trying an alternative approach,
|
||||
or informing the user of the failure.
|
||||
|
||||
There are four ways to handle errors in Swift.
|
||||
You can propagate the error from a function to the code that calls that function,
|
||||
handle the error using a `do`-`catch` statement,
|
||||
handle the error as an optional value,
|
||||
or assert that the error will not occur.
|
||||
Each approach is described in a section below.
|
||||
|
||||
When a function throws an error,
|
||||
it changes the flow of your program,
|
||||
so it's important that you can quickly identify places in your code that can throw errors.
|
||||
To identify these places in your code, write the `try` keyword ---
|
||||
or the `try?` or `try!` variation ---
|
||||
before a piece of code that calls a function, method, or initializer that can throw an error.
|
||||
These keywords are described in the sections below.
|
||||
|
||||
> Note: Error handling in Swift resembles exception handling in other languages,
|
||||
> with the use of the `try`, `catch` and `throw` keywords.
|
||||
> Unlike exception handling in many languages ---
|
||||
> including Objective-C ---
|
||||
> error handling in Swift doesn't involve unwinding the call stack,
|
||||
> a process that can be computationally expensive.
|
||||
> As such, the performance characteristics
|
||||
> of a `throw` statement
|
||||
> are comparable to those of a `return` statement.
|
||||
|
||||
### Propagating Errors Using Throwing Functions
|
||||
|
||||
To indicate that a function, method, or initializer can throw an error,
|
||||
you write the `throws` keyword in the function's declaration
|
||||
after its parameters.
|
||||
A function marked with `throws` is called a *throwing function*.
|
||||
If the function specifies a return type,
|
||||
you write the `throws` keyword before the return arrow (`->`).
|
||||
|
||||
<!--
|
||||
TODO Add discussion of throwing initializers
|
||||
-->
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throwingFunctionDeclaration`
|
||||
|
||||
```swifttest
|
||||
-> func canThrowErrors() throws -> String
|
||||
>> { return "foo" }
|
||||
|
||||
-> func cannotThrowErrors() -> String
|
||||
>> { return "foo" }
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `throwing-function-cant-overload-nonthrowing`
|
||||
|
||||
```swifttest
|
||||
-> func f() -> Int { return 10 }
|
||||
-> func f() throws -> Int { return 10 } // Error
|
||||
!$ error: invalid redeclaration of 'f()'
|
||||
!! func f() throws -> Int { return 10 } // Error
|
||||
!! ^
|
||||
!$ note: 'f()' previously declared here
|
||||
!! func f() -> Int { return 10 }
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `throwing-parameter-can-overload-nonthrowing`
|
||||
|
||||
```swifttest
|
||||
-> func f(callback: () -> Int) {}
|
||||
-> func f(callback: () throws -> Int) {} // Allowed
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Add more assertions to test these behaviors
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Write about the fact the above rules that govern overloading
|
||||
for throwing and nonthrowing functions.
|
||||
-->
|
||||
|
||||
A throwing function propagates errors that are thrown inside of it
|
||||
to the scope from which it's called.
|
||||
|
||||
> Note: Only throwing functions can propagate errors.
|
||||
> Any errors thrown inside a nonthrowing function
|
||||
> must be handled inside the function.
|
||||
|
||||
In the example below,
|
||||
the `VendingMachine` class has a `vend(itemNamed:)` method
|
||||
that throws an appropriate `VendingMachineError`
|
||||
if the requested item isn't available,
|
||||
is out of stock,
|
||||
or has a cost that exceeds the current deposited amount:
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
class VendingMachine {
|
||||
var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
var coinsDeposited = 0
|
||||
|
||||
func vend(itemNamed name: String) throws {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.invalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.outOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
print("Dispensing \(name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
>> enum VendingMachineError: Error {
|
||||
>> case invalidSelection
|
||||
>> case insufficientFunds(coinsNeeded: Int)
|
||||
>> case outOfStock
|
||||
>> }
|
||||
-> struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
-> class VendingMachine {
|
||||
-> var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
-> var coinsDeposited = 0
|
||||
|
||||
-> func vend(itemNamed name: String) throws {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.invalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.outOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
print("Dispensing \(name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The implementation of the `vend(itemNamed:)` method
|
||||
uses `guard` statements to exit the method early and throw appropriate errors
|
||||
if any of the requirements for purchasing a snack aren't met.
|
||||
Because a `throw` statement immediately transfers program control,
|
||||
an item will be vended only if all of these requirements are met.
|
||||
|
||||
Because the `vend(itemNamed:)` method propagates any errors it throws,
|
||||
any code that calls this method must either handle the errors ---
|
||||
using a `do`-`catch` statement, `try?`, or `try!` ---
|
||||
or continue to propagate them.
|
||||
For example,
|
||||
the `buyFavoriteSnack(person:vendingMachine:)` in the example below
|
||||
is also a throwing function,
|
||||
and any errors that the `vend(itemNamed:)` method throws will
|
||||
propagate up to the point where the `buyFavoriteSnack(person:vendingMachine:)` function is called.
|
||||
|
||||
```swift
|
||||
let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
-> func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
>> var v = VendingMachine()
|
||||
>> v.coinsDeposited = 100
|
||||
>> try buyFavoriteSnack(person: "Alice", vendingMachine: v)
|
||||
<< Dispensing Chips
|
||||
```
|
||||
-->
|
||||
|
||||
In this example,
|
||||
the `buyFavoriteSnack(person: vendingMachine:)` function looks up a given person's favorite snack
|
||||
and tries to buy it for them by calling the `vend(itemNamed:)` method.
|
||||
Because the `vend(itemNamed:)` method can throw an error,
|
||||
it's called with the `try` keyword in front of it.
|
||||
|
||||
Throwing initializers can propagate errors in the same way as throwing functions.
|
||||
For example,
|
||||
the initializer for the `PurchasedSnack` structure in the listing below
|
||||
calls a throwing function as part of the initialization process,
|
||||
and it handles any errors that it encounters by propagating them to its caller.
|
||||
|
||||
```swift
|
||||
struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
>> do {
|
||||
>> let succeeds = try PurchasedSnack(name: "Candy Bar", vendingMachine: v)
|
||||
>> print(succeeds)
|
||||
>> } catch {
|
||||
>> print("Threw unexpected error.")
|
||||
>> }
|
||||
<< Dispensing Candy Bar
|
||||
<< PurchasedSnack(name: "Candy Bar")
|
||||
>> do {
|
||||
>> let throwsError = try PurchasedSnack(name: "Jelly Baby", vendingMachine: v)
|
||||
>> print(throwsError)
|
||||
>> } catch {
|
||||
>> print("Threw EXPECTED error.")
|
||||
>> }
|
||||
<< Threw EXPECTED error.
|
||||
```
|
||||
-->
|
||||
|
||||
### Handling Errors Using Do-Catch
|
||||
|
||||
You use a `do`-`catch` statement to handle errors
|
||||
by running a block of code.
|
||||
If an error is thrown by the code in the `do` clause,
|
||||
it's matched against the `catch` clauses
|
||||
to determine which one of them can handle the error.
|
||||
|
||||
Here is the general form of a `do`-`catch` statement:
|
||||
|
||||
```swift
|
||||
do {
|
||||
try <#expression#>
|
||||
<#statements#>
|
||||
} catch <#pattern 1#> {
|
||||
<#statements#>
|
||||
} catch <#pattern 2#> where <#condition#> {
|
||||
<#statements#>
|
||||
} catch <#pattern 3#>, <#pattern 4#> where <#condition#> {
|
||||
<#statements#>
|
||||
} catch {
|
||||
<#statements#>
|
||||
}
|
||||
```
|
||||
|
||||
You write a pattern after `catch` to indicate what errors
|
||||
that clause can handle.
|
||||
If a `catch` clause doesn't have a pattern,
|
||||
the clause matches any error
|
||||
and binds the error to a local constant named `error`.
|
||||
For more information about pattern matching,
|
||||
see <doc:Patterns>.
|
||||
|
||||
<!--
|
||||
TODO: Call out the reasoning why we don't let you
|
||||
consider a catch clause exhaustive by just matching
|
||||
the errors in an given enum without a general catch/default.
|
||||
-->
|
||||
|
||||
For example, the following code matches against all three cases
|
||||
of the `VendingMachineError` enumeration.
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
vendingMachine.coinsDeposited = 8
|
||||
do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
// Prints "Insufficient funds. Please insert an additional 2 coins."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> var vendingMachine = VendingMachine()
|
||||
-> vendingMachine.coinsDeposited = 8
|
||||
-> do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
<- Insufficient funds. Please insert an additional 2 coins.
|
||||
```
|
||||
-->
|
||||
|
||||
In the above example,
|
||||
the `buyFavoriteSnack(person:vendingMachine:)` function is called in a `try` expression,
|
||||
because it can throw an error.
|
||||
If an error is thrown,
|
||||
execution immediately transfers to the `catch` clauses,
|
||||
which decide whether to allow propagation to continue.
|
||||
If no pattern is matched, the error gets caught by the final `catch`
|
||||
clause and is bound to a local `error` constant.
|
||||
If no error is thrown,
|
||||
the remaining statements in the `do` statement are executed.
|
||||
|
||||
The `catch` clauses don't have to handle every possible error
|
||||
that the code in the `do` clause can throw.
|
||||
If none of the `catch` clauses handle the error,
|
||||
the error propagates to the surrounding scope.
|
||||
However, the propagated error
|
||||
must be handled by *some* surrounding scope.
|
||||
In a nonthrowing function,
|
||||
an enclosing `do`-`catch` statement
|
||||
must handle the error.
|
||||
In a throwing function,
|
||||
either an enclosing `do`-`catch` statement
|
||||
or the caller
|
||||
must handle the error.
|
||||
If the error propagates to the top-level scope
|
||||
without being handled,
|
||||
you'll get a runtime error.
|
||||
|
||||
For example, the above example can be written so any
|
||||
error that isn't a `VendingMachineError` is instead
|
||||
caught by the calling function:
|
||||
|
||||
```swift
|
||||
func nourish(with item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch is VendingMachineError {
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try nourish(with: "Beet-Flavored Chips")
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
// Prints "Couldn't buy that from the vending machine."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> func nourish(with item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch is VendingMachineError {
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
-> do {
|
||||
try nourish(with: "Beet-Flavored Chips")
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
<- Couldn't buy that from the vending machine.
|
||||
```
|
||||
-->
|
||||
|
||||
In the `nourish(with:)` function,
|
||||
if `vend(itemNamed:)` throws an error that's
|
||||
one of the cases of the `VendingMachineError` enumeration,
|
||||
`nourish(with:)` handles the error by printing a message.
|
||||
Otherwise,
|
||||
`nourish(with:)` propagates the error to its call site.
|
||||
The error is then caught by the general `catch` clause.
|
||||
|
||||
Another way to catch several related errors
|
||||
is to list them after `catch`, separated by commas.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
func eat(item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
|
||||
print("Invalid selection, out of stock, or not enough money.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> func eat(item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
|
||||
print("Invalid selection, out of stock, or not enough money.")
|
||||
}
|
||||
}
|
||||
>> do {
|
||||
>> try eat(item: "Beet-Flavored Chips")
|
||||
>> } catch {
|
||||
>> print("Unexpected error: \(error)")
|
||||
>> }
|
||||
<< Invalid selection, out of stock, or not enough money.
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
FIXME the catch clause is getting indented oddly in HTML output if I hard wrap it
|
||||
-->
|
||||
|
||||
The `eat(item:)` function lists the vending machine errors to catch,
|
||||
and its error text corresponds to the items in that list.
|
||||
If any of the three listed errors are thrown,
|
||||
this `catch` clause handles them by printing a message.
|
||||
Any other errors are propagated to the surrounding scope,
|
||||
including any vending-machine errors that might be added later.
|
||||
|
||||
### Converting Errors to Optional Values
|
||||
|
||||
You use `try?` to handle an error by converting it to an optional value.
|
||||
If an error is thrown while evaluating the `try?` expression,
|
||||
the value of the expression is `nil`.
|
||||
For example,
|
||||
in the following code `x` and `y` have the same value and behavior:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
}
|
||||
|
||||
let x = try? someThrowingFunction()
|
||||
|
||||
let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optional-try`
|
||||
|
||||
```swifttest
|
||||
-> func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
>> return 40
|
||||
-> }
|
||||
|
||||
-> let x = try? someThrowingFunction()
|
||||
>> print(x as Any)
|
||||
<< Optional(40)
|
||||
|
||||
-> let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
>> print(y as Any)
|
||||
<< Optional(40)
|
||||
```
|
||||
-->
|
||||
|
||||
If `someThrowingFunction()` throws an error,
|
||||
the value of `x` and `y` is `nil`.
|
||||
Otherwise, the value of `x` and `y` is the value that the function returned.
|
||||
Note that `x` and `y` are an optional of whatever type `someThrowingFunction()` returns.
|
||||
Here the function returns an integer, so `x` and `y` are optional integers.
|
||||
|
||||
Using `try?` lets you write concise error handling code
|
||||
when you want to handle all errors in the same way.
|
||||
For example,
|
||||
the following code
|
||||
uses several approaches to fetch data,
|
||||
or returns `nil` if all of the approaches fail.
|
||||
|
||||
```swift
|
||||
func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optional-try-cached-data`
|
||||
|
||||
```swifttest
|
||||
>> struct Data {}
|
||||
>> func fetchDataFromDisk() throws -> Data { return Data() }
|
||||
>> func fetchDataFromServer() throws -> Data { return Data() }
|
||||
-> func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
### Disabling Error Propagation
|
||||
|
||||
Sometimes you know a throwing function or method
|
||||
won't, in fact, throw an error at runtime.
|
||||
On those occasions,
|
||||
you can write `try!` before the expression to disable error propagation
|
||||
and wrap the call in a runtime assertion that no error will be thrown.
|
||||
If an error actually is thrown, you'll get a runtime error.
|
||||
|
||||
For example, the following code uses a `loadImage(atPath:)` function,
|
||||
which loads the image resource at a given path
|
||||
or throws an error if the image can't be loaded.
|
||||
In this case, because the image is shipped with the application,
|
||||
no error will be thrown at runtime,
|
||||
so it's appropriate to disable error propagation.
|
||||
|
||||
```swift
|
||||
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `forceTryStatement`
|
||||
|
||||
```swifttest
|
||||
>> struct Image {}
|
||||
>> func loadImage(atPath path: String) throws -> Image {
|
||||
>> return Image()
|
||||
>> }
|
||||
-> let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
|
||||
```
|
||||
-->
|
||||
|
||||
## Specifying the Error Type
|
||||
|
||||
All of the examples above use the most common kind of error handling,
|
||||
where the errors that your code throws
|
||||
can be values of any type that conforms to the `Error` protocol.
|
||||
This approach matches the reality that
|
||||
you don't know ahead of time every error that could happen
|
||||
while the code is running,
|
||||
especially when propagating errors thrown somewhere else.
|
||||
It also reflects the fact that errors can change over time.
|
||||
New versions of a library ---
|
||||
including libraries that your dependencies use ---
|
||||
can throw new errors,
|
||||
and the rich complexity of real-world user configurations
|
||||
can expose failure modes that weren't visible during development or testing.
|
||||
The error handling code in the examples above
|
||||
always includes a default case to handle errors
|
||||
that don't have a specific `catch` clause.
|
||||
|
||||
Most Swift code doesn't specify the type for the errors it throws.
|
||||
However,
|
||||
you might limit code to throwing errors of only one specific type
|
||||
in the following special cases:
|
||||
|
||||
- When running code on an embedded system
|
||||
that doesn't support dynamic allocation of memory.
|
||||
Throwing an instance of `any Error` or another boxed protocol type
|
||||
requires allocating memory at runtime to store the error.
|
||||
In contrast,
|
||||
throwing an error of a specific type
|
||||
lets Swift avoid heap allocation for errors.
|
||||
|
||||
- When the errors are an implementation detail of some unit of code,
|
||||
like a library,
|
||||
and aren't part of the interface to that code.
|
||||
Because the errors come from only the library,
|
||||
and not from other dependencies or the library's clients,
|
||||
you can make an exhaustive list of all possible failures.
|
||||
And because these errors are an implementation detail of the library,
|
||||
they're always handled within that library.
|
||||
|
||||
- In code that only propagates errors described by generic parameters,
|
||||
like a function that takes a closure argument
|
||||
and propagates any errors from that closure.
|
||||
For a comparison between propagating a specific error type
|
||||
and using `rethrows`,
|
||||
see <doc:Declarations#Rethrowing-Functions-and-Methods>.
|
||||
|
||||
For example,
|
||||
consider code that summarizes ratings
|
||||
and uses the following error type:
|
||||
|
||||
```swift
|
||||
enum StatisticsError: Error {
|
||||
case noRatings
|
||||
case invalidRating(Int)
|
||||
}
|
||||
```
|
||||
|
||||
To specify that a function throws only `StatisticsError` values as its errors,
|
||||
you write `throws(StatisticsError)` instead of only `throws`
|
||||
when declaring the function.
|
||||
This syntax is also called *typed throws*
|
||||
because you write the error type after `throws` in the declaration.
|
||||
For example,
|
||||
the function below throws `StatisticsError` values as its errors.
|
||||
|
||||
```swift
|
||||
func summarize(_ ratings: [Int]) throws(StatisticsError) {
|
||||
guard !ratings.isEmpty else { throw .noRatings }
|
||||
|
||||
var counts = [1: 0, 2: 0, 3: 0]
|
||||
for rating in ratings {
|
||||
guard rating > 0 && rating <= 3 else { throw .invalidRating(rating) }
|
||||
counts[rating]! += 1
|
||||
}
|
||||
|
||||
print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!)
|
||||
}
|
||||
```
|
||||
|
||||
In the code above,
|
||||
the `summarize(_:)` function summarizes a list of ratings
|
||||
expressed on a scale of 1 to 3.
|
||||
This function throws an instance of `StatisticsError` if the input isn't valid.
|
||||
Both places in the code above that throw an error
|
||||
omit the type of the error
|
||||
because the function's error type is already defined.
|
||||
You can use the short form, `throw .noRatings`,
|
||||
instead of writing `throw StatisticsError.noRatings`
|
||||
when throwing an error in a function like this.
|
||||
|
||||
When you write a specific error type at the start of the function,
|
||||
Swift checks that you don't throw any other errors.
|
||||
For example,
|
||||
if you tried to use `VendingMachineError` from examples earlier in this chapter
|
||||
in the `summarize(_:)` function above,
|
||||
that code would produce an error at compile time.
|
||||
|
||||
You can call a function that uses typed throws
|
||||
from within a regular throwing function:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws {
|
||||
let ratings = [1, 2, 3, 2, 2, 1]
|
||||
try summarize(ratings)
|
||||
}
|
||||
```
|
||||
|
||||
The code above doesn't specify an error type for `someThrowingFunction()`,
|
||||
so it throws `any Error`.
|
||||
You could also write the error type explicitly as `throws(any Error)`;
|
||||
the code below is equivalent to the code above:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws(any Error) {
|
||||
let ratings = [1, 2, 3, 2, 2, 1]
|
||||
try summarize(ratings)
|
||||
}
|
||||
```
|
||||
|
||||
In this code,
|
||||
`someThrowingFunction()` propagates any errors that `summarize(_:)` throws.
|
||||
The errors from `summarize(_:)` are always `StatisticsError` values,
|
||||
which is also a valid error for `someThrowingFunction()` to throw.
|
||||
|
||||
Just like you can write a function that never returns
|
||||
with a return type of `Never`,
|
||||
you can write a function that never throws with `throws(Never)`:
|
||||
|
||||
```swift
|
||||
func nonThrowingFunction() throws(Never) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
This function can't throw because
|
||||
it's impossible to create a value of type `Never` to throw.
|
||||
|
||||
In addition to specifying a function's error type,
|
||||
you can also write a specific error type for a `do`-`catch` statement.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
let ratings = []
|
||||
do throws(StatisticsError) {
|
||||
try summarize(ratings)
|
||||
} catch {
|
||||
switch error {
|
||||
case .noRatings:
|
||||
print("No ratings available")
|
||||
case .invalidRating(let rating):
|
||||
print("Invalid rating: \(rating)")
|
||||
}
|
||||
}
|
||||
// Prints "No ratings available".
|
||||
```
|
||||
|
||||
In this code,
|
||||
writing `do throws(StatisticsError)` indicates that
|
||||
the `do`-`catch` statement throws `StatisticsError` values as its errors.
|
||||
Like other `do`-`catch` statements,
|
||||
the `catch` clause can either handle every possible error
|
||||
or propagate unhandled errors for some surrounding scope to handle.
|
||||
This code handles all of the errors,
|
||||
using a `switch` statement with one case for each enumeration value.
|
||||
Like other `catch` clauses that don't have a pattern,
|
||||
the clause matches any error
|
||||
and binds the error to a local constant named `error`.
|
||||
Because the `do`-`catch` statement throws `StatisticsError` values,
|
||||
`error` is a value of type `StatisticsError`.
|
||||
|
||||
The `catch` clause above uses a `switch` statement
|
||||
to match and handle each possible error.
|
||||
If you tried to add a new case to `StatisticsError`
|
||||
without updating the error-handling code,
|
||||
Swift would give you an error
|
||||
because the `switch` statement wouldn't be exhaustive anymore.
|
||||
For a library that catches all of its own errors,
|
||||
you could use this approach to ensure any new errors
|
||||
get corresponding new code to handle them.
|
||||
|
||||
If a function or `do` block throws errors of only a single type,
|
||||
Swift infers that this code is using typed throws.
|
||||
Using this shorter syntax,
|
||||
you could write the `do`-`catch` example above as follows:
|
||||
|
||||
```swift
|
||||
let ratings = []
|
||||
do {
|
||||
try summarize(ratings)
|
||||
} catch {
|
||||
switch error {
|
||||
case .noRatings:
|
||||
print("No ratings available")
|
||||
case .invalidRating(let rating):
|
||||
print("Invalid rating: \(rating)")
|
||||
}
|
||||
}
|
||||
// Prints "No ratings available".
|
||||
```
|
||||
|
||||
Even though the `do`-`catch` block above
|
||||
doesn't specify what type of error it throws,
|
||||
Swift infers that it throws `StatisticsError`.
|
||||
You can explicitly write `throws(any Error)`
|
||||
to avoid letting Swift infer typed throws.
|
||||
|
||||
## Specifying Cleanup Actions
|
||||
|
||||
You use a `defer` statement to execute a set of statements
|
||||
just before code execution leaves the current block of code.
|
||||
This statement lets you do any necessary cleanup
|
||||
that should be performed regardless
|
||||
of *how* execution leaves the current block of code ---
|
||||
whether it leaves because an error was thrown
|
||||
or because of a statement such as `return` or `break`.
|
||||
For example, you can use a `defer` statement
|
||||
to ensure that file descriptors are closed
|
||||
and manually allocated memory is freed.
|
||||
|
||||
A `defer` statement defers execution until the current scope is exited.
|
||||
This statement consists of the `defer` keyword and the statements to be executed later.
|
||||
The deferred statements may not contain any code
|
||||
that would transfer control out of the statements,
|
||||
such as a `break` or a `return` statement,
|
||||
or by throwing an error.
|
||||
Deferred actions are executed in the reverse of
|
||||
the order that they're written in your source code.
|
||||
That is, the code in the first `defer` statement executes last,
|
||||
the code in the second `defer` statement executes second to last,
|
||||
and so on.
|
||||
The last `defer` statement in source code order executes first.
|
||||
|
||||
```swift
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// Work with the file.
|
||||
}
|
||||
// close(file) is called here, at the end of the scope.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `defer`
|
||||
|
||||
```swifttest
|
||||
>> func exists(_ file: String) -> Bool { return true }
|
||||
>> struct File {
|
||||
>> func readline() throws -> String? { return nil }
|
||||
>> }
|
||||
>> func open(_ file: String) -> File { return File() }
|
||||
>> func close(_ fileHandle: File) {}
|
||||
-> func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// Work with the file.
|
||||
>> print(line)
|
||||
}
|
||||
// close(file) is called here, at the end of the scope.
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The above example uses a `defer` statement
|
||||
to ensure that the `open(_:)` function
|
||||
has a corresponding call to `close(_:)`.
|
||||
|
||||
You can use a `defer` statement
|
||||
even when no error handling code is involved.
|
||||
For more information,
|
||||
see <doc:ControlFlow#Deferred-Actions>.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
693
skills/programming-swift/LanguageGuide/Extensions.md
Normal file
693
skills/programming-swift/LanguageGuide/Extensions.md
Normal file
@@ -0,0 +1,693 @@
|
||||
# Extensions
|
||||
|
||||
Add functionality to an existing type.
|
||||
|
||||
*Extensions* add new functionality to an existing
|
||||
class, structure, enumeration, or protocol type.
|
||||
This includes the ability to extend types
|
||||
for which you don't have access to the original source code
|
||||
(known as *retroactive modeling*).
|
||||
Extensions are similar to categories in Objective-C.
|
||||
(Unlike Objective-C categories, Swift extensions don't have names.)
|
||||
|
||||
Extensions in Swift can:
|
||||
|
||||
- Add computed instance properties and computed type properties
|
||||
- Define instance methods and type methods
|
||||
- Provide new initializers
|
||||
- Define subscripts
|
||||
- Define and use new nested types
|
||||
- Make an existing type conform to a protocol
|
||||
|
||||
In Swift,
|
||||
you can even extend a protocol to provide implementations of its requirements
|
||||
or add additional functionality that conforming types can take advantage of.
|
||||
For more details, see <doc:Protocols#Protocol-Extensions>.
|
||||
|
||||
> Note: Extensions can add new functionality to a type,
|
||||
> but they can't override existing functionality.
|
||||
|
||||
<!--
|
||||
- test: `extensionsCannotOverrideExistingBehavior`
|
||||
|
||||
```swifttest
|
||||
-> class C {
|
||||
var x = 0
|
||||
func foo() {}
|
||||
}
|
||||
-> extension C {
|
||||
override var x: Int {
|
||||
didSet {
|
||||
print("new x is \(x)")
|
||||
}
|
||||
}
|
||||
override func foo() {
|
||||
print("called overridden foo")
|
||||
}
|
||||
}
|
||||
!$ error: property does not override any property from its superclass
|
||||
!! override var x: Int {
|
||||
!! ~~~~~~~~ ^
|
||||
!$ error: ambiguous use of 'x'
|
||||
!! print("new x is \(x)")
|
||||
!! ^
|
||||
!$ note: found this candidate
|
||||
!! var x = 0
|
||||
!! ^
|
||||
!$ note: found this candidate
|
||||
!! override var x: Int {
|
||||
!! ^
|
||||
!$ error: invalid redeclaration of 'x'
|
||||
!! override var x: Int {
|
||||
!! ^
|
||||
!$ note: 'x' previously declared here
|
||||
!! var x = 0
|
||||
!! ^
|
||||
!$ error: method does not override any method from its superclass
|
||||
!! override func foo() {
|
||||
!! ~~~~~~~~ ^
|
||||
!$ error: invalid redeclaration of 'foo()'
|
||||
!! override func foo() {
|
||||
!! ^
|
||||
!$ note: 'foo()' previously declared here
|
||||
!! func foo() {}
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
## Extension Syntax
|
||||
|
||||
Declare extensions with the `extension` keyword:
|
||||
|
||||
```swift
|
||||
extension SomeType {
|
||||
// new functionality to add to SomeType goes here
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionSyntax`
|
||||
|
||||
```swifttest
|
||||
>> struct SomeType {}
|
||||
-> extension SomeType {
|
||||
// new functionality to add to SomeType goes here
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
An extension can extend an existing type to make it adopt one or more protocols.
|
||||
To add protocol conformance,
|
||||
you write the protocol names
|
||||
the same way as you write them for a class or structure:
|
||||
|
||||
```swift
|
||||
extension SomeType: SomeProtocol, AnotherProtocol {
|
||||
// implementation of protocol requirements goes here
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionSyntax`
|
||||
|
||||
```swifttest
|
||||
>> protocol SomeProtocol {}
|
||||
>> protocol AnotherProtocol {}
|
||||
-> extension SomeType: SomeProtocol, AnotherProtocol {
|
||||
// implementation of protocol requirements goes here
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Adding protocol conformance in this way is described in
|
||||
<doc:Protocols#Adding-Protocol-Conformance-with-an-Extension>.
|
||||
|
||||
An extension can be used to extend an existing generic type,
|
||||
as described in <doc:Generics#Extending-a-Generic-Type>.
|
||||
You can also extend a generic type to conditionally add functionality,
|
||||
as described in <doc:Generics#Extensions-with-a-Generic-Where-Clause>.
|
||||
|
||||
> Note: If you define an extension to add new functionality to an existing type,
|
||||
> the new functionality will be available on all existing instances of that type,
|
||||
> even if they were created before the extension was defined.
|
||||
|
||||
## Computed Properties
|
||||
|
||||
Extensions can add computed instance properties and computed type properties to existing types.
|
||||
This example adds five computed instance properties to Swift's built-in `Double` type,
|
||||
to provide basic support for working with distance units:
|
||||
|
||||
```swift
|
||||
extension Double {
|
||||
var km: Double { return self * 1_000.0 }
|
||||
var m: Double { return self }
|
||||
var cm: Double { return self / 100.0 }
|
||||
var mm: Double { return self / 1_000.0 }
|
||||
var ft: Double { return self / 3.28084 }
|
||||
}
|
||||
let oneInch = 25.4.mm
|
||||
print("One inch is \(oneInch) meters")
|
||||
// Prints "One inch is 0.0254 meters".
|
||||
let threeFeet = 3.ft
|
||||
print("Three feet is \(threeFeet) meters")
|
||||
// Prints "Three feet is 0.914399970739201 meters".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsComputedProperties`
|
||||
|
||||
```swifttest
|
||||
-> extension Double {
|
||||
var km: Double { return self * 1_000.0 }
|
||||
var m: Double { return self }
|
||||
var cm: Double { return self / 100.0 }
|
||||
var mm: Double { return self / 1_000.0 }
|
||||
var ft: Double { return self / 3.28084 }
|
||||
}
|
||||
-> let oneInch = 25.4.mm
|
||||
-> print("One inch is \(oneInch) meters")
|
||||
<- One inch is 0.0254 meters
|
||||
-> let threeFeet = 3.ft
|
||||
-> print("Three feet is \(threeFeet) meters")
|
||||
<- Three feet is 0.914399970739201 meters
|
||||
```
|
||||
-->
|
||||
|
||||
These computed properties express that a `Double` value
|
||||
should be considered as a certain unit of length.
|
||||
Although they're implemented as computed properties,
|
||||
the names of these properties can be appended to
|
||||
a floating-point literal value with dot syntax,
|
||||
as a way to use that literal value to perform distance conversions.
|
||||
|
||||
In this example, a `Double` value of `1.0` is considered to represent “one meter”.
|
||||
This is why the `m` computed property returns `self` ---
|
||||
the expression `1.m` is considered to calculate a `Double` value of `1.0`.
|
||||
|
||||
Other units require some conversion to be expressed as a value measured in meters.
|
||||
One kilometer is the same as 1,000 meters,
|
||||
so the `km` computed property multiplies the value by `1_000.00`
|
||||
to convert into a number expressed in meters.
|
||||
Similarly, there are 3.28084 feet in a meter,
|
||||
and so the `ft` computed property divides the underlying `Double` value
|
||||
by `3.28084`, to convert it from feet to meters.
|
||||
|
||||
These properties are read-only computed properties,
|
||||
and so they're expressed without the `get` keyword, for brevity.
|
||||
Their return value is of type `Double`,
|
||||
and can be used within mathematical calculations wherever a `Double` is accepted:
|
||||
|
||||
```swift
|
||||
let aMarathon = 42.km + 195.m
|
||||
print("A marathon is \(aMarathon) meters long")
|
||||
// Prints "A marathon is 42195.0 meters long".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsComputedProperties`
|
||||
|
||||
```swifttest
|
||||
-> let aMarathon = 42.km + 195.m
|
||||
-> print("A marathon is \(aMarathon) meters long")
|
||||
<- A marathon is 42195.0 meters long
|
||||
```
|
||||
-->
|
||||
|
||||
> Note: Extensions can add new computed properties, but they can't add stored properties,
|
||||
> or add property observers to existing properties.
|
||||
|
||||
<!--
|
||||
- test: `extensionsCannotAddStoredProperties`
|
||||
|
||||
```swifttest
|
||||
-> class C {}
|
||||
-> extension C { var x = 0 }
|
||||
!$ error: extensions must not contain stored properties
|
||||
!! extension C { var x = 0 }
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: change this example to something more advisable / less contentious.
|
||||
-->
|
||||
|
||||
## Initializers
|
||||
|
||||
Extensions can add new initializers to existing types.
|
||||
This enables you to extend other types to accept
|
||||
your own custom types as initializer parameters,
|
||||
or to provide additional initialization options
|
||||
that were not included as part of the type's original implementation.
|
||||
|
||||
Extensions can add new convenience initializers to a class,
|
||||
but they can't add new designated initializers or deinitializers to a class.
|
||||
Designated initializers and deinitializers
|
||||
must always be provided by the original class implementation.
|
||||
|
||||
If you use an extension to add an initializer to a value type that provides
|
||||
default values for all of its stored properties
|
||||
and doesn't define any custom initializers,
|
||||
you can call the default initializer and memberwise initializer for that value type
|
||||
from within your extension's initializer.
|
||||
This wouldn't be the case if you had written the initializer
|
||||
as part of the value type's original implementation,
|
||||
as described in <doc:Initialization#Initializer-Delegation-for-Value-Types>.
|
||||
|
||||
If you use an extension to add an initializer to a structure
|
||||
that was declared in another module,
|
||||
the new initializer can't access `self` until it calls
|
||||
an initializer from the defining module.
|
||||
|
||||
The example below defines a custom `Rect` structure to represent a geometric rectangle.
|
||||
The example also defines two supporting structures called `Size` and `Point`,
|
||||
both of which provide default values of `0.0` for all of their properties:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInitializers`
|
||||
|
||||
```swifttest
|
||||
-> struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
-> struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
-> struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Because the `Rect` structure provides default values for all of its properties,
|
||||
it receives a default initializer and a memberwise initializer automatically,
|
||||
as described in <doc:Initialization#Default-Initializers>.
|
||||
These initializers can be used to create new `Rect` instances:
|
||||
|
||||
```swift
|
||||
let defaultRect = Rect()
|
||||
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInitializers`
|
||||
|
||||
```swifttest
|
||||
-> let defaultRect = Rect()
|
||||
-> let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
-->
|
||||
|
||||
You can extend the `Rect` structure to provide an additional initializer
|
||||
that takes a specific center point and size:
|
||||
|
||||
```swift
|
||||
extension Rect {
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
self.init(origin: Point(x: originX, y: originY), size: size)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInitializers`
|
||||
|
||||
```swifttest
|
||||
-> extension Rect {
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
self.init(origin: Point(x: originX, y: originY), size: size)
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This new initializer starts by calculating an appropriate origin point based on
|
||||
the provided `center` point and `size` value.
|
||||
The initializer then calls the structure's automatic memberwise initializer
|
||||
`init(origin:size:)`, which stores the new origin and size values
|
||||
in the appropriate properties:
|
||||
|
||||
```swift
|
||||
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInitializers`
|
||||
|
||||
```swifttest
|
||||
-> let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
/> centerRect's origin is (\(centerRect.origin.x), \(centerRect.origin.y)) and its size is (\(centerRect.size.width), \(centerRect.size.height))
|
||||
</ centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
|
||||
```
|
||||
-->
|
||||
|
||||
> Note: If you provide a new initializer with an extension,
|
||||
> you are still responsible for making sure that each instance is fully initialized
|
||||
> once the initializer completes.
|
||||
|
||||
## Methods
|
||||
|
||||
Extensions can add new instance methods and type methods to existing types.
|
||||
The following example adds a new instance method called `repetitions` to the `Int` type:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
func repetitions(task: () -> Void) {
|
||||
for _ in 0..<self {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInstanceMethods`
|
||||
|
||||
```swifttest
|
||||
-> extension Int {
|
||||
func repetitions(task: () -> Void) {
|
||||
for _ in 0..<self {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `repetitions(task:)` method takes a single argument of type `() -> Void`,
|
||||
which indicates a function that has no parameters and doesn't return a value.
|
||||
|
||||
After defining this extension,
|
||||
you can call the `repetitions(task:)` method on any integer
|
||||
to perform a task that many number of times:
|
||||
|
||||
```swift
|
||||
3.repetitions {
|
||||
print("Hello!")
|
||||
}
|
||||
// Hello!
|
||||
// Hello!
|
||||
// Hello!
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInstanceMethods`
|
||||
|
||||
```swifttest
|
||||
-> 3.repetitions {
|
||||
print("Hello!")
|
||||
}
|
||||
</ Hello!
|
||||
</ Hello!
|
||||
</ Hello!
|
||||
```
|
||||
-->
|
||||
|
||||
### Mutating Instance Methods
|
||||
|
||||
Instance methods added with an extension can also modify (or *mutate*) the instance itself.
|
||||
Structure and enumeration methods that modify `self` or its properties
|
||||
must mark the instance method as `mutating`,
|
||||
just like mutating methods from an original implementation.
|
||||
|
||||
The example below adds a new mutating method called `square` to Swift's `Int` type,
|
||||
which squares the original value:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
mutating func square() {
|
||||
self = self * self
|
||||
}
|
||||
}
|
||||
var someInt = 3
|
||||
someInt.square()
|
||||
// someInt is now 9
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsInstanceMethods`
|
||||
|
||||
```swifttest
|
||||
-> extension Int {
|
||||
mutating func square() {
|
||||
self = self * self
|
||||
}
|
||||
}
|
||||
-> var someInt = 3
|
||||
-> someInt.square()
|
||||
/> someInt is now \(someInt)
|
||||
</ someInt is now 9
|
||||
```
|
||||
-->
|
||||
|
||||
## Subscripts
|
||||
|
||||
Extensions can add new subscripts to an existing type.
|
||||
This example adds an integer subscript to Swift's built-in `Int` type.
|
||||
This subscript `[n]` returns the decimal digit `n` places in
|
||||
from the right of the number:
|
||||
|
||||
- `123456789[0]` returns `9`
|
||||
- `123456789[1]` returns `8`
|
||||
|
||||
…and so on:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
subscript(digitIndex: Int) -> Int {
|
||||
var decimalBase = 1
|
||||
for _ in 0..<digitIndex {
|
||||
decimalBase *= 10
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
}
|
||||
}
|
||||
746381295[0]
|
||||
// returns 5
|
||||
746381295[1]
|
||||
// returns 9
|
||||
746381295[2]
|
||||
// returns 2
|
||||
746381295[8]
|
||||
// returns 7
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsSubscripts`
|
||||
|
||||
```swifttest
|
||||
-> extension Int {
|
||||
subscript(digitIndex: Int) -> Int {
|
||||
var decimalBase = 1
|
||||
for _ in 0..<digitIndex {
|
||||
decimalBase *= 10
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
}
|
||||
}
|
||||
>> let r0 =
|
||||
-> 746381295[0]
|
||||
/> returns \(r0)
|
||||
</ returns 5
|
||||
>> let r1 =
|
||||
-> 746381295[1]
|
||||
/> returns \(r1)
|
||||
</ returns 9
|
||||
>> let r2 =
|
||||
-> 746381295[2]
|
||||
/> returns \(r2)
|
||||
</ returns 2
|
||||
>> let r3 =
|
||||
-> 746381295[8]
|
||||
/> returns \(r3)
|
||||
</ returns 7
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
Rewrite the above to avoid bare expressions.
|
||||
Tracking bug is <rdar://problem/35301593>
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Replace the for loop above with an exponent,
|
||||
if/when integer exponents land in the stdlib.
|
||||
Darwin's pow() function is only for floating point.
|
||||
-->
|
||||
|
||||
If the `Int` value doesn't have enough digits for the requested index,
|
||||
the subscript implementation returns `0`,
|
||||
as if the number had been padded with zeros to the left:
|
||||
|
||||
```swift
|
||||
746381295[9]
|
||||
// returns 0, as if you had requested:
|
||||
0746381295[9]
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsSubscripts`
|
||||
|
||||
```swifttest
|
||||
>> let r4 =
|
||||
-> 746381295[9]
|
||||
/> returns \(r4), as if you had requested:
|
||||
</ returns 0, as if you had requested:
|
||||
>> let r5 =
|
||||
-> 0746381295[9]
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: provide an explanation of this example
|
||||
-->
|
||||
|
||||
<!--
|
||||
Rewrite the above to avoid bare expressions.
|
||||
Tracking bug is <rdar://problem/35301593>
|
||||
-->
|
||||
|
||||
## Nested Types
|
||||
|
||||
Extensions can add new nested types to existing classes, structures, and enumerations:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
enum Kind {
|
||||
case negative, zero, positive
|
||||
}
|
||||
var kind: Kind {
|
||||
switch self {
|
||||
case 0:
|
||||
return .zero
|
||||
case let x where x > 0:
|
||||
return .positive
|
||||
default:
|
||||
return .negative
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsNestedTypes`
|
||||
|
||||
```swifttest
|
||||
-> extension Int {
|
||||
enum Kind {
|
||||
case negative, zero, positive
|
||||
}
|
||||
var kind: Kind {
|
||||
switch self {
|
||||
case 0:
|
||||
return .zero
|
||||
case let x where x > 0:
|
||||
return .positive
|
||||
default:
|
||||
return .negative
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This example adds a new nested enumeration to `Int`.
|
||||
This enumeration, called `Kind`,
|
||||
expresses the kind of number that a particular integer represents.
|
||||
Specifically, it expresses whether the number is
|
||||
negative, zero, or positive.
|
||||
|
||||
This example also adds a new computed instance property to `Int`,
|
||||
called `kind`,
|
||||
which returns the appropriate `Kind` enumeration case for that integer.
|
||||
|
||||
The nested enumeration can now be used with any `Int` value:
|
||||
|
||||
```swift
|
||||
func printIntegerKinds(_ numbers: [Int]) {
|
||||
for number in numbers {
|
||||
switch number.kind {
|
||||
case .negative:
|
||||
print("- ", terminator: "")
|
||||
case .zero:
|
||||
print("0 ", terminator: "")
|
||||
case .positive:
|
||||
print("+ ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
// Prints "+ + - 0 - 0 + ".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `extensionsNestedTypes`
|
||||
|
||||
```swifttest
|
||||
-> func printIntegerKinds(_ numbers: [Int]) {
|
||||
for number in numbers {
|
||||
switch number.kind {
|
||||
case .negative:
|
||||
print("- ", terminator: "")
|
||||
case .zero:
|
||||
print("0 ", terminator: "")
|
||||
case .positive:
|
||||
print("+ ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
-> printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
<< + + - 0 - 0 +
|
||||
// Prints "+ + - 0 - 0 + ".
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
Workaround for rdar://26016325
|
||||
-->
|
||||
|
||||
This function, `printIntegerKinds(_:)`,
|
||||
takes an input array of `Int` values and iterates over those values in turn.
|
||||
For each integer in the array,
|
||||
the function considers the `kind` computed property for that integer,
|
||||
and prints an appropriate description.
|
||||
|
||||
> Note: `number.kind` is already known to be of type `Int.Kind`.
|
||||
> Because of this, all of the `Int.Kind` case values
|
||||
> can be written in shorthand form inside the `switch` statement,
|
||||
> such as `.negative` rather than `Int.Kind.negative`.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
1315
skills/programming-swift/LanguageGuide/Functions.md
Normal file
1315
skills/programming-swift/LanguageGuide/Functions.md
Normal file
File diff suppressed because it is too large
Load Diff
2057
skills/programming-swift/LanguageGuide/Generics.md
Normal file
2057
skills/programming-swift/LanguageGuide/Generics.md
Normal file
File diff suppressed because it is too large
Load Diff
617
skills/programming-swift/LanguageGuide/Inheritance.md
Normal file
617
skills/programming-swift/LanguageGuide/Inheritance.md
Normal file
@@ -0,0 +1,617 @@
|
||||
# Inheritance
|
||||
|
||||
Subclass to add or override functionality.
|
||||
|
||||
A class can *inherit* methods, properties, and other characteristics
|
||||
from another class.
|
||||
When one class inherits from another,
|
||||
the inheriting class is known as a *subclass*,
|
||||
and the class it inherits from is known as its *superclass*.
|
||||
Inheritance is a fundamental behavior that differentiates classes
|
||||
from other types in Swift.
|
||||
|
||||
Classes in Swift can call and access
|
||||
methods, properties, and subscripts belonging to their superclass
|
||||
and can provide their own overriding versions of those methods, properties, and subscripts
|
||||
to refine or modify their behavior.
|
||||
Swift helps to ensure your overrides are correct
|
||||
by checking that the override definition has a matching superclass definition.
|
||||
|
||||
Classes can also add property observers to inherited properties
|
||||
in order to be notified when the value of a property changes.
|
||||
Property observers can be added to any property,
|
||||
regardless of whether it was originally defined as a stored or computed property.
|
||||
|
||||
## Defining a Base Class
|
||||
|
||||
Any class that doesn't inherit from another class is known as a *base class*.
|
||||
|
||||
> Note: Swift classes don't inherit from a universal base class.
|
||||
> Classes you define without specifying a superclass
|
||||
> automatically become base classes for you to build upon.
|
||||
|
||||
The example below defines a base class called `Vehicle`.
|
||||
This base class defines a stored property called `currentSpeed`,
|
||||
with a default value of `0.0` (inferring a property type of `Double`).
|
||||
The `currentSpeed` property's value is used by
|
||||
a read-only computed `String` property called `description`
|
||||
to create a description of the vehicle.
|
||||
|
||||
The `Vehicle` base class also defines a method called `makeNoise`.
|
||||
This method doesn't actually do anything for a base `Vehicle` instance,
|
||||
but will be customized by subclasses of `Vehicle` later on:
|
||||
|
||||
```swift
|
||||
class Vehicle {
|
||||
var currentSpeed = 0.0
|
||||
var description: String {
|
||||
return "traveling at \(currentSpeed) miles per hour"
|
||||
}
|
||||
func makeNoise() {
|
||||
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class Vehicle {
|
||||
var currentSpeed = 0.0
|
||||
var description: String {
|
||||
return "traveling at \(currentSpeed) miles per hour"
|
||||
}
|
||||
func makeNoise() {
|
||||
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
You create a new instance of `Vehicle` with *initializer syntax*,
|
||||
which is written as a type name followed by empty parentheses:
|
||||
|
||||
```swift
|
||||
let someVehicle = Vehicle()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let someVehicle = Vehicle()
|
||||
```
|
||||
-->
|
||||
|
||||
Having created a new `Vehicle` instance,
|
||||
you can access its `description` property to print
|
||||
a human-readable description of the vehicle's current speed:
|
||||
|
||||
```swift
|
||||
print("Vehicle: \(someVehicle.description)")
|
||||
// Vehicle: traveling at 0.0 miles per hour
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> print("Vehicle: \(someVehicle.description)")
|
||||
</ Vehicle: traveling at 0.0 miles per hour
|
||||
```
|
||||
-->
|
||||
|
||||
The `Vehicle` class defines common characteristics for an arbitrary vehicle,
|
||||
but isn't much use in itself.
|
||||
To make it more useful,
|
||||
you need to refine it to describe more specific kinds of vehicles.
|
||||
|
||||
## Subclassing
|
||||
|
||||
*Subclassing* is the act of basing a new class on an existing class.
|
||||
The subclass inherits characteristics from the existing class, which you can then refine.
|
||||
You can also add new characteristics to the subclass.
|
||||
|
||||
To indicate that a subclass has a superclass,
|
||||
write the subclass name before the superclass name,
|
||||
separated by a colon:
|
||||
|
||||
```swift
|
||||
class SomeSubclass: SomeSuperclass {
|
||||
// subclass definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `protocolSyntax`
|
||||
|
||||
```swifttest
|
||||
>> class SomeSuperclass {}
|
||||
-> class SomeSubclass: SomeSuperclass {
|
||||
// subclass definition goes here
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The following example defines a subclass called `Bicycle`,
|
||||
with a superclass of `Vehicle`:
|
||||
|
||||
```swift
|
||||
class Bicycle: Vehicle {
|
||||
var hasBasket = false
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class Bicycle: Vehicle {
|
||||
var hasBasket = false
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The new `Bicycle` class automatically gains all of the characteristics of `Vehicle`,
|
||||
such as its `currentSpeed` and `description` properties and its `makeNoise()` method.
|
||||
|
||||
In addition to the characteristics it inherits,
|
||||
the `Bicycle` class defines a new stored property,
|
||||
`hasBasket`, with a default value of `false`
|
||||
(inferring a type of `Bool` for the property).
|
||||
|
||||
By default, any new `Bicycle` instance you create will not have a basket.
|
||||
You can set the `hasBasket` property to `true` for a particular `Bicycle` instance
|
||||
after that instance is created:
|
||||
|
||||
```swift
|
||||
let bicycle = Bicycle()
|
||||
bicycle.hasBasket = true
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let bicycle = Bicycle()
|
||||
-> bicycle.hasBasket = true
|
||||
```
|
||||
-->
|
||||
|
||||
You can also modify the inherited `currentSpeed` property of a `Bicycle` instance,
|
||||
and query the instance's inherited `description` property:
|
||||
|
||||
```swift
|
||||
bicycle.currentSpeed = 15.0
|
||||
print("Bicycle: \(bicycle.description)")
|
||||
// Bicycle: traveling at 15.0 miles per hour
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> bicycle.currentSpeed = 15.0
|
||||
-> print("Bicycle: \(bicycle.description)")
|
||||
</ Bicycle: traveling at 15.0 miles per hour
|
||||
```
|
||||
-->
|
||||
|
||||
Subclasses can themselves be subclassed.
|
||||
The next example creates a subclass of `Bicycle` for a two-seater bicycle
|
||||
known as a “tandem”:
|
||||
|
||||
```swift
|
||||
class Tandem: Bicycle {
|
||||
var currentNumberOfPassengers = 0
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class Tandem: Bicycle {
|
||||
var currentNumberOfPassengers = 0
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
`Tandem` inherits all of the properties and methods from `Bicycle`,
|
||||
which in turn inherits all of the properties and methods from `Vehicle`.
|
||||
The `Tandem` subclass also adds a new stored property called `currentNumberOfPassengers`,
|
||||
with a default value of `0`.
|
||||
|
||||
If you create an instance of `Tandem`,
|
||||
you can work with any of its new and inherited properties,
|
||||
and query the read-only `description` property it inherits from `Vehicle`:
|
||||
|
||||
```swift
|
||||
let tandem = Tandem()
|
||||
tandem.hasBasket = true
|
||||
tandem.currentNumberOfPassengers = 2
|
||||
tandem.currentSpeed = 22.0
|
||||
print("Tandem: \(tandem.description)")
|
||||
// Tandem: traveling at 22.0 miles per hour
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let tandem = Tandem()
|
||||
-> tandem.hasBasket = true
|
||||
-> tandem.currentNumberOfPassengers = 2
|
||||
-> tandem.currentSpeed = 22.0
|
||||
-> print("Tandem: \(tandem.description)")
|
||||
</ Tandem: traveling at 22.0 miles per hour
|
||||
```
|
||||
-->
|
||||
|
||||
## Overriding
|
||||
|
||||
A subclass can provide its own custom implementation of
|
||||
an instance method, type method, instance property, type property, or subscript
|
||||
that it would otherwise inherit from a superclass.
|
||||
This is known as *overriding*.
|
||||
|
||||
To override a characteristic that would otherwise be inherited,
|
||||
you prefix your overriding definition with the `override` keyword.
|
||||
Doing so clarifies that you intend to provide an override
|
||||
and haven't provided a matching definition by mistake.
|
||||
Overriding by accident can cause unexpected behavior,
|
||||
and any overrides without the `override` keyword are
|
||||
diagnosed as an error when your code is compiled.
|
||||
|
||||
The `override` keyword also prompts the Swift compiler
|
||||
to check that your overriding class's superclass (or one of its parents)
|
||||
has a declaration that matches the one you provided for the override.
|
||||
This check ensures that your overriding definition is correct.
|
||||
|
||||
### Accessing Superclass Methods, Properties, and Subscripts
|
||||
|
||||
When you provide a method, property, or subscript override for a subclass,
|
||||
it's sometimes useful to use the existing superclass implementation
|
||||
as part of your override.
|
||||
For example, you can refine the behavior of that existing implementation,
|
||||
or store a modified value in an existing inherited variable.
|
||||
|
||||
Where this is appropriate,
|
||||
you access the superclass version of a method, property, or subscript
|
||||
by using the `super` prefix:
|
||||
|
||||
- An overridden method named `someMethod()` can call the superclass version of `someMethod()`
|
||||
by calling `super.someMethod()` within the overriding method implementation.
|
||||
- An overridden property called `someProperty` can access the superclass version of `someProperty`
|
||||
as `super.someProperty` within the overriding getter or setter implementation.
|
||||
- An overridden subscript for `someIndex` can access the superclass version of the same subscript
|
||||
as `super[someIndex]` from within the overriding subscript implementation.
|
||||
|
||||
### Overriding Methods
|
||||
|
||||
You can override an inherited instance or type method
|
||||
to provide a tailored or alternative implementation of the method within your subclass.
|
||||
|
||||
The following example defines a new subclass of `Vehicle` called `Train`,
|
||||
which overrides the `makeNoise()` method that `Train` inherits from `Vehicle`:
|
||||
|
||||
```swift
|
||||
class Train: Vehicle {
|
||||
override func makeNoise() {
|
||||
print("Choo Choo")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class Train: Vehicle {
|
||||
override func makeNoise() {
|
||||
print("Choo Choo")
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
If you create a new instance of `Train` and call its `makeNoise()` method,
|
||||
you can see that the `Train` subclass version of the method is called:
|
||||
|
||||
```swift
|
||||
let train = Train()
|
||||
train.makeNoise()
|
||||
// Prints "Choo Choo".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let train = Train()
|
||||
-> train.makeNoise()
|
||||
<- Choo Choo
|
||||
```
|
||||
-->
|
||||
|
||||
### Overriding Properties
|
||||
|
||||
You can override an inherited instance or type property
|
||||
to provide your own custom getter and setter for that property,
|
||||
or to add property observers to enable the overriding property
|
||||
to observe when the underlying property value changes.
|
||||
|
||||
#### Overriding Property Getters and Setters
|
||||
|
||||
You can provide a custom getter (and setter, if appropriate)
|
||||
to override *any* inherited property,
|
||||
regardless of whether the inherited property is implemented as
|
||||
a stored or computed property at source.
|
||||
The stored or computed nature of an inherited property isn't known by a subclass ---
|
||||
it only knows that the inherited property has a certain name and type.
|
||||
You must always state both the name and the type of the property you are overriding,
|
||||
to enable the compiler to check that your override matches
|
||||
a superclass property with the same name and type.
|
||||
|
||||
You can present an inherited read-only property as a read-write property
|
||||
by providing both a getter and a setter in your subclass property override.
|
||||
You can't, however, present an inherited read-write property as a read-only property.
|
||||
|
||||
> Note: If you provide a setter as part of a property override,
|
||||
> you must also provide a getter for that override.
|
||||
> If you don't want to modify the inherited property's value within the overriding getter,
|
||||
> you can simply pass through the inherited value
|
||||
> by returning `super.someProperty` from the getter,
|
||||
> where `someProperty` is the name of the property you are overriding.
|
||||
|
||||
The following example defines a new class called `Car`,
|
||||
which is a subclass of `Vehicle`.
|
||||
The `Car` class introduces a new stored property called `gear`,
|
||||
with a default integer value of `1`.
|
||||
The `Car` class also overrides the `description` property it inherits from `Vehicle`,
|
||||
to provide a custom description that includes the current gear:
|
||||
|
||||
```swift
|
||||
class Car: Vehicle {
|
||||
var gear = 1
|
||||
override var description: String {
|
||||
return super.description + " in gear \(gear)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class Car: Vehicle {
|
||||
var gear = 1
|
||||
override var description: String {
|
||||
return super.description + " in gear \(gear)"
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The override of the `description` property starts by calling `super.description`,
|
||||
which returns the `Vehicle` class's `description` property.
|
||||
The `Car` class's version of `description` then adds some extra text onto
|
||||
the end of this description to provide information about the current gear.
|
||||
|
||||
If you create an instance of the `Car` class
|
||||
and set its `gear` and `currentSpeed` properties,
|
||||
you can see that its `description` property returns
|
||||
the tailored description defined within the `Car` class:
|
||||
|
||||
```swift
|
||||
let car = Car()
|
||||
car.currentSpeed = 25.0
|
||||
car.gear = 3
|
||||
print("Car: \(car.description)")
|
||||
// Car: traveling at 25.0 miles per hour in gear 3
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let car = Car()
|
||||
-> car.currentSpeed = 25.0
|
||||
-> car.gear = 3
|
||||
-> print("Car: \(car.description)")
|
||||
</ Car: traveling at 25.0 miles per hour in gear 3
|
||||
```
|
||||
-->
|
||||
|
||||
#### Overriding Property Observers
|
||||
|
||||
You can use property overriding to add property observers to an inherited property.
|
||||
This enables you to be notified when the value of an inherited property changes,
|
||||
regardless of how that property was originally implemented.
|
||||
For more information on property observers, see <doc:Properties#Property-Observers>.
|
||||
|
||||
> Note: You can't add property observers to
|
||||
> inherited constant stored properties or inherited read-only computed properties.
|
||||
> The value of these properties can't be set,
|
||||
> and so it isn't appropriate to provide a `willSet` or `didSet` implementation
|
||||
> as part of an override.
|
||||
>
|
||||
> Note also that you can't provide both
|
||||
> an overriding setter and an overriding property observer for the same property.
|
||||
> If you want to observe changes to a property's value,
|
||||
> and you are already providing a custom setter for that property,
|
||||
> you can simply observe any value changes from within the custom setter.
|
||||
|
||||
The following example defines a new class called `AutomaticCar`,
|
||||
which is a subclass of `Car`.
|
||||
The `AutomaticCar` class represents a car with an automatic gearbox,
|
||||
which automatically selects an appropriate gear to use based on the current speed:
|
||||
|
||||
```swift
|
||||
class AutomaticCar: Car {
|
||||
override var currentSpeed: Double {
|
||||
didSet {
|
||||
gear = Int(currentSpeed / 10.0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> class AutomaticCar: Car {
|
||||
override var currentSpeed: Double {
|
||||
didSet {
|
||||
gear = Int(currentSpeed / 10.0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Whenever you set the `currentSpeed` property of an `AutomaticCar` instance,
|
||||
the property's `didSet` observer sets the instance's `gear` property to
|
||||
an appropriate choice of gear for the new speed.
|
||||
Specifically, the property observer chooses a gear that's
|
||||
the new `currentSpeed` value divided by `10`,
|
||||
rounded down to the nearest integer, plus `1`.
|
||||
A speed of `35.0` produces a gear of `4`:
|
||||
|
||||
```swift
|
||||
let automatic = AutomaticCar()
|
||||
automatic.currentSpeed = 35.0
|
||||
print("AutomaticCar: \(automatic.description)")
|
||||
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `inheritance`
|
||||
|
||||
```swifttest
|
||||
-> let automatic = AutomaticCar()
|
||||
-> automatic.currentSpeed = 35.0
|
||||
-> print("AutomaticCar: \(automatic.description)")
|
||||
</ AutomaticCar: traveling at 35.0 miles per hour in gear 4
|
||||
```
|
||||
-->
|
||||
|
||||
## Preventing Overrides
|
||||
|
||||
You can prevent a method, property, or subscript from being overridden
|
||||
by marking it as *final*.
|
||||
Do this by writing the `final` modifier before
|
||||
the method, property, or subscript's introducer keyword
|
||||
(such as `final var`, `final func`, `final class func`, and `final subscript`).
|
||||
|
||||
Any attempt to override a final method, property, or subscript in a subclass
|
||||
is reported as a compile-time error.
|
||||
Methods, properties, or subscripts that you add to a class in an extension
|
||||
can also be marked as final within the extension's definition.
|
||||
For more information, see <doc:Extensions>.
|
||||
|
||||
<!--
|
||||
- test: `finalPreventsOverriding`
|
||||
|
||||
```swifttest
|
||||
-> class C {
|
||||
final var someVar = 0
|
||||
final func someFunction() {
|
||||
print("In someFunction")
|
||||
}
|
||||
}
|
||||
-> class D : C {
|
||||
override var someVar: Int {
|
||||
get { return 1 }
|
||||
set {}
|
||||
}
|
||||
override func someFunction() {
|
||||
print("In overridden someFunction")
|
||||
}
|
||||
}
|
||||
!$ error: property overrides a 'final' property
|
||||
!! override var someVar: Int {
|
||||
!! ^
|
||||
!$ note: overridden declaration is here
|
||||
!! final var someVar = 0
|
||||
!! ^
|
||||
!$ error: instance method overrides a 'final' instance method
|
||||
!! override func someFunction() {
|
||||
!! ^
|
||||
!$ note: overridden declaration is here
|
||||
!! final func someFunction() {
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
You can mark an entire class as final by writing the `final` modifier
|
||||
before the `class` keyword in its class definition (`final class`).
|
||||
Any attempt to subclass a final class is reported as a compile-time error.
|
||||
|
||||
<!--
|
||||
- test: `finalClassPreventsOverriding`
|
||||
|
||||
```swifttest
|
||||
-> final class C {
|
||||
var someVar = 0
|
||||
func someFunction() {
|
||||
print("In someFunction")
|
||||
}
|
||||
}
|
||||
-> class D : C {
|
||||
override var someVar: Int {
|
||||
get { return 1 }
|
||||
set {}
|
||||
}
|
||||
override func someFunction() {
|
||||
print("In overridden someFunction")
|
||||
}
|
||||
}
|
||||
!$ error: property overrides a 'final' property
|
||||
!! override var someVar: Int {
|
||||
!! ^
|
||||
!$ note: overridden declaration is here
|
||||
!! var someVar = 0
|
||||
!! ^
|
||||
!$ error: instance method overrides a 'final' instance method
|
||||
!! override func someFunction() {
|
||||
!! ^
|
||||
!$ note: overridden declaration is here
|
||||
!! func someFunction() {
|
||||
!! ^
|
||||
!$ error: inheritance from a final class 'C'
|
||||
!! class D : C {
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: I should probably provide an example here.
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: provide more information about function signatures,
|
||||
and what does / doesn't make them unique.
|
||||
For example, the parameter names don't have to match
|
||||
in order for a function to override a similar signature in its parent.
|
||||
(This is true for both of the function declaration syntaxes.)
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Mention that you can return more-specific types, and take less-specific types,
|
||||
when overriding methods that use optionals / unchecked optionals.
|
||||
|
||||
TODO: Overriding Type Methods
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
2965
skills/programming-swift/LanguageGuide/Initialization.md
Normal file
2965
skills/programming-swift/LanguageGuide/Initialization.md
Normal file
File diff suppressed because it is too large
Load Diff
774
skills/programming-swift/LanguageGuide/Macros.md
Normal file
774
skills/programming-swift/LanguageGuide/Macros.md
Normal file
@@ -0,0 +1,774 @@
|
||||
# Macros
|
||||
|
||||
Use macros to generate code at compile time.
|
||||
|
||||
Macros transform your source code when you compile it,
|
||||
letting you avoid writing repetitive code by hand.
|
||||
During compilation,
|
||||
Swift expands any macros in your code before building your code as usual.
|
||||
|
||||

|
||||
|
||||
Expanding a macro is always an additive operation:
|
||||
Macros add new code,
|
||||
but they never delete or modify existing code.
|
||||
|
||||
Both the input to a macro and the output of macro expansion
|
||||
are checked to ensure they're syntactically valid Swift code.
|
||||
Likewise, the values you pass to a macro
|
||||
and the values in code generated by a macro
|
||||
are checked to ensure they have the correct types.
|
||||
In addition,
|
||||
if the macro's implementation encounters an error when expanding that macro,
|
||||
the compiler treats this as a compilation error.
|
||||
These guarantees make it easier to reason about code that uses macros,
|
||||
and they make it easier to identify issues
|
||||
like using a macro incorrectly
|
||||
or a macro implementation that has a bug.
|
||||
|
||||
Swift has two kinds of macros:
|
||||
|
||||
- *Freestanding macros* appear on their own,
|
||||
without being attached to a declaration.
|
||||
|
||||
- *Attached macros* modify the declaration that they're attached to.
|
||||
|
||||
You call attached and freestanding macros slightly differently,
|
||||
but they both follow the same model for macro expansion,
|
||||
and you implement them both using the same approach.
|
||||
The following sections describe both kinds of macros in more detail.
|
||||
|
||||
## Freestanding Macros
|
||||
|
||||
To call a freestanding macro,
|
||||
you write a number sign (`#`) before its name,
|
||||
and you write any arguments to the macro in parentheses after its name.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
func myFunction() {
|
||||
print("Currently running \(#function)")
|
||||
#warning("Something's wrong")
|
||||
}
|
||||
```
|
||||
|
||||
In the first line,
|
||||
`#function` calls the [`function()`][] macro from the Swift standard library.
|
||||
When you compile this code,
|
||||
Swift calls that macro's implementation,
|
||||
which replaces `#function` with the name of the current function.
|
||||
When you run this code and call `myFunction()`,
|
||||
it prints "Currently running myFunction()".
|
||||
In the second line,
|
||||
`#warning` calls the [`warning(_:)`][] macro from the Swift standard library
|
||||
to produce a custom compile-time warning.
|
||||
|
||||
[`function()`]: https://developer.apple.com/documentation/swift/function()
|
||||
[`warning(_:)`]: https://developer.apple.com/documentation/swift/warning(_:)
|
||||
|
||||
Freestanding macros can produce a value, like `#function` does,
|
||||
or they can perform an action at compile time, like `#warning` does.
|
||||
<!-- SE-0397: or they can generate new declarations. -->
|
||||
|
||||
## Attached Macros
|
||||
|
||||
To call an attached macro,
|
||||
you write an at sign (`@`) before its name,
|
||||
and you write any arguments to the macro in parentheses after its name.
|
||||
|
||||
Attached macros modify the declaration that they're attached to.
|
||||
They add code to that declaration,
|
||||
like defining a new method or adding conformance to a protocol.
|
||||
|
||||
For example, consider the following code
|
||||
that doesn't use macros:
|
||||
|
||||
```swift
|
||||
struct SundaeToppings: OptionSet {
|
||||
let rawValue: Int
|
||||
static let nuts = SundaeToppings(rawValue: 1 << 0)
|
||||
static let cherry = SundaeToppings(rawValue: 1 << 1)
|
||||
static let fudge = SundaeToppings(rawValue: 1 << 2)
|
||||
}
|
||||
```
|
||||
|
||||
In this code,
|
||||
each of the options in the `SundaeToppings` option set
|
||||
includes a call to the initializer,
|
||||
which is repetitive and manual.
|
||||
It would be easy to make a mistake when adding a new option,
|
||||
like typing the wrong number at the end of the line.
|
||||
|
||||
Here's a version of this code that uses a macro instead:
|
||||
|
||||
```swift
|
||||
@OptionSet<Int>
|
||||
struct SundaeToppings {
|
||||
private enum Options: Int {
|
||||
case nuts
|
||||
case cherry
|
||||
case fudge
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This version of `SundaeToppings` calls an `@OptionSet` macro.
|
||||
The macro reads the list of cases in the private enumeration,
|
||||
generates the list of constants for each option,
|
||||
and adds a conformance to the [`OptionSet`][] protocol.
|
||||
|
||||
[`OptionSet`]: https://developer.apple.com/documentation/swift/optionset
|
||||
|
||||
<!--
|
||||
When the @OptionSet macro comes back, change both links back:
|
||||
|
||||
[`@OptionSet`]: https://developer.apple.com/documentation/swift/optionset-swift.macro
|
||||
[`OptionSet`]: https://developer.apple.com/documentation/swift/optionset-swift.protocol
|
||||
-->
|
||||
|
||||
For comparison,
|
||||
here's what the expanded version of the `@OptionSet` macro looks like.
|
||||
You don't write this code,
|
||||
and you would see it only if you specifically asked Swift
|
||||
to show the macro's expansion.
|
||||
|
||||
```swift
|
||||
struct SundaeToppings {
|
||||
private enum Options: Int {
|
||||
case nuts
|
||||
case cherry
|
||||
case fudge
|
||||
}
|
||||
|
||||
typealias RawValue = Int
|
||||
var rawValue: RawValue
|
||||
init() { self.rawValue = 0 }
|
||||
init(rawValue: RawValue) { self.rawValue = rawValue }
|
||||
static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)
|
||||
static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)
|
||||
static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)
|
||||
}
|
||||
extension SundaeToppings: OptionSet { }
|
||||
```
|
||||
|
||||
All of the code after the private enumeration
|
||||
comes from the `@OptionSet` macro.
|
||||
The version of `SundaeToppings`
|
||||
that uses a macro to generate all of the static variables
|
||||
is easier to read and easier to maintain
|
||||
than the manually coded version, earlier.
|
||||
|
||||
## Macro Declarations
|
||||
|
||||
In most Swift code,
|
||||
when you implement a symbol, like a function or type,
|
||||
there's no separate declaration.
|
||||
However, for macros, the declaration and implementation are separate.
|
||||
A macro's declaration contains its name,
|
||||
the parameters it takes,
|
||||
where it can be used,
|
||||
and what kind of code it generates.
|
||||
A macro's implementation contains the code
|
||||
that expands the macro by generating Swift code.
|
||||
|
||||
You introduce a macro declaration with the `macro` keyword.
|
||||
For example,
|
||||
here's part of the declaration for
|
||||
the `@OptionSet` macro used in the previous example:
|
||||
|
||||
```swift
|
||||
public macro OptionSet<RawType>() =
|
||||
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
||||
```
|
||||
|
||||
The first line
|
||||
specifies the macro's name and its arguments ---
|
||||
the name is `OptionSet`, and it doesn't take any arguments.
|
||||
The second line
|
||||
uses the [`externalMacro(module:type:)`][] macro from the Swift standard library
|
||||
to tell Swift where the macro's implementation is located.
|
||||
In this case,
|
||||
the `SwiftMacros` module
|
||||
contains a type named `OptionSetMacro`,
|
||||
which implements the `@OptionSet` macro.
|
||||
|
||||
[`externalMacro(module:type:)`]: https://developer.apple.com/documentation/swift/externalmacro(module:type:)
|
||||
|
||||
Because `OptionSet` is an attached macro,
|
||||
its name uses upper camel case,
|
||||
like the names for structures and classes.
|
||||
Freestanding macros have lower camel case names,
|
||||
like the names for variables and functions.
|
||||
|
||||
> Note:
|
||||
> Macros are always declared as `public`.
|
||||
> Because the code that declares a macro
|
||||
> is in a different module from code that uses that macro,
|
||||
> there isn't anywhere you could apply a nonpublic macro.
|
||||
|
||||
A macro declaration defines the macro's *roles* ---
|
||||
the places in source code where that macro can be called,
|
||||
and the kinds of code the macro can generate.
|
||||
Every macro has one or more roles,
|
||||
which you write as part of the attributes
|
||||
at the beginning of the macro declaration.
|
||||
Here's a bit more of the declaration for `@OptionSet`,
|
||||
including the attributes for its roles:
|
||||
|
||||
```swift
|
||||
@attached(member)
|
||||
@attached(extension, conformances: OptionSet)
|
||||
public macro OptionSet<RawType>() =
|
||||
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
||||
```
|
||||
|
||||
The `@attached` attribute appears twice in this declaration,
|
||||
once for each macro role.
|
||||
The first use, `@attached(member)`, indicates that the macro
|
||||
adds new members to the type you apply it to.
|
||||
The `@OptionSet` macro adds an `init(rawValue:)` initializer
|
||||
that's required by the `OptionSet` protocol,
|
||||
as well as some additional members.
|
||||
The second use, `@attached(extension, conformances: OptionSet)`,
|
||||
tells you that `@OptionSet`
|
||||
adds conformance to the `OptionSet` protocol.
|
||||
The `@OptionSet` macro
|
||||
extends the type that you apply the macro to,
|
||||
to add conformance to the `OptionSet` protocol.
|
||||
|
||||
For a freestanding macro,
|
||||
you write the `@freestanding` attribute to specify its role:
|
||||
|
||||
```swift
|
||||
@freestanding(expression)
|
||||
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
|
||||
/* ... location of the macro implementation... */
|
||||
```
|
||||
|
||||
<!--
|
||||
Elided the implementation of #line above
|
||||
because it's a compiler built-in:
|
||||
|
||||
public macro line<T: ExpressibleByIntegerLiteral>() -> T = Builtin.LineMacro
|
||||
-->
|
||||
|
||||
The `#line` macro above has the `expression` role.
|
||||
An expression macro produces a value,
|
||||
or performs a compile-time action like generating a warning.
|
||||
|
||||
In addition to the macro's role,
|
||||
a macro's declaration provides information about
|
||||
the names of the symbols that the macro generates.
|
||||
When a macro declaration provides a list of names,
|
||||
it's guaranteed to produce only declarations that use those names,
|
||||
which helps you understand and debug the generated code.
|
||||
Here's the full declaration of `@OptionSet`:
|
||||
|
||||
```swift
|
||||
@attached(member, names: named(RawValue), named(rawValue),
|
||||
named(`init`), arbitrary)
|
||||
@attached(extension, conformances: OptionSet)
|
||||
public macro OptionSet<RawType>() =
|
||||
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
||||
```
|
||||
|
||||
In the declaration above,
|
||||
the `@attached(member)` macro includes arguments after the `names:` label
|
||||
for each of the symbols that the `@OptionSet` macro generates.
|
||||
The macro adds declarations for symbols named
|
||||
`RawValue`, `rawValue`, and `init` ---
|
||||
because those names are known ahead of time,
|
||||
the macro declaration lists them explicitly.
|
||||
|
||||
The macro declaration also includes `arbitrary` after the list of names,
|
||||
allowing the macro to generate declarations
|
||||
whose names aren't known until you use the macro.
|
||||
For example,
|
||||
when the `@OptionSet` macro is applied to the `SundaeToppings` above,
|
||||
it generates type properties that correspond to the enumeration cases,
|
||||
`nuts`, `cherry`, and `fudge`.
|
||||
|
||||
For more information,
|
||||
including a full list of macro roles,
|
||||
see <doc:Attributes#attached> and <doc:Attributes#freestanding>
|
||||
in <doc:Attributes>.
|
||||
|
||||
## Macro Expansion
|
||||
|
||||
When building Swift code that uses macros,
|
||||
the compiler calls the macros' implementation to expand them.
|
||||
|
||||

|
||||
|
||||
Specifically, Swift expands macros in the following way:
|
||||
|
||||
1. The compiler reads the code,
|
||||
creating an in-memory representation of the syntax.
|
||||
|
||||
1. The compiler sends part of the in-memory representation
|
||||
to the macro implementation,
|
||||
which expands the macro.
|
||||
|
||||
1. The compiler replaces the macro call with its expanded form.
|
||||
|
||||
1. The compiler continues with compilation,
|
||||
using the expanded source code.
|
||||
|
||||
To go through the specific steps, consider the following:
|
||||
|
||||
```swift
|
||||
let magicNumber = #fourCharacterCode("ABCD")
|
||||
```
|
||||
|
||||
The `#fourCharacterCode` macro takes a string that's four characters long
|
||||
and returns an unsigned 32-bit integer
|
||||
that corresponds to the ASCII values in the string joined together.
|
||||
Some file formats use integers like this to identify data
|
||||
because they're compact but still readable in a debugger.
|
||||
The <doc:Macros#Implementing-a-Macro> section below
|
||||
shows how to implement this macro.
|
||||
|
||||
To expand the macros in the code above,
|
||||
the compiler reads the Swift file
|
||||
and creates an in-memory representation of that code
|
||||
known as an *abstract syntax tree*, or AST.
|
||||
The AST makes the code's structure explicit,
|
||||
which makes it easier to write code that interacts with that structure ---
|
||||
like a compiler or a macro implementation.
|
||||
Here's a representation of the AST for the code above,
|
||||
slightly simplified by omitting some extra detail:
|
||||
|
||||

|
||||
|
||||
The diagram above shows how the structure of this code
|
||||
is represented in memory.
|
||||
Each element in the AST
|
||||
corresponds to a part of the source code.
|
||||
The "Constant declaration" AST element
|
||||
has two child elements under it,
|
||||
which represent the two parts of a constant declaration:
|
||||
its name and its value.
|
||||
The "Macro call" element has child elements
|
||||
that represent the macro's name
|
||||
and the list of arguments being passed to the macro.
|
||||
|
||||
As part of constructing this AST,
|
||||
the compiler checks that the source code is valid Swift.
|
||||
For example, `#fourCharacterCode` takes a single argument,
|
||||
which must be a string.
|
||||
If you tried to pass an integer argument,
|
||||
or forgot the quotation mark (`"`) at the end of the string literal,
|
||||
you'd get an error at this point in the process.
|
||||
|
||||
The compiler finds the places in the code where you call a macro,
|
||||
and loads the external binary that implements those macros.
|
||||
For each macro call,
|
||||
the compiler passes part of the AST to that macro's implementation.
|
||||
Here's a representation of that partial AST:
|
||||
|
||||

|
||||
|
||||
The implementation of the `#fourCharacterCode` macro
|
||||
reads this partial AST as its input when expanding the macro.
|
||||
A macro's implementation
|
||||
operates only on the partial AST that it receives as its input,
|
||||
meaning a macro always expands the same way
|
||||
regardless of what code comes before and after it.
|
||||
This limitation helps make macro expansion easier to understand,
|
||||
and helps your code build faster
|
||||
because Swift can avoid expanding macros that haven't changed.
|
||||
<!-- TODO TR: Confirm -->
|
||||
Swift helps macro authors avoid accidentally reading other input
|
||||
by restricting the code that implements macros:
|
||||
|
||||
- The AST passed to a macro implementation
|
||||
contains only the AST elements that represent the macro,
|
||||
not any of the code that comes before or after it.
|
||||
|
||||
- The macro implementation runs in a sandboxed environment
|
||||
that prevents it from accessing the file system or the network.
|
||||
|
||||
In addition to these safeguards,
|
||||
the macro's author is responsible for not reading or modifying anything
|
||||
outside of the macro's inputs.
|
||||
For example, a macro's expansion must not depend on the current time of day.
|
||||
|
||||
The implementation of `#fourCharacterCode`
|
||||
generates a new AST containing the expanded code.
|
||||
Here's what that code returns to the compiler:
|
||||
|
||||

|
||||
|
||||
When the compiler receives this expansion,
|
||||
it replaces the AST element that contains the macro call
|
||||
with the element that contains the macro's expansion.
|
||||
After macro expansion,
|
||||
the compiler checks again to ensure
|
||||
the program is still syntactically valid Swift
|
||||
and all the types are correct.
|
||||
That produces a final AST that can be compiled as usual:
|
||||
|
||||

|
||||
|
||||
This AST corresponds to Swift code like this:
|
||||
|
||||
```swift
|
||||
let magicNumber = 1145258561 as UInt32
|
||||
```
|
||||
|
||||
In this example, the input source code has only one macro,
|
||||
but a real program could have several instances of the same macro
|
||||
and several calls to different macros.
|
||||
The compiler expands macros one at a time.
|
||||
|
||||
If one macro appears inside another,
|
||||
the outer macro is expanded first ---
|
||||
this lets the outer macro modify the inner macro before it's expanded.
|
||||
|
||||
<!-- OUTLINE
|
||||
|
||||
- TR: Is there any limit to nesting?
|
||||
TR: Is it valid to nest like this -- if so, anything to note about it?
|
||||
|
||||
```
|
||||
let something = #someMacro {
|
||||
struct A { }
|
||||
@someMacro struct B { }
|
||||
}
|
||||
```
|
||||
|
||||
- Macro recursion is limited.
|
||||
One macro can call another,
|
||||
but a given macro can't directly or indirectly call itself.
|
||||
The result of macro expansion can include other macros,
|
||||
but it can't include a macro that uses this macro in its expansion
|
||||
or declare a new macro.
|
||||
(TR: Likely need to iterate on details here)
|
||||
-->
|
||||
|
||||
## Implementing a Macro
|
||||
|
||||
To implement a macro, you make two components:
|
||||
A type that performs the macro expansion,
|
||||
and a library that declares the macro to expose it as API.
|
||||
These parts are built separately from code that uses the macro,
|
||||
even if you're developing the macro and its clients together,
|
||||
because the macro implementation runs
|
||||
as part of building the macro's clients.
|
||||
|
||||
To create a new macro using Swift Package Manager,
|
||||
run `swift package init --type macro` ---
|
||||
this creates several files,
|
||||
including a template for a macro implementation and declaration.
|
||||
|
||||
To add macros to an existing project,
|
||||
edit the beginning of your `Package.swift` file as follows:
|
||||
|
||||
- Set a Swift tools version of 5.9 or later in the `swift-tools-version` comment.
|
||||
- Import the `CompilerPluginSupport` module.
|
||||
- Include macOS 10.15 as a minimum deployment target in the `platforms` list.
|
||||
|
||||
The code below shows the beginning of an example `Package.swift` file.
|
||||
|
||||
```swift
|
||||
// swift-tools-version: 5.9
|
||||
|
||||
import PackageDescription
|
||||
import CompilerPluginSupport
|
||||
|
||||
let package = Package(
|
||||
name: "MyPackage",
|
||||
platforms: [ .iOS(.v17), .macOS(.v13)],
|
||||
// ...
|
||||
)
|
||||
```
|
||||
|
||||
Next, add a target for the macro implementation
|
||||
and a target for the macro library
|
||||
to your existing `Package.swift` file.
|
||||
For example,
|
||||
you can add something like the following,
|
||||
changing the names to match your project:
|
||||
|
||||
```swift
|
||||
targets: [
|
||||
// Macro implementation that performs the source transformations.
|
||||
.macro(
|
||||
name: "MyProjectMacros",
|
||||
dependencies: [
|
||||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
|
||||
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
|
||||
]
|
||||
),
|
||||
|
||||
// Library that exposes a macro as part of its API.
|
||||
.target(name: "MyProject", dependencies: ["MyProjectMacros"]),
|
||||
]
|
||||
```
|
||||
|
||||
The code above defines two targets:
|
||||
`MyProjectMacros` contains the implementation of the macros,
|
||||
and `MyProject` makes those macros available.
|
||||
|
||||
The implementation of a macro
|
||||
uses the [SwiftSyntax][] module to interact with Swift code
|
||||
in a structured way, using an AST.
|
||||
If you created a new macro package with Swift Package Manager,
|
||||
the generated `Package.swift` file
|
||||
automatically includes a dependency on SwiftSyntax.
|
||||
If you're adding macros to an existing project,
|
||||
add a dependency on SwiftSyntax in your `Package.swift` file:
|
||||
|
||||
[SwiftSyntax]: https://github.com/swiftlang/swift-syntax
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0")
|
||||
],
|
||||
```
|
||||
|
||||
Depending on your macro's role,
|
||||
there's a corresponding protocol from SwiftSyntax
|
||||
that the macro implementation conforms to.
|
||||
For example,
|
||||
consider `#fourCharacterCode` from the previous section.
|
||||
Here's a structure that implements that macro:
|
||||
|
||||
```swift
|
||||
import SwiftSyntax
|
||||
import SwiftSyntaxMacros
|
||||
|
||||
public struct FourCharacterCode: ExpressionMacro {
|
||||
public static func expansion(
|
||||
of node: some FreestandingMacroExpansionSyntax,
|
||||
in context: some MacroExpansionContext
|
||||
) throws -> ExprSyntax {
|
||||
guard let argument = node.argumentList.first?.expression,
|
||||
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
|
||||
segments.count == 1,
|
||||
case .stringSegment(let literalSegment)? = segments.first
|
||||
else {
|
||||
throw CustomError.message("Need a static string")
|
||||
}
|
||||
|
||||
let string = literalSegment.content.text
|
||||
guard let result = fourCharacterCode(for: string) else {
|
||||
throw CustomError.message("Invalid four-character code")
|
||||
}
|
||||
|
||||
return "\(raw: result) as UInt32"
|
||||
}
|
||||
}
|
||||
|
||||
private func fourCharacterCode(for characters: String) -> UInt32? {
|
||||
guard characters.count == 4 else { return nil }
|
||||
|
||||
var result: UInt32 = 0
|
||||
for character in characters {
|
||||
result = result << 8
|
||||
guard let asciiValue = character.asciiValue else { return nil }
|
||||
result += UInt32(asciiValue)
|
||||
}
|
||||
return result
|
||||
}
|
||||
enum CustomError: Error { case message(String) }
|
||||
```
|
||||
|
||||
If you're adding this macro to an existing Swift Package Manager project,
|
||||
add a type that acts as the entry point for the macro target
|
||||
and lists the macros that the target defines:
|
||||
|
||||
```swift
|
||||
import SwiftCompilerPlugin
|
||||
|
||||
@main
|
||||
struct MyProjectMacros: CompilerPlugin {
|
||||
var providingMacros: [Macro.Type] = [FourCharacterCode.self]
|
||||
}
|
||||
```
|
||||
|
||||
The `#fourCharacterCode` macro
|
||||
is a freestanding macro that produces an expression,
|
||||
so the `FourCharacterCode` type that implements it
|
||||
conforms to the `ExpressionMacro` protocol.
|
||||
The `ExpressionMacro` protocol has one requirement,
|
||||
an `expansion(of:in:)` method that expands the AST.
|
||||
For the list of macro roles and their corresponding SwiftSyntax protocols,
|
||||
see <doc:Attributes#attached> and <doc:Attributes#freestanding>
|
||||
in <doc:Attributes>.
|
||||
|
||||
To expand the `#fourCharacterCode` macro,
|
||||
Swift sends the AST for the code that uses this macro
|
||||
to the library that contains the macro implementation.
|
||||
Inside the library, Swift calls `FourCharacterCode.expansion(of:in:)`,
|
||||
passing in the AST and the context as arguments to the method.
|
||||
The implementation of `expansion(of:in:)`
|
||||
finds the string that was passed as an argument to `#fourCharacterCode`
|
||||
and calculates the corresponding 32-bit unsigned integer literal value.
|
||||
|
||||
In the example above,
|
||||
the first `guard` block extracts the string literal from the AST,
|
||||
assigning that AST element to `literalSegment`.
|
||||
The second `guard` block
|
||||
calls the private `fourCharacterCode(for:)` function.
|
||||
Both of these blocks throw an error if the macro is used incorrectly ---
|
||||
the error message becomes a compiler error
|
||||
at the malformed call site.
|
||||
For example,
|
||||
if you try to call the macro as `#fourCharacterCode("AB" + "CD")`
|
||||
the compiler shows the error "Need a static string".
|
||||
|
||||
The `expansion(of:in:)` method returns an instance of `ExprSyntax`,
|
||||
a type from SwiftSyntax that represents an expression in an AST.
|
||||
Because this type conforms to the `StringLiteralConvertible` protocol,
|
||||
the macro implementation uses a string literal
|
||||
as a lightweight syntax to create its result.
|
||||
All of the SwiftSyntax types that you return from a macro implementation
|
||||
conform to `StringLiteralConvertible`,
|
||||
so you can use this approach when implementing any kind of macro.
|
||||
|
||||
<!-- TODO contrast the `\(raw:)` and non-raw version. -->
|
||||
|
||||
<!--
|
||||
The return-a-string APIs come from here
|
||||
|
||||
https://github.com/swiftlang/swift-syntax/blob/main/Sources/SwiftSyntaxBuilder/Syntax%2BStringInterpolation.swift
|
||||
-->
|
||||
|
||||
<!-- OUTLINE:
|
||||
|
||||
- Note:
|
||||
Behind the scenes, Swift serializes and deserializes the AST,
|
||||
to pass the data across process boundaries,
|
||||
but your macro implementation doesn't need to deal with any of that.
|
||||
|
||||
- This method is also passed a macro-expansion context, which you use to:
|
||||
|
||||
+ Generate unique symbol names
|
||||
+ Produce diagnostics (`Diagnostic` and `SimpleDiagnosticMessage`)
|
||||
+ Find a node's location in source
|
||||
|
||||
- Macro expansion happens in their surrounding context.
|
||||
A macro can affect that environment if it needs to ---
|
||||
and a macro that has bugs can interfere with that environment.
|
||||
(Give guidance on when you'd do this. It should be rare.)
|
||||
|
||||
- Generated symbol names let a macro
|
||||
avoid accidentally interacting with symbols in that environment.
|
||||
To generate a unique symbol name,
|
||||
call the `MacroExpansionContext.makeUniqueName()` method.
|
||||
|
||||
- Ways to create a syntax node include
|
||||
Making an instance of the `Syntax` struct,
|
||||
or `SyntaxToken`
|
||||
or `ExprSyntax`.
|
||||
(Need to give folks some general ideas,
|
||||
and enough guidance so they can sort through
|
||||
all the various `SwiftSyntax` node types and find the right one.)
|
||||
|
||||
- Attached macros follow the same general model as expression macros,
|
||||
but with more moving parts.
|
||||
|
||||
- Pick the subprotocol of `AttachedMacro` to conform to,
|
||||
depending on which kind of attached macro you're making.
|
||||
[This is probably a table]
|
||||
|
||||
+ `AccessorMacro` goes with `@attached(accessor)`
|
||||
+ `ConformanceMacro` goes with `@attached(conformance)`
|
||||
[missing from the list under Declaring a Macro]
|
||||
+ `MemberMacro` goes with `@attached(member)`
|
||||
+ `PeerMacro` goes with `@attached(peer)`
|
||||
+ `MemberAttributeMacro` goes with `@member(memberAttribute)`
|
||||
|
||||
- Code example of conforming to `MemberMacro`.
|
||||
|
||||
```
|
||||
static func expansion<
|
||||
Declaration: DeclGroupSyntax,
|
||||
Context: MacroExpansionContext
|
||||
>(
|
||||
of node: AttributeSyntax,
|
||||
providingMembersOf declaration: Declaration,
|
||||
in context: Context
|
||||
) throws -> [DeclSyntax]
|
||||
```
|
||||
|
||||
- Adding a new member by making an instance of `Declaration`,
|
||||
and returning it as part of the `[DeclSyntax]` list.
|
||||
|
||||
-->
|
||||
|
||||
## Developing and Debugging Macros
|
||||
|
||||
Macros are well suited to development using tests:
|
||||
They transform one AST into another AST
|
||||
without depending on any external state,
|
||||
and without making changes to any external state.
|
||||
In addition, you can create syntax nodes from a string literal,
|
||||
which simplifies setting up the input for a test.
|
||||
You can also read the `description` property of an AST
|
||||
to get a string to compare against an expected value.
|
||||
For example,
|
||||
here's a test of the `#fourCharacterCode` macro from previous sections:
|
||||
|
||||
```swift
|
||||
let source: SourceFileSyntax =
|
||||
"""
|
||||
let abcd = #fourCharacterCode("ABCD")
|
||||
"""
|
||||
|
||||
let file = BasicMacroExpansionContext.KnownSourceFile(
|
||||
moduleName: "MyModule",
|
||||
fullFilePath: "test.swift"
|
||||
)
|
||||
|
||||
let context = BasicMacroExpansionContext(sourceFiles: [source: file])
|
||||
|
||||
let transformedSF = source.expand(
|
||||
macros:["fourCharacterCode": FourCharacterCode.self],
|
||||
in: context
|
||||
)
|
||||
|
||||
let expectedDescription =
|
||||
"""
|
||||
let abcd = 1145258561 as UInt32
|
||||
"""
|
||||
|
||||
precondition(transformedSF.description == expectedDescription)
|
||||
```
|
||||
|
||||
The example above tests the macro using a precondition,
|
||||
but you could use a testing framework instead.
|
||||
|
||||
<!-- OUTLINE:
|
||||
|
||||
- Ways to view the macro expansion while debugging.
|
||||
The SE prototype provides `-Xfrontend -dump-macro-expansions` for this.
|
||||
[TR: Is this flag what we should suggest folks use,
|
||||
or will there be better command-line options coming?]
|
||||
|
||||
- Use diagnostics for macros that have constraints/requirements
|
||||
so your code can give a meaningful error to users when those aren't met,
|
||||
instead of letting the compiler try & fail to build the generated code.
|
||||
|
||||
Additional APIs and concepts to introduce in the future,
|
||||
in no particular order:
|
||||
|
||||
- Using `SyntaxRewriter` and the visitor pattern for modifying the AST
|
||||
|
||||
- Adding a suggested correction using `FixIt`
|
||||
|
||||
- concept of trivia
|
||||
|
||||
- `TokenSyntax`
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
758
skills/programming-swift/LanguageGuide/MemorySafety.md
Normal file
758
skills/programming-swift/LanguageGuide/MemorySafety.md
Normal file
@@ -0,0 +1,758 @@
|
||||
# Memory Safety
|
||||
|
||||
Structure your code to avoid conflicts when accessing memory.
|
||||
|
||||
By default, Swift prevents unsafe behavior from happening in your code.
|
||||
For example,
|
||||
Swift ensures that variables are initialized before they're used,
|
||||
memory isn't accessed after it's been deallocated,
|
||||
and array indices are checked for out-of-bounds errors.
|
||||
|
||||
Swift also makes sure that multiple accesses
|
||||
to the same area of memory don't conflict,
|
||||
by requiring code that modifies a location in memory
|
||||
to have exclusive access to that memory.
|
||||
Because Swift manages memory automatically,
|
||||
most of the time you don't have to think about accessing memory at all.
|
||||
However,
|
||||
it's important to understand where potential conflicts can occur,
|
||||
so you can avoid writing code that has conflicting access to memory.
|
||||
If your code does contain conflicts,
|
||||
you'll get a compile-time or runtime error.
|
||||
|
||||
<!--
|
||||
TODO: maybe re-introduce this text...
|
||||
|
||||
Memory safety refers to...
|
||||
The term *safety* usually refers to :newTerm:`memory safety`...
|
||||
Unsafe access to memory is available, if you ask for it explicitly...
|
||||
-->
|
||||
|
||||
## Understanding Conflicting Access to Memory
|
||||
|
||||
Access to memory happens in your code
|
||||
when you do things like set the value of a variable
|
||||
or pass an argument to a function.
|
||||
For example,
|
||||
the following code contains both a read access and a write access:
|
||||
|
||||
```swift
|
||||
// A write access to the memory where one is stored.
|
||||
var one = 1
|
||||
|
||||
// A read access from the memory where one is stored.
|
||||
print("We're number \(one)!")
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-read-write`
|
||||
|
||||
```swifttest
|
||||
// A write access to the memory where one is stored.
|
||||
-> var one = 1
|
||||
|
||||
// A read access from the memory where one is stored.
|
||||
-> print("We're number \(one)!")
|
||||
<< We're number 1!
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
Might be worth a different example,
|
||||
or else I'm going to keep getting "We are Number One" stuck in my head.
|
||||
-->
|
||||
|
||||
A conflicting access to memory can occur
|
||||
when different parts of your code are trying
|
||||
to access the same location in memory at the same time.
|
||||
Multiple accesses to a location in memory at the same time
|
||||
can produce unpredictable or inconsistent behavior.
|
||||
In Swift, there are ways to modify a value
|
||||
that span several lines of code,
|
||||
making it possible to attempt to access a value
|
||||
in the middle of its own modification.
|
||||
|
||||
You can see a similar problem
|
||||
by thinking about how you update a budget
|
||||
that's written on a piece of paper.
|
||||
Updating the budget is a two-step process:
|
||||
First you add the items' names and prices,
|
||||
and then you change the total amount
|
||||
to reflect the items currently on the list.
|
||||
Before and after the update,
|
||||
you can read any information from the budget
|
||||
and get a correct answer,
|
||||
as shown in the figure below.
|
||||
|
||||

|
||||
|
||||
While you're adding items to the budget,
|
||||
it's in a temporary, invalid state
|
||||
because the total amount hasn't been updated
|
||||
to reflect the newly added items.
|
||||
Reading the total amount
|
||||
during the process of adding an item
|
||||
gives you incorrect information.
|
||||
|
||||
This example also demonstrates
|
||||
a challenge you may encounter
|
||||
when fixing conflicting access to memory:
|
||||
There are sometimes multiple ways to fix the conflict
|
||||
that produce different answers,
|
||||
and it's not always obvious which answer is correct.
|
||||
In this example,
|
||||
depending on whether you wanted the original total amount
|
||||
or the updated total amount,
|
||||
either $5 or $320 could be the correct answer.
|
||||
Before you can fix the conflicting access,
|
||||
you have to determine what it was intended to do.
|
||||
|
||||
> Note: If you've written concurrent or multithreaded code,
|
||||
> conflicting access to memory might be a familiar problem.
|
||||
> However,
|
||||
> the conflicting access discussed here can happen
|
||||
> on a single thread and
|
||||
> *doesn't* involve concurrent or multithreaded code.
|
||||
>
|
||||
> If you have conflicting access to memory
|
||||
> from within a single thread,
|
||||
> Swift guarantees that you'll get an error
|
||||
> at either compile time or runtime.
|
||||
> For multithreaded code,
|
||||
> use [Thread Sanitizer](https://developer.apple.com/documentation/xcode/diagnosing_memory_thread_and_crash_issues_early)
|
||||
> to help detect conflicting access across threads.
|
||||
|
||||
<!--
|
||||
TODO: The xref above doesn't seem to give enough information.
|
||||
What should I be looking for when I get to the linked page?
|
||||
-->
|
||||
|
||||
### Characteristics of Memory Access
|
||||
|
||||
There are three characteristics of memory access
|
||||
to consider in the context of conflicting access:
|
||||
whether the access is a read or a write,
|
||||
the duration of the access,
|
||||
and the location in memory being accessed.
|
||||
Specifically,
|
||||
a conflict occurs if you have two accesses
|
||||
that meet all of the following conditions:
|
||||
|
||||
- The accesses aren't both reads, and aren't both atomic.
|
||||
- They access the same location in memory.
|
||||
- Their durations overlap.
|
||||
|
||||
The difference between a read and write access
|
||||
is usually obvious:
|
||||
a write access changes the location in memory,
|
||||
but a read access doesn't.
|
||||
The location in memory
|
||||
refers to what is being accessed ---
|
||||
for example, a variable, constant, or property.
|
||||
The duration of a memory access
|
||||
is either instantaneous or long-term.
|
||||
|
||||
An access is *atomic* if
|
||||
it's a call to an atomic operation on [`Atomic`] or [`AtomicLazyReference`],
|
||||
or it it uses only C atomic operations;
|
||||
otherwise it's nonatomic.
|
||||
For a list of C atomic functions, see the `stdatomic(3)` man page.
|
||||
|
||||
[`Atomic`]: https://developer.apple.com/documentation/synchronization/atomic
|
||||
[`AtomicLazyReference`]: https://developer.apple.com/documentation/synchronization/atomiclazyreference
|
||||
|
||||
<!--
|
||||
Using the C atomic functions from Swift
|
||||
requires some shimming that's out of scope for TSPL - for example:
|
||||
https://github.com/apple/swift-atomics/tree/main/Sources/_AtomicsShims
|
||||
-->
|
||||
|
||||
An access is *instantaneous*
|
||||
if it's not possible for other code to run
|
||||
after that access starts but before it ends.
|
||||
By their nature, two instantaneous accesses can't happen at the same time.
|
||||
Most memory access is instantaneous.
|
||||
For example,
|
||||
all the read and write accesses in the code listing below are instantaneous:
|
||||
|
||||
```swift
|
||||
func oneMore(than number: Int) -> Int {
|
||||
return number + 1
|
||||
}
|
||||
|
||||
var myNumber = 1
|
||||
myNumber = oneMore(than: myNumber)
|
||||
print(myNumber)
|
||||
// Prints "2".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-instantaneous`
|
||||
|
||||
```swifttest
|
||||
-> func oneMore(than number: Int) -> Int {
|
||||
return number + 1
|
||||
}
|
||||
|
||||
-> var myNumber = 1
|
||||
-> myNumber = oneMore(than: myNumber)
|
||||
-> print(myNumber)
|
||||
<- 2
|
||||
```
|
||||
-->
|
||||
|
||||
However,
|
||||
there are several ways to access memory,
|
||||
called *long-term* accesses,
|
||||
that span the execution of other code.
|
||||
The difference between instantaneous access and long-term access
|
||||
is that it’s possible for other code to run
|
||||
after a long-term access starts but before it ends,
|
||||
which is called *overlap*.
|
||||
A long-term access can overlap
|
||||
with other long-term accesses and instantaneous accesses.
|
||||
|
||||
Overlapping accesses appear primarily in code that uses
|
||||
in-out parameters in functions and methods
|
||||
or mutating methods of a structure.
|
||||
The specific kinds of Swift code that use long-term accesses
|
||||
are discussed in the sections below.
|
||||
|
||||
## Conflicting Access to In-Out Parameters
|
||||
|
||||
A function has long-term write access
|
||||
to all of its in-out parameters.
|
||||
The write access for an in-out parameter starts
|
||||
after all of the non-in-out parameters have been evaluated
|
||||
and lasts for the entire duration of that function call.
|
||||
If there are multiple in-out parameters,
|
||||
the write accesses start in the same order as the parameters appear.
|
||||
|
||||
One consequence of this long-term write access
|
||||
is that you can't access the original
|
||||
variable that was passed as in-out,
|
||||
even if scoping rules and access control would otherwise permit it ---
|
||||
any access to the original creates a conflict.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
var stepSize = 1
|
||||
|
||||
func increment(_ number: inout Int) {
|
||||
number += stepSize
|
||||
}
|
||||
|
||||
increment(&stepSize)
|
||||
// Error: Conflicting accesses to stepSize.
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-increment`
|
||||
|
||||
```swifttest
|
||||
-> var stepSize = 1
|
||||
|
||||
-> func increment(_ number: inout Int) {
|
||||
number += stepSize
|
||||
}
|
||||
|
||||
-> increment(&stepSize)
|
||||
// Error: Conflicting accesses to stepSize.
|
||||
xx Simultaneous accesses to 0x10e8667d8, but modification requires exclusive access.
|
||||
xx Previous access (a modification) started at (0x10e86b032).
|
||||
xx Current access (a read) started at:
|
||||
```
|
||||
-->
|
||||
|
||||
In the code above,
|
||||
`stepSize` is a global variable,
|
||||
and it's normally accessible from within `increment(_:)`.
|
||||
However,
|
||||
the read access to `stepSize` overlaps with
|
||||
the write access to `number`.
|
||||
As shown in the figure below,
|
||||
both `number` and `stepSize` refer to the same location in memory.
|
||||
The read and write accesses
|
||||
refer to the same memory and they overlap,
|
||||
producing a conflict.
|
||||
|
||||

|
||||
|
||||
One way to solve this conflict
|
||||
is to make an explicit copy of `stepSize`:
|
||||
|
||||
```swift
|
||||
// Make an explicit copy.
|
||||
var copyOfStepSize = stepSize
|
||||
increment(©OfStepSize)
|
||||
|
||||
// Update the original.
|
||||
stepSize = copyOfStepSize
|
||||
// stepSize is now 2
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-increment-copy`
|
||||
|
||||
```swifttest
|
||||
>> var stepSize = 1
|
||||
>> func increment(_ number: inout Int) {
|
||||
>> number += stepSize
|
||||
>> }
|
||||
// Make an explicit copy.
|
||||
-> var copyOfStepSize = stepSize
|
||||
-> increment(©OfStepSize)
|
||||
|
||||
// Update the original.
|
||||
-> stepSize = copyOfStepSize
|
||||
/> stepSize is now \(stepSize)
|
||||
</ stepSize is now 2
|
||||
```
|
||||
-->
|
||||
|
||||
When you make a copy of `stepSize` before calling `increment(_:)`,
|
||||
it's clear that the value of `copyOfStepSize` is incremented
|
||||
by the current step size.
|
||||
The read access ends before the write access starts,
|
||||
so there isn't a conflict.
|
||||
|
||||
Another consequence of long-term write access
|
||||
to in-out parameters is that
|
||||
passing a single variable
|
||||
as the argument for multiple in-out parameters
|
||||
of the same function
|
||||
produces a conflict.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
func balance(_ x: inout Int, _ y: inout Int) {
|
||||
let sum = x + y
|
||||
x = sum / 2
|
||||
y = sum - x
|
||||
}
|
||||
var playerOneScore = 42
|
||||
var playerTwoScore = 30
|
||||
balance(&playerOneScore, &playerTwoScore) // OK
|
||||
balance(&playerOneScore, &playerOneScore)
|
||||
// Error: Conflicting accesses to playerOneScore.
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-balance`
|
||||
|
||||
```swifttest
|
||||
-> func balance(_ x: inout Int, _ y: inout Int) {
|
||||
let sum = x + y
|
||||
x = sum / 2
|
||||
y = sum - x
|
||||
}
|
||||
-> var playerOneScore = 42
|
||||
-> var playerTwoScore = 30
|
||||
-> balance(&playerOneScore, &playerTwoScore) // OK
|
||||
-> balance(&playerOneScore, &playerOneScore)
|
||||
// Error: Conflicting accesses to playerOneScore.
|
||||
!$ error: inout arguments are not allowed to alias each other
|
||||
!! balance(&playerOneScore, &playerOneScore)
|
||||
!! ^~~~~~~~~~~~~~~
|
||||
!$ note: previous aliasing argument
|
||||
!! balance(&playerOneScore, &playerOneScore)
|
||||
!! ^~~~~~~~~~~~~~~
|
||||
!$ error: overlapping accesses to 'playerOneScore', but modification requires exclusive access; consider copying to a local variable
|
||||
!! balance(&playerOneScore, &playerOneScore)
|
||||
!! ^~~~~~~~~~~~~~~
|
||||
!$ note: conflicting access is here
|
||||
!! balance(&playerOneScore, &playerOneScore)
|
||||
!! ^~~~~~~~~~~~~~~
|
||||
```
|
||||
-->
|
||||
|
||||
The `balance(_:_:)` function above
|
||||
modifies its two parameters
|
||||
to divide the total value evenly between them.
|
||||
Calling it with `playerOneScore` and `playerTwoScore` as arguments
|
||||
doesn't produce a conflict ---
|
||||
there are two write accesses that overlap in time,
|
||||
but they access different locations in memory.
|
||||
In contrast,
|
||||
passing `playerOneScore` as the value for both parameters
|
||||
produces a conflict
|
||||
because it tries to perform two write accesses
|
||||
to the same location in memory at the same time.
|
||||
|
||||
> Note: Because operators are functions,
|
||||
> they can also have long-term accesses to their in-out parameters.
|
||||
> For example, if `balance(_:_:)` was an operator function named `<^>`,
|
||||
> writing `playerOneScore <^> playerOneScore`
|
||||
> would result in the same conflict
|
||||
> as `balance(&playerOneScore, &playerOneScore)`.
|
||||
|
||||
## Conflicting Access to self in Methods
|
||||
|
||||
<!--
|
||||
This (probably?) applies to all value types,
|
||||
but structures are the only place you can observe it.
|
||||
Enumerations can have mutating methods
|
||||
but you can't mutate their associated values in place,
|
||||
and tuples can't have methods.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Methods behave like self is passed to the method as inout
|
||||
because, under the hood, that's exactly what happens.
|
||||
-->
|
||||
|
||||
A mutating method on a structure has write access to `self`
|
||||
for the duration of the method call.
|
||||
For example, consider a game where each player
|
||||
has a health amount, which decreases when taking damage,
|
||||
and an energy amount, which decreases when using special abilities.
|
||||
|
||||
```swift
|
||||
struct Player {
|
||||
var name: String
|
||||
var health: Int
|
||||
var energy: Int
|
||||
|
||||
static let maxHealth = 10
|
||||
mutating func restoreHealth() {
|
||||
health = Player.maxHealth
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-player-share-with-self`
|
||||
|
||||
```swifttest
|
||||
>> func balance(_ x: inout Int, _ y: inout Int) {
|
||||
>> let sum = x + y
|
||||
>> x = sum / 2
|
||||
>> y = sum - x
|
||||
>> }
|
||||
-> struct Player {
|
||||
var name: String
|
||||
var health: Int
|
||||
var energy: Int
|
||||
|
||||
static let maxHealth = 10
|
||||
mutating func restoreHealth() {
|
||||
health = Player.maxHealth
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
In the `restoreHealth()` method above,
|
||||
a write access to `self` starts at the beginning of the method
|
||||
and lasts until the method returns.
|
||||
In this case, there's no other code
|
||||
inside `restoreHealth()`
|
||||
that could have an overlapping access to the properties of a `Player` instance.
|
||||
The `shareHealth(with:)` method below
|
||||
takes another `Player` instance as an in-out parameter,
|
||||
creating the possibility of overlapping accesses.
|
||||
|
||||
```swift
|
||||
extension Player {
|
||||
mutating func shareHealth(with teammate: inout Player) {
|
||||
balance(&teammate.health, &health)
|
||||
}
|
||||
}
|
||||
|
||||
var oscar = Player(name: "Oscar", health: 10, energy: 10)
|
||||
var maria = Player(name: "Maria", health: 5, energy: 10)
|
||||
oscar.shareHealth(with: &maria) // OK
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-player-share-with-self`
|
||||
|
||||
```swifttest
|
||||
-> extension Player {
|
||||
mutating func shareHealth(with teammate: inout Player) {
|
||||
balance(&teammate.health, &health)
|
||||
}
|
||||
}
|
||||
|
||||
-> var oscar = Player(name: "Oscar", health: 10, energy: 10)
|
||||
-> var maria = Player(name: "Maria", health: 5, energy: 10)
|
||||
-> oscar.shareHealth(with: &maria) // OK
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
calling the `shareHealth(with:)` method
|
||||
for Oscar's player to share health with Maria's player
|
||||
doesn't cause a conflict.
|
||||
There's a write access to `oscar` during the method call
|
||||
because `oscar` is the value of `self` in a mutating method,
|
||||
and there's a write access to `maria`
|
||||
for the same duration
|
||||
because `maria` was passed as an in-out parameter.
|
||||
As shown in the figure below,
|
||||
they access different locations in memory.
|
||||
Even though the two write accesses overlap in time,
|
||||
they don't conflict.
|
||||
|
||||

|
||||
|
||||
However,
|
||||
if you pass `oscar` as the argument to `shareHealth(with:)`,
|
||||
there's a conflict:
|
||||
|
||||
```swift
|
||||
oscar.shareHealth(with: &oscar)
|
||||
// Error: Conflicting accesses to oscar.
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-player-share-with-self`
|
||||
|
||||
```swifttest
|
||||
-> oscar.shareHealth(with: &oscar)
|
||||
// Error: Conflicting accesses to oscar.
|
||||
!$ error: inout arguments are not allowed to alias each other
|
||||
!! oscar.shareHealth(with: &oscar)
|
||||
!! ^~~~~~
|
||||
!$ note: previous aliasing argument
|
||||
!! oscar.shareHealth(with: &oscar)
|
||||
!! ^~~~~
|
||||
!$ error: overlapping accesses to 'oscar', but modification requires exclusive access; consider copying to a local variable
|
||||
!! oscar.shareHealth(with: &oscar)
|
||||
!! ^~~~~
|
||||
!$ note: conflicting access is here
|
||||
!! oscar.shareHealth(with: &oscar)
|
||||
!! ^~~~~~
|
||||
```
|
||||
-->
|
||||
|
||||
The mutating method needs write access to `self`
|
||||
for the duration of the method,
|
||||
and the in-out parameter needs write access to `teammate`
|
||||
for the same duration.
|
||||
Within the method,
|
||||
both `self` and `teammate` refer to
|
||||
the same location in memory ---
|
||||
as shown in the figure below.
|
||||
The two write accesses
|
||||
refer to the same memory and they overlap,
|
||||
producing a conflict.
|
||||
|
||||

|
||||
|
||||
## Conflicting Access to Properties
|
||||
|
||||
Types like structures, tuples, and enumerations
|
||||
are made up of individual constituent values,
|
||||
such as the properties of a structure or the elements of a tuple.
|
||||
Because these are value types, mutating any piece of the value
|
||||
mutates the whole value,
|
||||
meaning read or write access to one of the properties
|
||||
requires read or write access to the whole value.
|
||||
For example,
|
||||
overlapping write accesses to the elements of a tuple
|
||||
produces a conflict:
|
||||
|
||||
```swift
|
||||
var playerInformation = (health: 10, energy: 20)
|
||||
balance(&playerInformation.health, &playerInformation.energy)
|
||||
// Error: Conflicting access to properties of playerInformation.
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-tuple`
|
||||
|
||||
```swifttest
|
||||
>> func balance(_ x: inout Int, _ y: inout Int) {
|
||||
>> let sum = x + y
|
||||
>> x = sum / 2
|
||||
>> y = sum - x
|
||||
>> }
|
||||
-> var playerInformation = (health: 10, energy: 20)
|
||||
-> balance(&playerInformation.health, &playerInformation.energy)
|
||||
// Error: Conflicting access to properties of playerInformation.
|
||||
xx Simultaneous accesses to 0x10794d848, but modification requires exclusive access.
|
||||
xx Previous access (a modification) started at (0x107952037).
|
||||
xx Current access (a modification) started at:
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
calling `balance(_:_:)` on the elements of a tuple
|
||||
produces a conflict
|
||||
because there are overlapping write accesses to `playerInformation`.
|
||||
Both `playerInformation.health` and `playerInformation.energy`
|
||||
are passed as in-out parameters,
|
||||
which means `balance(_:_:)` needs write access to them
|
||||
for the duration of the function call.
|
||||
In both cases, a write access to the tuple element
|
||||
requires a write access to the entire tuple.
|
||||
This means there are two write accesses to `playerInformation`
|
||||
with durations that overlap,
|
||||
causing a conflict.
|
||||
|
||||
The code below shows that the same error appears
|
||||
for overlapping write accesses
|
||||
to the properties of a structure
|
||||
that's stored in a global variable.
|
||||
|
||||
```swift
|
||||
var holly = Player(name: "Holly", health: 10, energy: 10)
|
||||
balance(&holly.health, &holly.energy) // Error
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-share-health-global`
|
||||
|
||||
```swifttest
|
||||
>> struct Player {
|
||||
>> var name: String
|
||||
>> var health: Int
|
||||
>> var energy: Int
|
||||
>> }
|
||||
>> func balance(_ x: inout Int, _ y: inout Int) {
|
||||
>> let sum = x + y
|
||||
>> x = sum / 2
|
||||
>> y = sum - x
|
||||
>> }
|
||||
-> var holly = Player(name: "Holly", health: 10, energy: 10)
|
||||
-> balance(&holly.health, &holly.energy) // Error
|
||||
xx Simultaneous accesses to 0x10794d848, but modification requires exclusive access.
|
||||
xx Previous access (a modification) started at (0x107952037).
|
||||
xx Current access (a modification) started at:
|
||||
```
|
||||
-->
|
||||
|
||||
In practice,
|
||||
most access to the properties of a structure
|
||||
can overlap safely.
|
||||
For example,
|
||||
if the variable `holly` in the example above
|
||||
is changed to a local variable instead of a global variable,
|
||||
the compiler can prove that overlapping access
|
||||
to stored properties of the structure is safe:
|
||||
|
||||
```swift
|
||||
func someFunction() {
|
||||
var oscar = Player(name: "Oscar", health: 10, energy: 10)
|
||||
balance(&oscar.health, &oscar.energy) // OK
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `memory-share-health-local`
|
||||
|
||||
```swifttest
|
||||
>> struct Player {
|
||||
>> var name: String
|
||||
>> var health: Int
|
||||
>> var energy: Int
|
||||
>> }
|
||||
>> func balance(_ x: inout Int, _ y: inout Int) {
|
||||
>> let sum = x + y
|
||||
>> x = sum / 2
|
||||
>> y = sum - x
|
||||
>> }
|
||||
-> func someFunction() {
|
||||
var oscar = Player(name: "Oscar", health: 10, energy: 10)
|
||||
balance(&oscar.health, &oscar.energy) // OK
|
||||
}
|
||||
>> someFunction()
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
Oscar's health and energy are passed
|
||||
as the two in-out parameters to `balance(_:_:)`.
|
||||
The compiler can prove that memory safety is preserved
|
||||
because the two stored properties don't interact in any way.
|
||||
|
||||
The restriction against
|
||||
overlapping access to properties of a structure
|
||||
isn't always necessary to preserve memory safety.
|
||||
Memory safety is the desired guarantee,
|
||||
but exclusive access is a stricter requirement than memory safety ---
|
||||
which means some code preserves memory safety,
|
||||
even though it violates exclusive access to memory.
|
||||
Swift allows this memory-safe code if the compiler can prove
|
||||
that the nonexclusive access to memory is still safe.
|
||||
Specifically, it can prove
|
||||
that overlapping access to properties of a structure is safe
|
||||
if the following conditions apply:
|
||||
|
||||
- You're accessing only stored properties of an instance,
|
||||
not computed properties or class properties.
|
||||
- The structure is the value of a local variable,
|
||||
not a global variable.
|
||||
- The structure is either not captured by any closures,
|
||||
or it's captured only by nonescaping closures.
|
||||
|
||||
If the compiler can't prove the access is safe,
|
||||
it doesn't allow the access.
|
||||
|
||||
<!--
|
||||
Because there's no syntax
|
||||
to mutate an enum's associated value in place,
|
||||
we can't show that overlapping mutations
|
||||
to two different associated values on the same enum
|
||||
would violate exclusivity.
|
||||
Otherwise, we'd want an example of that
|
||||
in this section too --
|
||||
it's the moral equivalent of property access.
|
||||
-->
|
||||
|
||||
<!--
|
||||
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
|
||||
-->
|
||||
|
||||
<!--
|
||||
In Swift 4, the only way to create a long-term read
|
||||
is to use implicit pointer conversion
|
||||
when passing a value as a nonmutating unsafe pointer parameter,
|
||||
as in the example below.
|
||||
There's discussion in <rdar://problem/33115142>
|
||||
about changing the semantics of nonmutating method calls
|
||||
to be long-term reads,
|
||||
but it's not clear if/when that change will land.
|
||||
|
||||
::
|
||||
|
||||
var global = 4
|
||||
|
||||
func foo(_ x: UnsafePointer<Int>){
|
||||
global = 7
|
||||
}
|
||||
|
||||
foo(&global)
|
||||
print(global)
|
||||
|
||||
// Simultaneous accesses to 0x106761618, but modification requires exclusive access.
|
||||
// Previous access (a read) started at temp2`main + 87 (0x10675e417).
|
||||
// Current access (a modification) started at:
|
||||
// 0 libswiftCore.dylib 0x0000000106ac7b90 swift_beginAccess + 605
|
||||
// 1 temp2 0x000000010675e500 foo(_:) + 39
|
||||
// 2 temp2 0x000000010675e3c0 main + 102
|
||||
// 3 libdyld.dylib 0x00007fff69c75144 start + 1
|
||||
// Fatal access conflict detected.
|
||||
-->
|
||||
|
||||
<!--
|
||||
TEXT FOR THE FUTURE
|
||||
|
||||
Versions of Swift before Swift 5 ensure memory safety
|
||||
by aggressively making a copy of the shared mutable state
|
||||
when a conflicting access is possible.
|
||||
The copy is no longer shared, preventing the possibility of conflicts.
|
||||
However, the copying approach has a negative impact
|
||||
on performance and memory usage.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
661
skills/programming-swift/LanguageGuide/Methods.md
Normal file
661
skills/programming-swift/LanguageGuide/Methods.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# Methods
|
||||
|
||||
Define and call functions that are part of an instance or type.
|
||||
|
||||
*Methods* are functions that are associated with a particular type.
|
||||
Classes, structures, and enumerations can all define instance methods,
|
||||
which encapsulate specific tasks and functionality for working with an instance of a given type.
|
||||
Classes, structures, and enumerations can also define type methods,
|
||||
which are associated with the type itself.
|
||||
Type methods are similar to class methods in Objective-C.
|
||||
|
||||
The fact that structures and enumerations can define methods in Swift
|
||||
is a major difference from C and Objective-C.
|
||||
In Objective-C, classes are the only types that can define methods.
|
||||
In Swift, you can choose whether to define a class, structure, or enumeration,
|
||||
and still have the flexibility to define methods on the type you create.
|
||||
|
||||
## Instance Methods
|
||||
|
||||
*Instance methods* are functions that belong to instances of
|
||||
a particular class, structure, or enumeration.
|
||||
They support the functionality of those instances,
|
||||
either by providing ways to access and modify instance properties,
|
||||
or by providing functionality related to the instance's purpose.
|
||||
Instance methods have exactly the same syntax as functions,
|
||||
as described in <doc:Functions>.
|
||||
|
||||
You write an instance method within the opening and closing braces of the type it belongs to.
|
||||
An instance method has implicit access to all other instance methods and properties of that type.
|
||||
An instance method can be called only on a specific instance of the type it belongs to.
|
||||
It can't be called in isolation without an existing instance.
|
||||
|
||||
Here's an example that defines a simple `Counter` class,
|
||||
which can be used to count the number of times an action occurs:
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
func increment() {
|
||||
count += 1
|
||||
}
|
||||
func increment(by amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `instanceMethods`
|
||||
|
||||
```swifttest
|
||||
-> class Counter {
|
||||
var count = 0
|
||||
func increment() {
|
||||
count += 1
|
||||
}
|
||||
func increment(by amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `Counter` class defines three instance methods:
|
||||
|
||||
- `increment()` increments the counter by `1`.
|
||||
- `increment(by: Int)` increments the counter by a specified integer amount.
|
||||
- `reset()` resets the counter to zero.
|
||||
|
||||
The `Counter` class also declares a variable property, `count`,
|
||||
to keep track of the current counter value.
|
||||
|
||||
You call instance methods with the same dot syntax as properties:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
// the initial counter value is 0
|
||||
counter.increment()
|
||||
// the counter's value is now 1
|
||||
counter.increment(by: 5)
|
||||
// the counter's value is now 6
|
||||
counter.reset()
|
||||
// the counter's value is now 0
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `instanceMethods`
|
||||
|
||||
```swifttest
|
||||
-> let counter = Counter()
|
||||
/> the initial counter value is \(counter.count)
|
||||
</ the initial counter value is 0
|
||||
-> counter.increment()
|
||||
/> the counter's value is now \(counter.count)
|
||||
</ the counter's value is now 1
|
||||
-> counter.increment(by: 5)
|
||||
/> the counter's value is now \(counter.count)
|
||||
</ the counter's value is now 6
|
||||
-> counter.reset()
|
||||
/> the counter's value is now \(counter.count)
|
||||
</ the counter's value is now 0
|
||||
```
|
||||
-->
|
||||
|
||||
Function parameters can have both a name (for use within the function's body)
|
||||
and an argument label (for use when calling the function),
|
||||
as described in <doc:Functions#Function-Argument-Labels-and-Parameter-Names>.
|
||||
The same is true for method parameters,
|
||||
because methods are just functions that are associated with a type.
|
||||
|
||||
### The self Property
|
||||
|
||||
Every instance of a type has an implicit property called `self`,
|
||||
which is exactly equivalent to the instance itself.
|
||||
You use the `self` property to refer to the current instance
|
||||
within its own instance methods.
|
||||
|
||||
The `increment()` method in the example above could have been written like this:
|
||||
|
||||
```swift
|
||||
func increment() {
|
||||
self.count += 1
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `instanceMethodsIncrement`
|
||||
|
||||
```swifttest
|
||||
>> class Counter {
|
||||
>> var count: Int = 0
|
||||
func increment() {
|
||||
self.count += 1
|
||||
}
|
||||
>> }
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
NOTE: I'm slightly cheating with my testing of this excerpt, but it works!
|
||||
-->
|
||||
|
||||
In practice, you don't need to write `self` in your code very often.
|
||||
If you don't explicitly write `self`,
|
||||
Swift assumes that you are referring to a property or method of the current instance
|
||||
whenever you use a known property or method name within a method.
|
||||
This assumption is demonstrated by the use of `count` (rather than `self.count`)
|
||||
inside the three instance methods for `Counter`.
|
||||
|
||||
The main exception to this rule occurs when a parameter name for an instance method
|
||||
has the same name as a property of that instance.
|
||||
In this situation, the parameter name takes precedence,
|
||||
and it becomes necessary to refer to the property in a more qualified way.
|
||||
You use the `self` property to
|
||||
distinguish between the parameter name and the property name.
|
||||
|
||||
Here, `self` disambiguates between
|
||||
a method parameter called `x` and an instance property that's also called `x`:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOf(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
}
|
||||
let somePoint = Point(x: 4.0, y: 5.0)
|
||||
if somePoint.isToTheRightOf(x: 1.0) {
|
||||
print("This point is to the right of the line where x == 1.0")
|
||||
}
|
||||
// Prints "This point is to the right of the line where x == 1.0".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `self`
|
||||
|
||||
```swifttest
|
||||
-> struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOf(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
}
|
||||
-> let somePoint = Point(x: 4.0, y: 5.0)
|
||||
-> if somePoint.isToTheRightOf(x: 1.0) {
|
||||
print("This point is to the right of the line where x == 1.0")
|
||||
}
|
||||
<- This point is to the right of the line where x == 1.0
|
||||
```
|
||||
-->
|
||||
|
||||
Without the `self` prefix,
|
||||
Swift would assume that both uses of `x` referred to the method parameter called `x`.
|
||||
|
||||
### Modifying Value Types from Within Instance Methods
|
||||
|
||||
Structures and enumerations are *value types*.
|
||||
By default, the properties of a value type can't be modified from within its instance methods.
|
||||
|
||||
<!--
|
||||
TODO: find out why. once I actually know why, explain it.
|
||||
-->
|
||||
|
||||
However, if you need to modify the properties of your structure or enumeration
|
||||
within a particular method,
|
||||
you can opt in to *mutating* behavior for that method.
|
||||
The method can then mutate (that is, change)
|
||||
its properties from within the method,
|
||||
and any changes that it makes are written back to the original structure when the method ends.
|
||||
The method can also assign a completely new instance to its implicit `self` property,
|
||||
and this new instance will replace the existing one when the method ends.
|
||||
|
||||
You can opt in to this behavior by placing the `mutating` keyword
|
||||
before the `func` keyword for that method:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
}
|
||||
var somePoint = Point(x: 1.0, y: 1.0)
|
||||
somePoint.moveBy(x: 2.0, y: 3.0)
|
||||
print("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
// Prints "The point is now at (3.0, 4.0)".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `selfStructures`
|
||||
|
||||
```swifttest
|
||||
-> struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
}
|
||||
-> var somePoint = Point(x: 1.0, y: 1.0)
|
||||
-> somePoint.moveBy(x: 2.0, y: 3.0)
|
||||
-> print("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
<- The point is now at (3.0, 4.0)
|
||||
```
|
||||
-->
|
||||
|
||||
The `Point` structure above defines a mutating `moveBy(x:y:)` method,
|
||||
which moves a `Point` instance by a certain amount.
|
||||
Instead of returning a new point,
|
||||
this method actually modifies the point on which it's called.
|
||||
The `mutating` keyword is added to its definition
|
||||
to enable it to modify its properties.
|
||||
|
||||
Note that you can't call a mutating method on a constant of structure type,
|
||||
because its properties can't be changed, even if they're variable properties,
|
||||
as described in <doc:Properties#Stored-Properties-of-Constant-Structure-Instances>:
|
||||
|
||||
```swift
|
||||
let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
fixedPoint.moveBy(x: 2.0, y: 3.0)
|
||||
// this will report an error
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `selfStructures-err`
|
||||
|
||||
```swifttest
|
||||
>> struct Point {
|
||||
>> var x = 0.0, y = 0.0
|
||||
>> mutating func moveBy(x deltaX: Double, y deltaY: Double) {
|
||||
>> x += deltaX
|
||||
>> y += deltaY
|
||||
>> }
|
||||
>> }
|
||||
-> let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
-> fixedPoint.moveBy(x: 2.0, y: 3.0)
|
||||
!$ error: cannot use mutating member on immutable value: 'fixedPoint' is a 'let' constant
|
||||
!! fixedPoint.moveBy(x: 2.0, y: 3.0)
|
||||
!! ~~~~~~~~~~ ^
|
||||
!$ note: change 'let' to 'var' to make it mutable
|
||||
!! let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
!! ^~~
|
||||
!! var
|
||||
// this will report an error
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: talk about nonmutating as well.
|
||||
Struct setters are implicitly 'mutating' by default and thus don't work on 'let's.
|
||||
However, JoeG says that this ought to work
|
||||
if the setter for the computed property is explicitly defined as @!mutating.
|
||||
-->
|
||||
|
||||
### Assigning to self Within a Mutating Method
|
||||
|
||||
Mutating methods can assign an entirely new instance to the implicit `self` property.
|
||||
The `Point` example shown above could have been written in the following way instead:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `selfStructuresAssign`
|
||||
|
||||
```swifttest
|
||||
-> struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
>> var somePoint = Point(x: 1.0, y: 1.0)
|
||||
>> somePoint.moveBy(x: 2.0, y: 3.0)
|
||||
>> print("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
<< The point is now at (3.0, 4.0)
|
||||
```
|
||||
-->
|
||||
|
||||
This version of the mutating `moveBy(x:y:)` method creates a new structure
|
||||
whose `x` and `y` values are set to the target location.
|
||||
The end result of calling this alternative version of the method
|
||||
will be exactly the same as for calling the earlier version.
|
||||
|
||||
Mutating methods for enumerations can set the implicit `self` parameter to be
|
||||
a different case from the same enumeration:
|
||||
|
||||
```swift
|
||||
enum TriStateSwitch {
|
||||
case off, low, high
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case .off:
|
||||
self = .low
|
||||
case .low:
|
||||
self = .high
|
||||
case .high:
|
||||
self = .off
|
||||
}
|
||||
}
|
||||
}
|
||||
var ovenLight = TriStateSwitch.low
|
||||
ovenLight.next()
|
||||
// ovenLight is now equal to .high
|
||||
ovenLight.next()
|
||||
// ovenLight is now equal to .off
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `selfEnumerations`
|
||||
|
||||
```swifttest
|
||||
-> enum TriStateSwitch {
|
||||
case off, low, high
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case .off:
|
||||
self = .low
|
||||
case .low:
|
||||
self = .high
|
||||
case .high:
|
||||
self = .off
|
||||
}
|
||||
}
|
||||
}
|
||||
-> var ovenLight = TriStateSwitch.low
|
||||
-> ovenLight.next()
|
||||
// ovenLight is now equal to .high
|
||||
-> ovenLight.next()
|
||||
// ovenLight is now equal to .off
|
||||
```
|
||||
-->
|
||||
|
||||
This example defines an enumeration for a three-state switch.
|
||||
The switch cycles between three different power states
|
||||
(`off`, `low` and `high`)
|
||||
every time its `next()` method is called.
|
||||
|
||||
## Type Methods
|
||||
|
||||
Instance methods, as described above,
|
||||
are methods that you call on an instance of a particular type.
|
||||
You can also define methods that are called on the type itself.
|
||||
These kinds of methods are called *type methods*.
|
||||
You indicate type methods by writing
|
||||
the `static` keyword before the method's `func` keyword.
|
||||
Classes can use the `class` keyword instead,
|
||||
to allow subclasses to override the superclass’s implementation of that method.
|
||||
|
||||
> Note: In Objective-C, you can define type-level methods only for Objective-C classes.
|
||||
> In Swift, you can define type-level methods for all classes, structures, and enumerations.
|
||||
> Each type method is explicitly scoped to the type it supports.
|
||||
|
||||
Type methods are called with dot syntax, like instance methods.
|
||||
However, you call type methods on the type, not on an instance of that type.
|
||||
Here's how you call a type method on a class called `SomeClass`:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
class func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
}
|
||||
SomeClass.someTypeMethod()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeMethods`
|
||||
|
||||
```swifttest
|
||||
-> class SomeClass {
|
||||
class func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
}
|
||||
-> SomeClass.someTypeMethod()
|
||||
```
|
||||
-->
|
||||
|
||||
Within the body of a type method,
|
||||
the implicit `self` property refers to the type itself,
|
||||
rather than an instance of that type.
|
||||
This means that you can use `self` to disambiguate between
|
||||
type properties and type method parameters,
|
||||
just as you do for instance properties and instance method parameters.
|
||||
|
||||
More generally, any unqualified method and property names that you use
|
||||
within the body of a type method will refer to other type-level methods and properties.
|
||||
A type method can call another type method with the other method's name,
|
||||
without needing to prefix it with the type name.
|
||||
Similarly, type methods on structures and enumerations can access type properties
|
||||
by using the type property's name without a type name prefix.
|
||||
|
||||
The example below defines a structure called `LevelTracker`,
|
||||
which tracks a player's progress through the different levels or stages of a game.
|
||||
It's a single-player game,
|
||||
but can store information for multiple players on a single device.
|
||||
|
||||
All of the game's levels (apart from level one) are locked when the game is first played.
|
||||
Every time a player finishes a level,
|
||||
that level is unlocked for all players on the device.
|
||||
The `LevelTracker` structure uses type properties and methods
|
||||
to keep track of which levels of the game have been unlocked.
|
||||
It also tracks the current level for an individual player.
|
||||
|
||||
```swift
|
||||
struct LevelTracker {
|
||||
static var highestUnlockedLevel = 1
|
||||
var currentLevel = 1
|
||||
|
||||
static func unlock(_ level: Int) {
|
||||
if level > highestUnlockedLevel { highestUnlockedLevel = level }
|
||||
}
|
||||
|
||||
static func isUnlocked(_ level: Int) -> Bool {
|
||||
return level <= highestUnlockedLevel
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func advance(to level: Int) -> Bool {
|
||||
if LevelTracker.isUnlocked(level) {
|
||||
currentLevel = level
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeMethods`
|
||||
|
||||
```swifttest
|
||||
-> struct LevelTracker {
|
||||
static var highestUnlockedLevel = 1
|
||||
var currentLevel = 1
|
||||
|
||||
-> static func unlock(_ level: Int) {
|
||||
if level > highestUnlockedLevel { highestUnlockedLevel = level }
|
||||
}
|
||||
|
||||
-> static func isUnlocked(_ level: Int) -> Bool {
|
||||
return level <= highestUnlockedLevel
|
||||
}
|
||||
|
||||
-> @discardableResult
|
||||
mutating func advance(to level: Int) -> Bool {
|
||||
if LevelTracker.isUnlocked(level) {
|
||||
currentLevel = level
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `LevelTracker` structure keeps track of the highest level that any player has unlocked.
|
||||
This value is stored in a type property called `highestUnlockedLevel`.
|
||||
|
||||
`LevelTracker` also defines two type functions to work with
|
||||
the `highestUnlockedLevel` property.
|
||||
The first is a type function called `unlock(_:)`,
|
||||
which updates the value of `highestUnlockedLevel` whenever a new level is unlocked.
|
||||
The second is a convenience type function called `isUnlocked(_:)`,
|
||||
which returns `true` if a particular level number is already unlocked.
|
||||
(Note that these type methods can access the `highestUnlockedLevel` type property
|
||||
without your needing to write it as `LevelTracker.highestUnlockedLevel`.)
|
||||
|
||||
In addition to its type property and type methods,
|
||||
`LevelTracker` tracks an individual player's progress through the game.
|
||||
It uses an instance property called `currentLevel` to track
|
||||
the level that a player is currently playing.
|
||||
|
||||
To help manage the `currentLevel` property,
|
||||
`LevelTracker` defines an instance method called `advance(to:)`.
|
||||
Before updating `currentLevel`,
|
||||
this method checks whether the requested new level is already unlocked.
|
||||
The `advance(to:)` method returns a Boolean value to indicate
|
||||
whether or not it was actually able to set `currentLevel`.
|
||||
Because it's not necessarily a mistake for
|
||||
code that calls the `advance(to:)` method
|
||||
to ignore the return value,
|
||||
this function is marked with the `@discardableResult` attribute.
|
||||
For more information about this attribute,
|
||||
see <doc:Attributes>.
|
||||
|
||||
The `LevelTracker` structure is used with the `Player` class, shown below,
|
||||
to track and update the progress of an individual player:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func complete(level: Int) {
|
||||
LevelTracker.unlock(level + 1)
|
||||
tracker.advance(to: level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeMethods`
|
||||
|
||||
```swifttest
|
||||
-> class Player {
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func complete(level: Int) {
|
||||
LevelTracker.unlock(level + 1)
|
||||
tracker.advance(to: level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `Player` class creates a new instance of `LevelTracker`
|
||||
to track that player's progress.
|
||||
It also provides a method called `complete(level:)`,
|
||||
which is called whenever a player completes a particular level.
|
||||
This method unlocks the next level for all players
|
||||
and updates the player's progress to move them to the next level.
|
||||
(The Boolean return value of `advance(to:)` is ignored,
|
||||
because the level is known to have been unlocked
|
||||
by the call to `LevelTracker.unlock(_:)` on the previous line.)
|
||||
|
||||
You can create an instance of the `Player` class for a new player,
|
||||
and see what happens when the player completes level one:
|
||||
|
||||
```swift
|
||||
var player = Player(name: "Argyrios")
|
||||
player.complete(level: 1)
|
||||
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
|
||||
// Prints "highest unlocked level is now 2".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeMethods`
|
||||
|
||||
```swifttest
|
||||
-> var player = Player(name: "Argyrios")
|
||||
-> player.complete(level: 1)
|
||||
-> print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
|
||||
<- highest unlocked level is now 2
|
||||
```
|
||||
-->
|
||||
|
||||
If you create a second player, whom you try to move to a level
|
||||
that's not yet unlocked by any player in the game,
|
||||
the attempt to set the player's current level fails:
|
||||
|
||||
```swift
|
||||
player = Player(name: "Beto")
|
||||
if player.tracker.advance(to: 6) {
|
||||
print("player is now on level 6")
|
||||
} else {
|
||||
print("level 6 hasn't yet been unlocked")
|
||||
}
|
||||
// Prints "level 6 hasn't yet been unlocked".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeMethods`
|
||||
|
||||
```swifttest
|
||||
-> player = Player(name: "Beto")
|
||||
-> if player.tracker.advance(to: 6) {
|
||||
print("player is now on level 6")
|
||||
} else {
|
||||
print("level 6 hasn't yet been unlocked")
|
||||
}
|
||||
<- level 6 hasn't yet been unlocked
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Method Binding
|
||||
--------------------
|
||||
|
||||
TODO: you can get a function that refers to a method, either with or without the 'self' argument already being bound:
|
||||
class C {
|
||||
func foo(x: Int) -> Float { ... }
|
||||
}
|
||||
var c = C()
|
||||
var boundFunc = c.foo // a function with type (Int) -> Float
|
||||
var unboundFunc = C.foo // a function with type (C) -> (Int) -> Float
|
||||
|
||||
TODO: selector-style methods can be referenced as foo.bar:bas:
|
||||
(see Doug's comments from the 2014-03-12 release notes)
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
203
skills/programming-swift/LanguageGuide/NestedTypes.md
Normal file
203
skills/programming-swift/LanguageGuide/NestedTypes.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Nested Types
|
||||
|
||||
Define types inside the scope of another type.
|
||||
|
||||
Enumerations are often created to support a specific class or structure's functionality.
|
||||
Similarly, it can be convenient to define utility structures
|
||||
purely for use within the context of a more complex type,
|
||||
and protocols that are normally used in conjunction with a specific type.
|
||||
To accomplish this, Swift enables you to define *nested types*,
|
||||
whereby you nest supporting types like enumerations, structures, and protocols
|
||||
within the definition of the type they support.
|
||||
|
||||
To nest a type within another type,
|
||||
write its definition within the outer braces of the type it supports.
|
||||
Types can be nested to as many levels as are required.
|
||||
|
||||
## Nested Types in Action
|
||||
|
||||
The example below defines a structure called `BlackjackCard`,
|
||||
which models a playing card as used in the game of Blackjack.
|
||||
The `BlackjackCard` structure contains two nested enumeration types
|
||||
called `Suit` and `Rank`.
|
||||
|
||||
In Blackjack, the Ace cards have a value of either one or eleven.
|
||||
This feature is represented by a structure called `Values`,
|
||||
which is nested within the `Rank` enumeration:
|
||||
|
||||
```swift
|
||||
struct BlackjackCard {
|
||||
|
||||
// nested Suit enumeration
|
||||
enum Suit: Character {
|
||||
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
|
||||
}
|
||||
|
||||
// nested Rank enumeration
|
||||
enum Rank: Int {
|
||||
case two = 2, three, four, five, six, seven, eight, nine, ten
|
||||
case jack, queen, king, ace
|
||||
struct Values {
|
||||
let first: Int, second: Int?
|
||||
}
|
||||
var values: Values {
|
||||
switch self {
|
||||
case .ace:
|
||||
return Values(first: 1, second: 11)
|
||||
case .jack, .queen, .king:
|
||||
return Values(first: 10, second: nil)
|
||||
default:
|
||||
return Values(first: self.rawValue, second: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlackjackCard properties and methods
|
||||
let rank: Rank, suit: Suit
|
||||
var description: String {
|
||||
var output = "suit is \(suit.rawValue),"
|
||||
output += " value is \(rank.values.first)"
|
||||
if let second = rank.values.second {
|
||||
output += " or \(second)"
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `nestedTypes`
|
||||
|
||||
```swifttest
|
||||
-> struct BlackjackCard {
|
||||
|
||||
// nested Suit enumeration
|
||||
enum Suit: Character {
|
||||
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
|
||||
}
|
||||
|
||||
// nested Rank enumeration
|
||||
enum Rank: Int {
|
||||
case two = 2, three, four, five, six, seven, eight, nine, ten
|
||||
case jack, queen, king, ace
|
||||
struct Values {
|
||||
let first: Int, second: Int?
|
||||
}
|
||||
var values: Values {
|
||||
switch self {
|
||||
case .ace:
|
||||
return Values(first: 1, second: 11)
|
||||
case .jack, .queen, .king:
|
||||
return Values(first: 10, second: nil)
|
||||
default:
|
||||
return Values(first: self.rawValue, second: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlackjackCard properties and methods
|
||||
let rank: Rank, suit: Suit
|
||||
var description: String {
|
||||
var output = "suit is \(suit.rawValue),"
|
||||
output += " value is \(rank.values.first)"
|
||||
if let second = rank.values.second {
|
||||
output += " or \(second)"
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `Suit` enumeration describes the four common playing card suits,
|
||||
together with a raw `Character` value to represent their symbol.
|
||||
|
||||
The `Rank` enumeration describes the thirteen possible playing card ranks,
|
||||
together with a raw `Int` value to represent their face value.
|
||||
(This raw `Int` value isn't used for the Jack, Queen, King, and Ace cards.)
|
||||
|
||||
As mentioned above, the `Rank` enumeration defines
|
||||
a further nested structure of its own, called `Values`.
|
||||
This structure encapsulates the fact that most cards have one value,
|
||||
but the Ace card has two values.
|
||||
The `Values` structure defines two properties to represent this:
|
||||
|
||||
- `first`, of type `Int`
|
||||
- `second`, of type `Int?`, or “optional `Int`”
|
||||
|
||||
`Rank` also defines a computed property, `values`,
|
||||
which returns an instance of the `Values` structure.
|
||||
This computed property considers the rank of the card
|
||||
and initializes a new `Values` instance with appropriate values based on its rank.
|
||||
It uses special values for `jack`, `queen`, `king`, and `ace`.
|
||||
For the numeric cards, it uses the rank's raw `Int` value.
|
||||
|
||||
The `BlackjackCard` structure itself has two properties --- `rank` and `suit`.
|
||||
It also defines a computed property called `description`,
|
||||
which uses the values stored in `rank` and `suit` to build
|
||||
a description of the name and value of the card.
|
||||
The `description` property uses optional binding to check whether there's
|
||||
a second value to display, and if so,
|
||||
inserts additional description detail for that second value.
|
||||
|
||||
Because `BlackjackCard` is a structure with no custom initializers,
|
||||
it has an implicit memberwise initializer,
|
||||
as described in <doc:Initialization#Memberwise-Initializers-for-Structure-Types>.
|
||||
You can use this initializer to initialize a new constant called `theAceOfSpades`:
|
||||
|
||||
```swift
|
||||
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
|
||||
print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
// Prints "theAceOfSpades: suit is ♠, value is 1 or 11".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `nestedTypes`
|
||||
|
||||
```swifttest
|
||||
-> let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
|
||||
-> print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
<- theAceOfSpades: suit is ♠, value is 1 or 11
|
||||
```
|
||||
-->
|
||||
|
||||
Even though `Rank` and `Suit` are nested within `BlackjackCard`,
|
||||
their type can be inferred from context,
|
||||
and so the initialization of this instance is able to refer to the enumeration cases
|
||||
by their case names (`.ace` and `.spades`) alone.
|
||||
In the example above, the `description` property correctly reports that
|
||||
the Ace of Spades has a value of `1` or `11`.
|
||||
|
||||
## Referring to Nested Types
|
||||
|
||||
To use a nested type outside of its definition context,
|
||||
prefix its name with the name of the type it's nested within:
|
||||
|
||||
```swift
|
||||
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
|
||||
// heartsSymbol is "♡"
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `nestedTypes`
|
||||
|
||||
```swifttest
|
||||
-> let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
|
||||
/> heartsSymbol is \"\(heartsSymbol)\"
|
||||
</ heartsSymbol is "♡"
|
||||
```
|
||||
-->
|
||||
|
||||
For the example above,
|
||||
this enables the names of `Suit`, `Rank`, and `Values` to be kept deliberately short,
|
||||
because their names are naturally qualified by the context in which they're defined.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
993
skills/programming-swift/LanguageGuide/OpaqueTypes.md
Normal file
993
skills/programming-swift/LanguageGuide/OpaqueTypes.md
Normal file
@@ -0,0 +1,993 @@
|
||||
# Opaque and Boxed Protocol Types
|
||||
|
||||
Hide implementation details about a value's type.
|
||||
|
||||
Swift provides two ways to hide details about a value's type:
|
||||
opaque types and boxed protocol types.
|
||||
Hiding type information
|
||||
is useful at boundaries between
|
||||
a module and code that calls into the module,
|
||||
because the underlying type of the return value can remain private.
|
||||
|
||||
A function or method that returns an opaque type
|
||||
hides its return value's type information.
|
||||
Instead of providing a concrete type as the function's return type,
|
||||
the return value is described in terms of the protocols it supports.
|
||||
Opaque types preserve type identity ---
|
||||
the compiler has access to the type information,
|
||||
but clients of the module don't.
|
||||
|
||||
A boxed protocol type can store an instance of any type
|
||||
that conforms to the given protocol.
|
||||
Boxed protocol types don't preserve type identity ---
|
||||
the value's specific type isn't known until runtime,
|
||||
and it can change over time as different values are stored.
|
||||
|
||||
## The Problem that Opaque Types Solve
|
||||
|
||||
For example,
|
||||
suppose you're writing a module that draws ASCII art shapes.
|
||||
The basic characteristic of an ASCII art shape
|
||||
is a `draw()` function that returns the string representation of that shape,
|
||||
which you can use as the requirement for the `Shape` protocol:
|
||||
|
||||
```swift
|
||||
protocol Shape {
|
||||
func draw() -> String
|
||||
}
|
||||
|
||||
struct Triangle: Shape {
|
||||
var size: Int
|
||||
func draw() -> String {
|
||||
var result: [String] = []
|
||||
for length in 1...size {
|
||||
result.append(String(repeating: "*", count: length))
|
||||
}
|
||||
return result.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
let smallTriangle = Triangle(size: 3)
|
||||
print(smallTriangle.draw())
|
||||
// *
|
||||
// **
|
||||
// ***
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> protocol Shape {
|
||||
func draw() -> String
|
||||
}
|
||||
|
||||
-> struct Triangle: Shape {
|
||||
var size: Int
|
||||
func draw() -> String {
|
||||
var result: [String] = []
|
||||
for length in 1...size {
|
||||
result.append(String(repeating: "*", count: length))
|
||||
}
|
||||
return result.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
-> let smallTriangle = Triangle(size: 3)
|
||||
-> print(smallTriangle.draw())
|
||||
</ *
|
||||
</ **
|
||||
</ ***
|
||||
```
|
||||
-->
|
||||
|
||||
You could use generics to implement operations like flipping a shape vertically,
|
||||
as shown in the code below.
|
||||
However, there's an important limitation to this approach:
|
||||
The flipped result exposes the exact generic types
|
||||
that were used to create it.
|
||||
|
||||
```swift
|
||||
struct FlippedShape<T: Shape>: Shape {
|
||||
var shape: T
|
||||
func draw() -> String {
|
||||
let lines = shape.draw().split(separator: "\n")
|
||||
return lines.reversed().joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
let flippedTriangle = FlippedShape(shape: smallTriangle)
|
||||
print(flippedTriangle.draw())
|
||||
// ***
|
||||
// **
|
||||
// *
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> struct FlippedShape<T: Shape>: Shape {
|
||||
var shape: T
|
||||
func draw() -> String {
|
||||
let lines = shape.draw().split(separator: "\n")
|
||||
return lines.reversed().joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
-> let flippedTriangle = FlippedShape(shape: smallTriangle)
|
||||
-> print(flippedTriangle.draw())
|
||||
</ ***
|
||||
</ **
|
||||
</ *
|
||||
```
|
||||
-->
|
||||
|
||||
This approach to defining a `JoinedShape<T: Shape, U: Shape>` structure
|
||||
that joins two shapes together vertically, like the code below shows,
|
||||
results in types like `JoinedShape<Triangle, FlippedShape<Triangle>>`
|
||||
from joining a triangle with a flipped triangle.
|
||||
|
||||
```swift
|
||||
struct JoinedShape<T: Shape, U: Shape>: Shape {
|
||||
var top: T
|
||||
var bottom: U
|
||||
func draw() -> String {
|
||||
return top.draw() + "\n" + bottom.draw()
|
||||
}
|
||||
}
|
||||
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
|
||||
print(joinedTriangles.draw())
|
||||
// *
|
||||
// **
|
||||
// ***
|
||||
// ***
|
||||
// **
|
||||
// *
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> struct JoinedShape<T: Shape, U: Shape>: Shape {
|
||||
var top: T
|
||||
var bottom: U
|
||||
func draw() -> String {
|
||||
return top.draw() + "\n" + bottom.draw()
|
||||
}
|
||||
}
|
||||
-> let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
|
||||
-> print(joinedTriangles.draw())
|
||||
</ *
|
||||
</ **
|
||||
</ ***
|
||||
</ ***
|
||||
</ **
|
||||
</ *
|
||||
```
|
||||
-->
|
||||
|
||||
Exposing detailed information about the creation of a shape
|
||||
allows types that aren't meant to be
|
||||
part of the ASCII art module's public interface
|
||||
to leak out because of the need to state the full return type.
|
||||
The code inside the module
|
||||
could build up the same shape in a variety of ways,
|
||||
and other code outside the module
|
||||
that uses the shape shouldn't have to account for
|
||||
the implementation details about the list of transformations.
|
||||
Wrapper types like `JoinedShape` and `FlippedShape`
|
||||
don't matter to the module's users,
|
||||
and they shouldn't be visible.
|
||||
The module's public interface
|
||||
consists of operations like joining and flipping a shape,
|
||||
and those operations return another `Shape` value.
|
||||
|
||||
## Returning an Opaque Type
|
||||
|
||||
You can think of an opaque type like being the reverse of a generic type.
|
||||
Generic types let the code that calls a function
|
||||
pick the type for that function's parameters and return value
|
||||
in a way that's abstracted away from the function implementation.
|
||||
For example, the function in the following code
|
||||
returns a type that depends on its caller:
|
||||
|
||||
```swift
|
||||
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
|
||||
```
|
||||
|
||||
<!--
|
||||
From https://developer.apple.com/documentation/swift/1538951-max
|
||||
Not test code because it won't actually compile
|
||||
and there's nothing to meaningfully test.
|
||||
-->
|
||||
|
||||
The code that calls `max(_:_:)` chooses the values for `x` and `y`,
|
||||
and the type of those values determines the concrete type of `T`.
|
||||
The calling code can use any type
|
||||
that conforms to the `Comparable` protocol.
|
||||
The code inside the function is written in a general way
|
||||
so it can handle whatever type the caller provides.
|
||||
The implementation of `max(_:_:)` uses only functionality
|
||||
that all `Comparable` types share.
|
||||
|
||||
Those roles are reversed for a function with an opaque return type.
|
||||
An opaque type lets the function implementation
|
||||
pick the type for the value it returns
|
||||
in a way that's abstracted away from the code that calls the function.
|
||||
For example, the function in the following example returns a trapezoid
|
||||
without exposing the underlying type of that shape.
|
||||
|
||||
```swift
|
||||
struct Square: Shape {
|
||||
var size: Int
|
||||
func draw() -> String {
|
||||
let line = String(repeating: "*", count: size)
|
||||
let result = Array<String>(repeating: line, count: size)
|
||||
return result.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func makeTrapezoid() -> some Shape {
|
||||
let top = Triangle(size: 2)
|
||||
let middle = Square(size: 2)
|
||||
let bottom = FlippedShape(shape: top)
|
||||
let trapezoid = JoinedShape(
|
||||
top: top,
|
||||
bottom: JoinedShape(top: middle, bottom: bottom)
|
||||
)
|
||||
return trapezoid
|
||||
}
|
||||
let trapezoid = makeTrapezoid()
|
||||
print(trapezoid.draw())
|
||||
// *
|
||||
// **
|
||||
// **
|
||||
// **
|
||||
// **
|
||||
// *
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> struct Square: Shape {
|
||||
var size: Int
|
||||
func draw() -> String {
|
||||
let line = String(repeating: "*", count: size)
|
||||
let result = Array<String>(repeating: line, count: size)
|
||||
return result.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
-> func makeTrapezoid() -> some Shape {
|
||||
let top = Triangle(size: 2)
|
||||
let middle = Square(size: 2)
|
||||
let bottom = FlippedShape(shape: top)
|
||||
let trapezoid = JoinedShape(
|
||||
top: top,
|
||||
bottom: JoinedShape(top: middle, bottom: bottom)
|
||||
)
|
||||
return trapezoid
|
||||
}
|
||||
-> let trapezoid = makeTrapezoid()
|
||||
-> print(trapezoid.draw())
|
||||
</ *
|
||||
</ **
|
||||
</ **
|
||||
</ **
|
||||
</ **
|
||||
</ *
|
||||
```
|
||||
-->
|
||||
|
||||
The `makeTrapezoid()` function in this example
|
||||
declares its return type as `some Shape`;
|
||||
as a result, the function
|
||||
returns a value of some given type that conforms to the `Shape` protocol,
|
||||
without specifying any particular concrete type.
|
||||
Writing `makeTrapezoid()` this way lets it express
|
||||
the fundamental aspect of its public interface ---
|
||||
the value it returns is a shape ---
|
||||
without making the specific types that the shape is made from
|
||||
a part of its public interface.
|
||||
This implementation uses two triangles and a square,
|
||||
but the function could be rewritten to draw a trapezoid
|
||||
in a variety of other ways
|
||||
without changing its return type.
|
||||
|
||||
This example highlights the way that an opaque return type
|
||||
is like the reverse of a generic type.
|
||||
The code inside `makeTrapezoid()` can return any type it needs to,
|
||||
as long as that type conforms to the `Shape` protocol,
|
||||
like the calling code does for a generic function.
|
||||
The code that calls the function needs to be written in a general way,
|
||||
like the implementation of a generic function,
|
||||
so that it can work with any `Shape` value
|
||||
that's returned by `makeTrapezoid()`.
|
||||
|
||||
You can also combine opaque return types with generics.
|
||||
The functions in the following code both return a value
|
||||
of some type that conforms to the `Shape` protocol.
|
||||
|
||||
```swift
|
||||
func flip<T: Shape>(_ shape: T) -> some Shape {
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
|
||||
JoinedShape(top: top, bottom: bottom)
|
||||
}
|
||||
|
||||
let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
|
||||
print(opaqueJoinedTriangles.draw())
|
||||
// *
|
||||
// **
|
||||
// ***
|
||||
// ***
|
||||
// **
|
||||
// *
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> func flip<T: Shape>(_ shape: T) -> some Shape {
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
-> func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
|
||||
JoinedShape(top: top, bottom: bottom)
|
||||
}
|
||||
|
||||
-> let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
|
||||
-> print(opaqueJoinedTriangles.draw())
|
||||
</ *
|
||||
</ **
|
||||
</ ***
|
||||
</ ***
|
||||
</ **
|
||||
</ *
|
||||
```
|
||||
-->
|
||||
|
||||
The value of `opaqueJoinedTriangles` in this example
|
||||
is the same as `joinedTriangles` in the generics example
|
||||
in the <doc:OpaqueTypes#The-Problem-that-Opaque-Types-Solve> section earlier in this chapter.
|
||||
However, unlike the value in that example,
|
||||
`flip(_:)` and `join(_:_:)` wrap the underlying types
|
||||
that the generic shape operations return
|
||||
in an opaque return type,
|
||||
which prevents those types from being visible.
|
||||
Both functions are generic because the types they rely on are generic,
|
||||
and the type parameters to the function
|
||||
pass along the type information needed by `FlippedShape` and `JoinedShape`.
|
||||
|
||||
If a function with an opaque return type
|
||||
returns from multiple places,
|
||||
all of the possible return values must have the same type.
|
||||
For a generic function,
|
||||
that return type can use the function's generic type parameters,
|
||||
but it must still be a single type.
|
||||
For example,
|
||||
here's an *invalid* version of the shape-flipping function
|
||||
that includes a special case for squares:
|
||||
|
||||
```swift
|
||||
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
|
||||
if shape is Square {
|
||||
return shape // Error: Return types don't match.
|
||||
}
|
||||
return FlippedShape(shape: shape) // Error: Return types don't match.
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-err`
|
||||
|
||||
```swifttest
|
||||
>> protocol Shape {
|
||||
>> func draw() -> String
|
||||
>> }
|
||||
>> struct Square: Shape {
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
>> struct FlippedShape<T: Shape>: Shape {
|
||||
>> var shape: T
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
-> func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
|
||||
if shape is Square {
|
||||
return shape // Error: Return types don't match.
|
||||
}
|
||||
return FlippedShape(shape: shape) // Error: Return types don't match.
|
||||
}
|
||||
!$ error: function declares an opaque return type 'some Shape', but the return statements in its body do not have matching underlying types
|
||||
!! func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
|
||||
!! ^ ~~~~~~~~~~
|
||||
!$ note: return statement has underlying type 'T'
|
||||
!! return shape // Error: Return types don't match.
|
||||
!! ^
|
||||
!$ note: return statement has underlying type 'FlippedShape<T>'
|
||||
!! return FlippedShape(shape: shape) // Error: Return types don't match.
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
If you call this function with a `Square`, it returns a `Square`;
|
||||
otherwise, it returns a `FlippedShape`.
|
||||
This violates the requirement to return values of only one type
|
||||
and makes `invalidFlip(_:)` invalid code.
|
||||
One way to fix `invalidFlip(_:)` is to move the special case for squares
|
||||
into the implementation of `FlippedShape`,
|
||||
which lets this function always return a `FlippedShape` value:
|
||||
|
||||
```swift
|
||||
struct FlippedShape<T: Shape>: Shape {
|
||||
var shape: T
|
||||
func draw() -> String {
|
||||
if shape is Square {
|
||||
return shape.draw()
|
||||
}
|
||||
let lines = shape.draw().split(separator: "\n")
|
||||
return lines.reversed().joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-special-flip`
|
||||
|
||||
```swifttest
|
||||
>> protocol Shape { func draw() -> String }
|
||||
>> struct Square: Shape {
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
-> struct FlippedShape<T: Shape>: Shape {
|
||||
var shape: T
|
||||
func draw() -> String {
|
||||
if shape is Square {
|
||||
return shape.draw()
|
||||
}
|
||||
let lines = shape.draw().split(separator: "\n")
|
||||
return lines.reversed().joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
Another way to fix it is with type erasure.
|
||||
Define a wrapper called AnyShape,
|
||||
and wrap whatever shape you created inside invalidFlip(_:)
|
||||
before returning it.
|
||||
That example is long enough that it breaks the flow here.
|
||||
-->
|
||||
|
||||
The requirement to always return a single type
|
||||
doesn't prevent you from using generics in an opaque return type.
|
||||
Here's an example of a function that incorporates its type parameter
|
||||
into the underlying type of the value it returns:
|
||||
|
||||
```swift
|
||||
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
|
||||
return Array<T>(repeating: shape, count: count)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
|
||||
return Array<T>(repeating: shape, count: count)
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
In this case,
|
||||
the underlying type of the return value
|
||||
varies depending on `T`:
|
||||
Whatever shape is passed it,
|
||||
`repeat(shape:count:)` creates and returns an array of that shape.
|
||||
Nevertheless,
|
||||
the return value always has the same underlying type of `[T]`,
|
||||
so it follows the requirement that functions with opaque return types
|
||||
must return values of only a single type.
|
||||
|
||||
## Boxed Protocol Types
|
||||
|
||||
A boxed protocol type is also sometimes called an *existential type*,
|
||||
which comes from the phrase
|
||||
"there exists a type *T* such that *T* conforms to the protocol".
|
||||
To make a boxed protocol type,
|
||||
write `any` before the name of a protocol.
|
||||
Here's an example:
|
||||
|
||||
```swift
|
||||
struct VerticalShapes: Shape {
|
||||
var shapes: [any Shape]
|
||||
func draw() -> String {
|
||||
return shapes.map { $0.draw() }.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
let largeTriangle = Triangle(size: 5)
|
||||
let largeSquare = Square(size: 5)
|
||||
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
|
||||
print(vertical.draw())
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `boxed-protocol-types`
|
||||
|
||||
```swifttest
|
||||
>> protocol Shape {
|
||||
>> func draw() -> String
|
||||
>> }
|
||||
>> struct Triangle: Shape {
|
||||
>> var size: Int
|
||||
>> func draw() -> String {
|
||||
>> var result: [String] = []
|
||||
>> for length in 1...size {
|
||||
>> result.append(String(repeating: "*", count: length))
|
||||
>> }
|
||||
>> return result.joined(separator: "\n")
|
||||
>> }
|
||||
>> }
|
||||
>> struct Square: Shape {
|
||||
>> var size: Int
|
||||
>> func draw() -> String {
|
||||
>> let line = String(repeating: "*", count: size)
|
||||
>> let result = Array<String>(repeating: line, count: size)
|
||||
>> return result.joined(separator: "\n")
|
||||
>> }
|
||||
>
|
||||
-> struct VerticalShapes: Shape {
|
||||
var shapes: [any Shape]
|
||||
func draw() -> String {
|
||||
return shapes.map { $0.draw() }.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
->
|
||||
-> let largeTriangle = Triangle(size: 5)
|
||||
-> let largeSquare = Square(size: 5)
|
||||
-> let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
|
||||
-> print(vertical.draw())
|
||||
<< *
|
||||
<< **
|
||||
<< ***
|
||||
<< ****
|
||||
<< *****
|
||||
<<-
|
||||
<< *****
|
||||
<< *****
|
||||
<< *****
|
||||
<< *****
|
||||
<< *****
|
||||
```
|
||||
-->
|
||||
|
||||
In the example above,
|
||||
`VerticalShapes` declares the type of `shapes` as `[any Shape]` ---
|
||||
an array of boxed `Shape` elements.
|
||||
Each element in the array can be a different type,
|
||||
and each of those types must conform to the `Shape` protocol.
|
||||
To support this runtime flexibility,
|
||||
Swift adds a level of indirection when necessary ---
|
||||
this indirection is called a *box*,
|
||||
and it has a performance cost.
|
||||
|
||||
Within the `VerticalShapes` type,
|
||||
the code can use methods, properties, and subscripts
|
||||
that are required by the `Shape` protocol.
|
||||
For example, the `draw()` method of `VerticalShapes`
|
||||
calls the `draw()` method on each element of the array.
|
||||
This method is available because `Shape` requires a `draw()` method.
|
||||
In contrast,
|
||||
trying to access the `size` property of the triangle,
|
||||
or any other properties or methods that aren't required by `Shape`,
|
||||
produces an error.
|
||||
|
||||
Contrast the three types you could use for `shapes`:
|
||||
|
||||
- Using generics,
|
||||
by writing `struct VerticalShapes<S: Shape>` and `var shapes: [S]`,
|
||||
makes an array whose elements are some specific shape type,
|
||||
and where the identity of that specific type
|
||||
is visible to any code that interacts with the array.
|
||||
|
||||
- Using an opaque type,
|
||||
by writing `var shapes: [some Shape]`,
|
||||
makes an array whose elements are some specific shape type,
|
||||
and where that specific type's identity is hidden.
|
||||
|
||||
- Using a boxed protocol type,
|
||||
by writing `var shapes: [any Shape]`,
|
||||
makes an array that can store elements of different types,
|
||||
and where those types' identities are hidden.
|
||||
|
||||
In this case,
|
||||
a boxed protocol type is the only approach
|
||||
that lets callers of `VerticalShapes` mix different kinds of shapes together.
|
||||
|
||||
You can use an `as` cast
|
||||
when you know the underlying type of a boxed value.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
if let downcastTriangle = vertical.shapes[0] as? Triangle {
|
||||
print(downcastTriangle.size)
|
||||
}
|
||||
// Prints "5".
|
||||
```
|
||||
|
||||
For more information, see <doc:TypeCasting#Downcasting>.
|
||||
|
||||
## Differences Between Opaque Types and Boxed Protocol Types
|
||||
|
||||
Returning an opaque type looks very similar
|
||||
to using a boxed protocol type as the return type of a function,
|
||||
but these two kinds of return type differ in
|
||||
whether they preserve type identity.
|
||||
An opaque type refers to one specific type,
|
||||
although the caller of the function isn't able to see which type;
|
||||
a boxed protocol type can refer to any type that conforms to the protocol.
|
||||
Generally speaking,
|
||||
boxed protocol types give you more flexibility
|
||||
about the underlying types of the values they store,
|
||||
and opaque types let you make stronger guarantees
|
||||
about those underlying types.
|
||||
|
||||
For example,
|
||||
here's a version of `flip(_:)`
|
||||
that uses a boxed protocol type as its return type
|
||||
instead of an opaque return type:
|
||||
|
||||
```swift
|
||||
func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-existential-error`
|
||||
|
||||
```swifttest
|
||||
>> protocol Shape {
|
||||
>> func draw() -> String
|
||||
>> }
|
||||
>> struct Triangle: Shape {
|
||||
>> var size: Int
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
>> struct Square: Shape {
|
||||
>> var size: Int
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
>> struct FlippedShape<T: Shape>: Shape {
|
||||
>> var shape: T
|
||||
>> func draw() -> String { return "#" } // stub implementation
|
||||
>> }
|
||||
-> func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This version of `protoFlip(_:)`
|
||||
has the same body as `flip(_:)`,
|
||||
and it always returns a value of the same type.
|
||||
Unlike `flip(_:)`,
|
||||
the value that `protoFlip(_:)` returns isn't required
|
||||
to always have the same type ---
|
||||
it just has to conform to the `Shape` protocol.
|
||||
Put another way,
|
||||
`protoFlip(_:)` makes a much looser API contract with its caller
|
||||
than `flip(_:)` makes.
|
||||
It reserves the flexibility to return values of multiple types:
|
||||
|
||||
```swift
|
||||
func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
if shape is Square {
|
||||
return shape
|
||||
}
|
||||
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-existential-error`
|
||||
|
||||
```swifttest
|
||||
-> func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
if shape is Square {
|
||||
return shape
|
||||
}
|
||||
|
||||
return FlippedShape(shape: shape)
|
||||
}
|
||||
!$ error: invalid redeclaration of 'protoFlip'
|
||||
!! func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
!! ^
|
||||
!$ note: 'protoFlip' previously declared here
|
||||
!! func protoFlip<T: Shape>(_ shape: T) -> Shape {
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
The revised version of the code returns
|
||||
an instance of `Square` or an instance of `FlippedShape`,
|
||||
depending on what shape is passed in.
|
||||
Two flipped shapes returned by this function
|
||||
might have completely different types.
|
||||
Other valid versions of this function could return values of different types
|
||||
when flipping multiple instances of the same shape.
|
||||
The less specific return type information from `protoFlip(_:)` means that
|
||||
many operations that depend on type information
|
||||
aren't available on the returned value.
|
||||
For example, it's not possible to write an `==` operator
|
||||
comparing results returned by this function.
|
||||
|
||||
```swift
|
||||
let protoFlippedTriangle = protoFlip(smallTriangle)
|
||||
let sameThing = protoFlip(smallTriangle)
|
||||
protoFlippedTriangle == sameThing // Error
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-existential-error`
|
||||
|
||||
```swifttest
|
||||
>> let smallTriangle = Triangle(size: 3)
|
||||
-> let protoFlippedTriangle = protoFlip(smallTriangle)
|
||||
-> let sameThing = protoFlip(smallTriangle)
|
||||
-> protoFlippedTriangle == sameThing // Error
|
||||
!$ error: binary operator '==' cannot be applied to two 'any Shape' operands
|
||||
!! protoFlippedTriangle == sameThing // Error
|
||||
!! ~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~
|
||||
```
|
||||
-->
|
||||
|
||||
The error on the last line of the example occurs for several reasons.
|
||||
The immediate issue is that the `Shape` doesn't include an `==` operator
|
||||
as part of its protocol requirements.
|
||||
If you try adding one, the next issue you'll encounter
|
||||
is that the `==` operator needs to know
|
||||
the types of its left-hand and right-hand arguments.
|
||||
This sort of operator usually takes arguments of type `Self`,
|
||||
matching whatever concrete type adopts the protocol,
|
||||
but adding a `Self` requirement to the protocol
|
||||
doesn't allow for the type erasure that happens
|
||||
when you use the protocol as a type.
|
||||
|
||||
Using a boxed protocol type as the return type for a function
|
||||
gives you the flexibility to return any type that conforms to the protocol.
|
||||
However, the cost of that flexibility
|
||||
is that some operations aren't possible on the returned values.
|
||||
The example shows how the `==` operator isn't available ---
|
||||
it depends on specific type information
|
||||
that isn't preserved by using a boxed protocol type.
|
||||
|
||||
Another problem with this approach is that the shape transformations don't nest.
|
||||
The result of flipping a triangle is a value of type `Shape`,
|
||||
and the `protoFlip(_:)` function takes an argument
|
||||
of some type that conforms to the `Shape` protocol.
|
||||
However, a value of a boxed protocol type doesn't conform to that protocol;
|
||||
the value returned by `protoFlip(_:)` doesn't conform to `Shape`.
|
||||
This means code like `protoFlip(protoFlip(smallTriangle))`
|
||||
that applies multiple transformations is invalid
|
||||
because the flipped shape isn't a valid argument to `protoFlip(_:)`.
|
||||
|
||||
In contrast,
|
||||
opaque types preserve the identity of the underlying type.
|
||||
Swift can infer associated types,
|
||||
which lets you use an opaque return value
|
||||
in places where a boxed protocol type can't be used as a return value.
|
||||
For example,
|
||||
here's a version of the `Container` protocol from <doc:Generics>:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype Item
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
}
|
||||
extension Array: Container { }
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result, opaque-result-existential-error`
|
||||
|
||||
```swifttest
|
||||
-> protocol Container {
|
||||
associatedtype Item
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
}
|
||||
-> extension Array: Container { }
|
||||
```
|
||||
-->
|
||||
|
||||
You can't use `Container` as the return type of a function
|
||||
because that protocol has an associated type.
|
||||
You also can't use it as constraint in a generic return type
|
||||
because there isn't enough information outside the function body
|
||||
to infer what the generic type needs to be.
|
||||
|
||||
```swift
|
||||
// Error: Protocol with associated types can't be used as a return type.
|
||||
func makeProtocolContainer<T>(item: T) -> Container {
|
||||
return [item]
|
||||
}
|
||||
|
||||
// Error: Not enough information to infer C.
|
||||
func makeProtocolContainer<T, C: Container>(item: T) -> C {
|
||||
return [item]
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result-existential-error`
|
||||
|
||||
```swifttest
|
||||
// Error: Protocol with associated types can't be used as a return type.
|
||||
-> func makeProtocolContainer<T>(item: T) -> Container {
|
||||
return [item]
|
||||
}
|
||||
|
||||
// Error: Not enough information to infer C.
|
||||
-> func makeProtocolContainer<T, C: Container>(item: T) -> C {
|
||||
return [item]
|
||||
}
|
||||
!$ error: use of protocol 'Container' as a type must be written 'any Container'
|
||||
!! func makeProtocolContainer<T>(item: T) -> Container {
|
||||
!! ^~~~~~~~~
|
||||
!! any Container
|
||||
!$ error: cannot convert return expression of type '[T]' to return type 'C'
|
||||
!! return [item]
|
||||
!! ^~~~~~
|
||||
!! as! C
|
||||
```
|
||||
-->
|
||||
|
||||
Using the opaque type `some Container` as a return type
|
||||
expresses the desired API contract --- the function returns a container,
|
||||
but declines to specify the container's type:
|
||||
|
||||
```swift
|
||||
func makeOpaqueContainer<T>(item: T) -> some Container {
|
||||
return [item]
|
||||
}
|
||||
let opaqueContainer = makeOpaqueContainer(item: 12)
|
||||
let twelve = opaqueContainer[0]
|
||||
print(type(of: twelve))
|
||||
// Prints "Int".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `opaque-result`
|
||||
|
||||
```swifttest
|
||||
-> func makeOpaqueContainer<T>(item: T) -> some Container {
|
||||
return [item]
|
||||
}
|
||||
-> let opaqueContainer = makeOpaqueContainer(item: 12)
|
||||
-> let twelve = opaqueContainer[0]
|
||||
-> print(type(of: twelve))
|
||||
<- Int
|
||||
```
|
||||
-->
|
||||
|
||||
The type of `twelve` is inferred to be `Int`,
|
||||
which illustrates the fact that type inference works with opaque types.
|
||||
In the implementation of `makeOpaqueContainer(item:)`,
|
||||
the underlying type of the opaque container is `[T]`.
|
||||
In this case, `T` is `Int`,
|
||||
so the return value is an array of integers
|
||||
and the `Item` associated type is inferred to be `Int`.
|
||||
The subscript on `Container` returns `Item`,
|
||||
which means that the type of `twelve` is also inferred to be `Int`.
|
||||
|
||||
<!--
|
||||
TODO: Expansion for the future
|
||||
|
||||
You can combine the flexibility of returning a value of protocol type
|
||||
with the API-boundary enforcement of opaque types
|
||||
by using type erasure
|
||||
like the Swift standard library uses in the
|
||||
`AnySequence <//apple_ref/fake/AnySequence`_ type.
|
||||
|
||||
protocol P { func f() -> Int }
|
||||
|
||||
struct AnyP: P {
|
||||
var p: P
|
||||
func f() -> Int { return p.f() }
|
||||
}
|
||||
|
||||
struct P1 {
|
||||
func f() -> Int { return 100 }
|
||||
}
|
||||
struct P2 {
|
||||
func f() -> Int { return 200 }
|
||||
}
|
||||
|
||||
func opaque(x: Int) -> some P {
|
||||
let result: P
|
||||
if x > 100 {
|
||||
result = P1()
|
||||
} else {
|
||||
result = P2()
|
||||
}
|
||||
return AnyP(p: result)
|
||||
}
|
||||
-->
|
||||
|
||||
|
||||
## Opaque Parameter Types
|
||||
|
||||
In addition to writing `some` to return an opaque type,
|
||||
you can also write `some` in the type for a parameter
|
||||
to a function, subscript, or initializer.
|
||||
However, when you write `some` in a parameter type
|
||||
that's just a shorter syntax for generics, not an opaque type.
|
||||
For example,
|
||||
both of the functions below are equivalent:
|
||||
|
||||
```swift
|
||||
func drawTwiceGeneric<SomeShape: Shape>(_ shape: SomeShape) -> String {
|
||||
let drawn = shape.draw()
|
||||
return drawn + "\n" + drawn
|
||||
}
|
||||
|
||||
func drawTwiceSome(_ shape: some Shape) -> String {
|
||||
let drawn = shape.draw()
|
||||
return drawn + "\n" + drawn
|
||||
}
|
||||
```
|
||||
|
||||
The `drawTwiceGeneric(_:)` function
|
||||
declares a generic type parameter named `SomeShape`,
|
||||
with a constraint that requires `SomeShape` to conform to the `Shape` protocol.
|
||||
The `drawTwiceSome(_:)` function
|
||||
uses the type `some Shape` for its argument.
|
||||
This creates a new, unnamed generic type parameter for the function
|
||||
with a constraint that requires the type to conform to the `Shape` protocol.
|
||||
Because the generic type doesn't have a name,
|
||||
you can't refer to that type elsewhere in the function.
|
||||
|
||||
If you write `some` before more than one parameter's type,
|
||||
each of the generic types are independent.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
func combine(shape s1: some Shape, with s2: some Shape) -> String {
|
||||
return s1.draw() + "\n" + s2.draw()
|
||||
}
|
||||
|
||||
combine(smallTriangle, trapezoid)
|
||||
```
|
||||
|
||||
In the `combine(shape:with:)` function,
|
||||
the types of the first and second parameter
|
||||
must both conform to the `Shape` protocol,
|
||||
but there's no constraint that requires them to be the same type.
|
||||
When you call `combine(shape:with)`,
|
||||
you can pass two different shapes ---
|
||||
in this case, one triangle and one trapezoid.
|
||||
|
||||
Unlike the syntax for named generic type parameters,
|
||||
described in <doc:Generics> chapter,
|
||||
this lightweight syntax can't include
|
||||
a generic `where` clause or any same-type (`==`) constraints.
|
||||
In addition,
|
||||
using the lightweight syntax for very complex constraints
|
||||
can be hard to read.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
894
skills/programming-swift/LanguageGuide/OptionalChaining.md
Normal file
894
skills/programming-swift/LanguageGuide/OptionalChaining.md
Normal file
@@ -0,0 +1,894 @@
|
||||
# Optional Chaining
|
||||
|
||||
Access members of an optional value without unwrapping.
|
||||
|
||||
*Optional chaining* is a process for querying and calling
|
||||
properties, methods, and subscripts on an optional that might currently be `nil`.
|
||||
If the optional contains a value,
|
||||
the property, method, or subscript call succeeds;
|
||||
if the optional is `nil`, the property, method, or subscript call returns `nil`.
|
||||
Multiple queries can be chained together,
|
||||
and the entire chain fails gracefully if any link in the chain is `nil`.
|
||||
|
||||
> Note: Optional chaining in Swift is similar to messaging `nil` in Objective-C,
|
||||
> but in a way that works for any type, and that can be checked for success or failure.
|
||||
|
||||
## Optional Chaining as an Alternative to Forced Unwrapping
|
||||
|
||||
You specify optional chaining by placing a question mark (`?`)
|
||||
after the optional value on which you wish to call a property, method or subscript
|
||||
if the optional is non-`nil`.
|
||||
This is very similar to placing an exclamation point (`!`)
|
||||
after an optional value to force the unwrapping of its value.
|
||||
The main difference is that optional chaining fails gracefully when the optional is `nil`,
|
||||
whereas forced unwrapping triggers a runtime error when the optional is `nil`.
|
||||
|
||||
To reflect the fact that optional chaining can be called on a `nil` value,
|
||||
the result of an optional chaining call is always an optional value,
|
||||
even if the property, method, or subscript you are querying returns a non-optional value.
|
||||
You can use this optional return value to check whether
|
||||
the optional chaining call was successful
|
||||
(the returned optional contains a value),
|
||||
or didn't succeed due to a `nil` value in the chain
|
||||
(the returned optional value is `nil`).
|
||||
|
||||
Specifically, the result of an optional chaining call
|
||||
is of the same type as the expected return value, but wrapped in an optional.
|
||||
A property that normally returns an `Int` will return an `Int?`
|
||||
when accessed through optional chaining.
|
||||
|
||||
The next several code snippets demonstrate
|
||||
how optional chaining differs from forced unwrapping
|
||||
and enables you to check for success.
|
||||
|
||||
First, two classes called `Person` and `Residence` are defined:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
|
||||
class Residence {
|
||||
var numberOfRooms = 1
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntro, optionalChainingIntroAssert`
|
||||
|
||||
```swifttest
|
||||
-> class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
|
||||
-> class Residence {
|
||||
var numberOfRooms = 1
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
`Residence` instances have a single `Int` property called `numberOfRooms`,
|
||||
with a default value of `1`.
|
||||
`Person` instances have an optional `residence` property of type `Residence?`.
|
||||
|
||||
If you create a new `Person` instance,
|
||||
its `residence` property is default initialized to `nil`,
|
||||
by virtue of being optional.
|
||||
In the code below, `john` has a `residence` property value of `nil`:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntro, optionalChainingIntroAssert`
|
||||
|
||||
```swifttest
|
||||
-> let john = Person()
|
||||
```
|
||||
-->
|
||||
|
||||
If you try to access the `numberOfRooms` property of this person's `residence`,
|
||||
by placing an exclamation point after `residence` to force the unwrapping of its value,
|
||||
you trigger a runtime error,
|
||||
because there's no `residence` value to unwrap:
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
// this triggers a runtime error
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntroAssert`
|
||||
|
||||
```swifttest
|
||||
-> let roomCount = john.residence!.numberOfRooms
|
||||
xx assert
|
||||
// this triggers a runtime error
|
||||
```
|
||||
-->
|
||||
|
||||
The code above succeeds when `john.residence` has a non-`nil` value
|
||||
and will set `roomCount` to an `Int` value containing the appropriate number of rooms.
|
||||
However, this code always triggers a runtime error when `residence` is `nil`,
|
||||
as illustrated above.
|
||||
|
||||
Optional chaining provides an alternative way to access the value of `numberOfRooms`.
|
||||
To use optional chaining, use a question mark in place of the exclamation point:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// Prints "Unable to retrieve the number of rooms."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntro`
|
||||
|
||||
```swifttest
|
||||
-> if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
<- Unable to retrieve the number of rooms.
|
||||
```
|
||||
-->
|
||||
|
||||
This tells Swift to “chain” on the optional `residence` property
|
||||
and to retrieve the value of `numberOfRooms` if `residence` exists.
|
||||
|
||||
Because the attempt to access `numberOfRooms` has the potential to fail,
|
||||
the optional chaining attempt returns a value of type `Int?`, or “optional `Int`”.
|
||||
When `residence` is `nil`, as in the example above,
|
||||
this optional `Int` will also be `nil`,
|
||||
to reflect the fact that it was not possible to access `numberOfRooms`.
|
||||
The optional `Int` is accessed through optional binding
|
||||
to unwrap the integer and assign the non-optional value
|
||||
to the `roomCount` constant.
|
||||
|
||||
Note that this is true even though `numberOfRooms` is a non-optional `Int`.
|
||||
The fact that it's queried through an optional chain
|
||||
means that the call to `numberOfRooms`
|
||||
will always return an `Int?` instead of an `Int`.
|
||||
|
||||
You can assign a `Residence` instance to `john.residence`,
|
||||
so that it no longer has a `nil` value:
|
||||
|
||||
```swift
|
||||
john.residence = Residence()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntro`
|
||||
|
||||
```swifttest
|
||||
-> john.residence = Residence()
|
||||
```
|
||||
-->
|
||||
|
||||
`john.residence` now contains an actual `Residence` instance, rather than `nil`.
|
||||
If you try to access `numberOfRooms` with the same optional chaining as before,
|
||||
it will now return an `Int?` that contains
|
||||
the default `numberOfRooms` value of `1`:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// Prints "John's residence has 1 room(s)."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingIntro`
|
||||
|
||||
```swifttest
|
||||
-> if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
<- John's residence has 1 room(s).
|
||||
```
|
||||
-->
|
||||
|
||||
## Defining Model Classes for Optional Chaining
|
||||
|
||||
You can use optional chaining with calls to properties, methods, and subscripts
|
||||
that are more than one level deep.
|
||||
This enables you to drill down into subproperties
|
||||
within complex models of interrelated types,
|
||||
and to check whether it's possible to access
|
||||
properties, methods, and subscripts on those subproperties.
|
||||
|
||||
The code snippets below define four model classes
|
||||
for use in several subsequent examples,
|
||||
including examples of multilevel optional chaining.
|
||||
These classes expand upon the `Person` and `Residence` model from above
|
||||
by adding a `Room` and `Address` class,
|
||||
with associated properties, methods, and subscripts.
|
||||
|
||||
The `Person` class is defined in the same way as before:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `Residence` class is more complex than before.
|
||||
This time, the `Residence` class defines a variable property called `rooms`,
|
||||
which is initialized with an empty array of type `[Room]`:
|
||||
|
||||
```swift
|
||||
class Residence {
|
||||
var rooms: [Room] = []
|
||||
var numberOfRooms: Int {
|
||||
return rooms.count
|
||||
}
|
||||
subscript(i: Int) -> Room {
|
||||
get {
|
||||
return rooms[i]
|
||||
}
|
||||
set {
|
||||
rooms[i] = newValue
|
||||
}
|
||||
}
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
var address: Address?
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> class Residence {
|
||||
var rooms: [Room] = []
|
||||
var numberOfRooms: Int {
|
||||
return rooms.count
|
||||
}
|
||||
subscript(i: Int) -> Room {
|
||||
get {
|
||||
return rooms[i]
|
||||
}
|
||||
set {
|
||||
rooms[i] = newValue
|
||||
}
|
||||
}
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
var address: Address?
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Because this version of `Residence` stores an array of `Room` instances,
|
||||
its `numberOfRooms` property is implemented as a computed property,
|
||||
not a stored property.
|
||||
The computed `numberOfRooms` property simply returns
|
||||
the value of the `count` property from the `rooms` array.
|
||||
|
||||
As a shortcut to accessing its `rooms` array,
|
||||
this version of `Residence` provides a read-write subscript that provides access to
|
||||
the room at the requested index in the `rooms` array.
|
||||
|
||||
This version of `Residence` also provides a method called `printNumberOfRooms`,
|
||||
which simply prints the number of rooms in the residence.
|
||||
|
||||
Finally, `Residence` defines an optional property called `address`,
|
||||
with a type of `Address?`.
|
||||
The `Address` class type for this property is defined below.
|
||||
|
||||
The `Room` class used for the `rooms` array is
|
||||
a simple class with one property called `name`,
|
||||
and an initializer to set that property to a suitable room name:
|
||||
|
||||
```swift
|
||||
class Room {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> class Room {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The final class in this model is called `Address`.
|
||||
This class has three optional properties of type `String?`.
|
||||
The first two properties, `buildingName` and `buildingNumber`,
|
||||
are alternative ways to identify a particular building as part of an address.
|
||||
The third property, `street`, is used to name the street for that address:
|
||||
|
||||
```swift
|
||||
class Address {
|
||||
var buildingName: String?
|
||||
var buildingNumber: String?
|
||||
var street: String?
|
||||
func buildingIdentifier() -> String? {
|
||||
if let buildingNumber = buildingNumber, let street = street {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else if buildingName != nil {
|
||||
return buildingName
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> class Address {
|
||||
var buildingName: String?
|
||||
var buildingNumber: String?
|
||||
var street: String?
|
||||
func buildingIdentifier() -> String? {
|
||||
if let buildingNumber, let street {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else if buildingName != nil {
|
||||
return buildingName
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The `Address` class also provides a method called `buildingIdentifier()`,
|
||||
which has a return type of `String?`.
|
||||
This method checks the properties of the address
|
||||
and returns `buildingName` if it has a value,
|
||||
or `buildingNumber` concatenated with `street` if both have values,
|
||||
or `nil` otherwise.
|
||||
|
||||
## Accessing Properties Through Optional Chaining
|
||||
|
||||
As demonstrated in <doc:OptionalChaining#Optional-Chaining-as-an-Alternative-to-Forced-Unwrapping>,
|
||||
you can use optional chaining to access a property on an optional value,
|
||||
and to check if that property access is successful.
|
||||
|
||||
Use the classes defined above to create a new `Person` instance,
|
||||
and try to access its `numberOfRooms` property as before:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// Prints "Unable to retrieve the number of rooms."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> let john = Person()
|
||||
-> if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
<- Unable to retrieve the number of rooms.
|
||||
```
|
||||
-->
|
||||
|
||||
Because `john.residence` is `nil`,
|
||||
this optional chaining call fails in the same way as before.
|
||||
|
||||
You can also attempt to set a property's value through optional chaining:
|
||||
|
||||
```swift
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
john.residence?.address = someAddress
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> let someAddress = Address()
|
||||
-> someAddress.buildingNumber = "29"
|
||||
-> someAddress.street = "Acacia Road"
|
||||
-> john.residence?.address = someAddress
|
||||
```
|
||||
-->
|
||||
|
||||
In this example,
|
||||
the attempt to set the `address` property of `john.residence` will fail,
|
||||
because `john.residence` is currently `nil`.
|
||||
|
||||
The assignment is part of the optional chaining,
|
||||
which means none of the code on the right-hand side of the `=` operator
|
||||
is evaluated.
|
||||
In the previous example,
|
||||
it's not easy to see that `someAddress` is never evaluated,
|
||||
because accessing a constant doesn't have any side effects.
|
||||
The listing below does the same assignment,
|
||||
but it uses a function to create the address.
|
||||
The function prints "Function was called" before returning a value,
|
||||
which lets you see
|
||||
whether the right-hand side of the `=` operator was evaluated.
|
||||
|
||||
```swift
|
||||
func createAddress() -> Address {
|
||||
print("Function was called.")
|
||||
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
|
||||
return someAddress
|
||||
}
|
||||
john.residence?.address = createAddress()
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> func createAddress() -> Address {
|
||||
print("Function was called.")
|
||||
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
|
||||
return someAddress
|
||||
}
|
||||
-> john.residence?.address = createAddress()
|
||||
>> let _ = createAddress()
|
||||
<< Function was called.
|
||||
```
|
||||
-->
|
||||
|
||||
You can tell that the `createAddress()` function isn't called,
|
||||
because nothing is printed.
|
||||
|
||||
## Calling Methods Through Optional Chaining
|
||||
|
||||
You can use optional chaining to call a method on an optional value,
|
||||
and to check whether that method call is successful.
|
||||
You can do this even if that method doesn't define a return value.
|
||||
|
||||
The `printNumberOfRooms()` method on the `Residence` class
|
||||
prints the current value of `numberOfRooms`.
|
||||
Here's how the method looks:
|
||||
|
||||
```swift
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChainingCallouts`
|
||||
|
||||
```swifttest
|
||||
-> func printNumberOfRooms() {
|
||||
>> let numberOfRooms = 3
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
This method doesn't specify a return type.
|
||||
However, functions and methods with no return type have an implicit return type of `Void`,
|
||||
as described in <doc:Functions#Functions-Without-Return-Values>.
|
||||
This means that they return a value of `()`, or an empty tuple.
|
||||
|
||||
If you call this method on an optional value with optional chaining,
|
||||
the method's return type will be `Void?`, not `Void`,
|
||||
because return values are always of an optional type when called through optional chaining.
|
||||
This enables you to use an `if` statement
|
||||
to check whether it was possible to call the `printNumberOfRooms()` method,
|
||||
even though the method doesn't itself define a return value.
|
||||
Compare the return value from the `printNumberOfRooms` call against `nil`
|
||||
to see if the method call was successful:
|
||||
|
||||
```swift
|
||||
if john.residence?.printNumberOfRooms() != nil {
|
||||
print("It was possible to print the number of rooms.")
|
||||
} else {
|
||||
print("It was not possible to print the number of rooms.")
|
||||
}
|
||||
// Prints "It was not possible to print the number of rooms."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if john.residence?.printNumberOfRooms() != nil {
|
||||
print("It was possible to print the number of rooms.")
|
||||
} else {
|
||||
print("It was not possible to print the number of rooms.")
|
||||
}
|
||||
<- It was not possible to print the number of rooms.
|
||||
```
|
||||
-->
|
||||
|
||||
The same is true if you attempt to set a property through optional chaining.
|
||||
The example above in <doc:OptionalChaining#Accessing-Properties-Through-Optional-Chaining>
|
||||
attempts to set an `address` value for `john.residence`,
|
||||
even though the `residence` property is `nil`.
|
||||
Any attempt to set a property through optional chaining returns a value of type `Void?`,
|
||||
which enables you to compare against `nil` to see if the property was set successfully:
|
||||
|
||||
```swift
|
||||
if (john.residence?.address = someAddress) != nil {
|
||||
print("It was possible to set the address.")
|
||||
} else {
|
||||
print("It was not possible to set the address.")
|
||||
}
|
||||
// Prints "It was not possible to set the address."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if (john.residence?.address = someAddress) != nil {
|
||||
print("It was possible to set the address.")
|
||||
} else {
|
||||
print("It was not possible to set the address.")
|
||||
}
|
||||
<- It was not possible to set the address.
|
||||
```
|
||||
-->
|
||||
|
||||
## Accessing Subscripts Through Optional Chaining
|
||||
|
||||
You can use optional chaining to try to retrieve and set
|
||||
a value from a subscript on an optional value,
|
||||
and to check whether that subscript call is successful.
|
||||
|
||||
> Note: When you access a subscript on an optional value through optional chaining,
|
||||
> you place the question mark *before* the subscript's brackets, not after.
|
||||
> The optional chaining question mark always follows immediately after
|
||||
> the part of the expression that's optional.
|
||||
|
||||
The example below tries to retrieve the name of
|
||||
the first room in the `rooms` array of the `john.residence` property
|
||||
using the subscript defined on the `Residence` class.
|
||||
Because `john.residence` is currently `nil`,
|
||||
the subscript call fails:
|
||||
|
||||
```swift
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// Prints "Unable to retrieve the first room name."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
<- Unable to retrieve the first room name.
|
||||
```
|
||||
-->
|
||||
|
||||
The optional chaining question mark in this subscript call
|
||||
is placed immediately after `john.residence`, before the subscript brackets,
|
||||
because `john.residence` is the optional value
|
||||
on which optional chaining is being attempted.
|
||||
|
||||
Similarly, you can try to set a new value through a subscript with optional chaining:
|
||||
|
||||
```swift
|
||||
john.residence?[0] = Room(name: "Bathroom")
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> john.residence?[0] = Room(name: "Bathroom")
|
||||
```
|
||||
-->
|
||||
|
||||
This subscript setting attempt also fails, because `residence` is currently `nil`.
|
||||
|
||||
If you create and assign an actual `Residence` instance to `john.residence`,
|
||||
with one or more `Room` instances in its `rooms` array,
|
||||
you can use the `Residence` subscript to access
|
||||
the actual items in the `rooms` array through optional chaining:
|
||||
|
||||
```swift
|
||||
let johnsHouse = Residence()
|
||||
johnsHouse.rooms.append(Room(name: "Living Room"))
|
||||
johnsHouse.rooms.append(Room(name: "Kitchen"))
|
||||
john.residence = johnsHouse
|
||||
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// Prints "The first room name is Living Room."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> let johnsHouse = Residence()
|
||||
-> johnsHouse.rooms.append(Room(name: "Living Room"))
|
||||
-> johnsHouse.rooms.append(Room(name: "Kitchen"))
|
||||
-> john.residence = johnsHouse
|
||||
|
||||
-> if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
<- The first room name is Living Room.
|
||||
```
|
||||
-->
|
||||
|
||||
### Accessing Subscripts of Optional Type
|
||||
|
||||
If a subscript returns a value of optional type ---
|
||||
such as the key subscript of Swift's `Dictionary` type ---
|
||||
place a question mark *after* the subscript's closing bracket
|
||||
to chain on its optional return value:
|
||||
|
||||
```swift
|
||||
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
||||
testScores["Dave"]?[0] = 91
|
||||
testScores["Bev"]?[0] += 1
|
||||
testScores["Brian"]?[0] = 72
|
||||
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
||||
-> testScores["Dave"]?[0] = 91
|
||||
-> testScores["Bev"]?[0] += 1
|
||||
-> testScores["Brian"]?[0] = 72
|
||||
>> let dave = "Dave"
|
||||
>> let bev = "Bev"
|
||||
/> the \"Dave\" array is now [\(testScores[dave]![0]), \(testScores[dave]![1]), \(testScores[dave]![2])] and the \"Bev\" array is now [\(testScores[bev]![0]), \(testScores[bev]![1]), \(testScores[bev]![2])]
|
||||
</ the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
|
||||
```
|
||||
-->
|
||||
|
||||
The example above defines a dictionary called `testScores`,
|
||||
which contains two key-value pairs that map a `String` key to an array of `Int` values.
|
||||
The example uses optional chaining to set the first item in the `"Dave"` array to `91`;
|
||||
to increment the first item in the `"Bev"` array by `1`;
|
||||
and to try to set the first item in an array for a key of `"Brian"`.
|
||||
The first two calls succeed, because the `testScores` dictionary
|
||||
contains keys for `"Dave"` and `"Bev"`.
|
||||
The third call fails, because the `testScores` dictionary
|
||||
doesn't contain a key for `"Brian"`.
|
||||
|
||||
## Linking Multiple Levels of Chaining
|
||||
|
||||
You can link together multiple levels of optional chaining
|
||||
to drill down to properties, methods, and subscripts deeper within a model.
|
||||
However, multiple levels of optional chaining
|
||||
don't add more levels of optionality to the returned value.
|
||||
|
||||
To put it another way:
|
||||
|
||||
- If the type you are trying to retrieve isn't optional,
|
||||
it will become optional because of the optional chaining.
|
||||
- If the type you are trying to retrieve is *already* optional,
|
||||
it will not become *more* optional because of the chaining.
|
||||
|
||||
Therefore:
|
||||
|
||||
- If you try to retrieve an `Int` value through optional chaining,
|
||||
an `Int?` is always returned,
|
||||
no matter how many levels of chaining are used.
|
||||
- Similarly, if you try to retrieve an `Int?` value through optional chaining,
|
||||
an `Int?` is always returned,
|
||||
no matter how many levels of chaining are used.
|
||||
|
||||
The example below tries to access the `street` property of the `address` property
|
||||
of the `residence` property of `john`.
|
||||
There are *two* levels of optional chaining in use here,
|
||||
to chain through the `residence` and `address` properties,
|
||||
both of which are of optional type:
|
||||
|
||||
```swift
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// Prints "Unable to retrieve the address."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
<- Unable to retrieve the address.
|
||||
```
|
||||
-->
|
||||
|
||||
The value of `john.residence` currently contains a valid `Residence` instance.
|
||||
However, the value of `john.residence.address` is currently `nil`.
|
||||
Because of this, the call to `john.residence?.address?.street` fails.
|
||||
|
||||
Note that in the example above,
|
||||
you are trying to retrieve the value of the `street` property.
|
||||
The type of this property is `String?`.
|
||||
The return value of `john.residence?.address?.street` is therefore also `String?`,
|
||||
even though two levels of optional chaining are applied in addition to
|
||||
the underlying optional type of the property.
|
||||
|
||||
If you set an actual `Address` instance as the value for `john.residence.address`,
|
||||
and set an actual value for the address's `street` property,
|
||||
you can access the value of the `street` property through multilevel optional chaining:
|
||||
|
||||
```swift
|
||||
let johnsAddress = Address()
|
||||
johnsAddress.buildingName = "The Larches"
|
||||
johnsAddress.street = "Laurel Street"
|
||||
john.residence?.address = johnsAddress
|
||||
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// Prints "John's street name is Laurel Street."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> let johnsAddress = Address()
|
||||
-> johnsAddress.buildingName = "The Larches"
|
||||
-> johnsAddress.street = "Laurel Street"
|
||||
-> john.residence?.address = johnsAddress
|
||||
|
||||
-> if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
<- John's street name is Laurel Street.
|
||||
```
|
||||
-->
|
||||
|
||||
In this example,
|
||||
the attempt to set the `address` property of `john.residence` will succeed,
|
||||
because the value of `john.residence`
|
||||
currently contains a valid `Residence` instance.
|
||||
|
||||
## Chaining on Methods with Optional Return Values
|
||||
|
||||
The previous example shows how to retrieve the value of
|
||||
a property of optional type through optional chaining.
|
||||
You can also use optional chaining to call a method that returns a value of optional type,
|
||||
and to chain on that method's return value if needed.
|
||||
|
||||
The example below calls the `Address` class's `buildingIdentifier()` method
|
||||
through optional chaining. This method returns a value of type `String?`.
|
||||
As described above, the ultimate return type of this method call after optional chaining
|
||||
is also `String?`:
|
||||
|
||||
```swift
|
||||
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
print("John's building identifier is \(buildingIdentifier).")
|
||||
}
|
||||
// Prints "John's building identifier is The Larches."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
print("John's building identifier is \(buildingIdentifier).")
|
||||
}
|
||||
<- John's building identifier is The Larches.
|
||||
```
|
||||
-->
|
||||
|
||||
If you want to perform further optional chaining on this method's return value,
|
||||
place the optional chaining question mark *after* the method's parentheses:
|
||||
|
||||
```swift
|
||||
if let beginsWithThe =
|
||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||
if beginsWithThe {
|
||||
print("John's building identifier begins with \"The\".")
|
||||
} else {
|
||||
print("John's building identifier doesn't begin with \"The\".")
|
||||
}
|
||||
}
|
||||
// Prints "John's building identifier begins with "The"."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optionalChaining`
|
||||
|
||||
```swifttest
|
||||
-> if let beginsWithThe =
|
||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||
if beginsWithThe {
|
||||
print("John's building identifier begins with \"The\".")
|
||||
} else {
|
||||
print("John's building identifier doesn't begin with \"The\".")
|
||||
}
|
||||
}
|
||||
<- John's building identifier begins with "The".
|
||||
```
|
||||
-->
|
||||
|
||||
> Note: In the example above,
|
||||
> you place the optional chaining question mark *after* the parentheses,
|
||||
> because the optional value you are chaining on is
|
||||
> the `buildingIdentifier()` method's return value,
|
||||
> and not the `buildingIdentifier()` method itself.
|
||||
|
||||
<!--
|
||||
TODO: add an example of chaining on a property of optional function type.
|
||||
This can then be tied in to a revised description of how
|
||||
the sugar for optional protocol requirements works.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
2071
skills/programming-swift/LanguageGuide/Properties.md
Normal file
2071
skills/programming-swift/LanguageGuide/Properties.md
Normal file
File diff suppressed because it is too large
Load Diff
2645
skills/programming-swift/LanguageGuide/Protocols.md
Normal file
2645
skills/programming-swift/LanguageGuide/Protocols.md
Normal file
File diff suppressed because it is too large
Load Diff
1796
skills/programming-swift/LanguageGuide/StringsAndCharacters.md
Normal file
1796
skills/programming-swift/LanguageGuide/StringsAndCharacters.md
Normal file
File diff suppressed because it is too large
Load Diff
433
skills/programming-swift/LanguageGuide/Subscripts.md
Normal file
433
skills/programming-swift/LanguageGuide/Subscripts.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Subscripts
|
||||
|
||||
Access the elements of a collection.
|
||||
|
||||
Classes, structures, and enumerations can define *subscripts*,
|
||||
which are shortcuts for accessing the member elements of a collection, list, or sequence.
|
||||
You use subscripts to set and retrieve values by index without needing
|
||||
separate methods for setting and retrieval.
|
||||
For example, you access elements in an `Array` instance as `someArray[index]`
|
||||
and elements in a `Dictionary` instance as `someDictionary[key]`.
|
||||
|
||||
You can define multiple subscripts for a single type,
|
||||
and the appropriate subscript overload to use is selected
|
||||
based on the type of index value you pass to the subscript.
|
||||
Subscripts aren't limited to a single dimension,
|
||||
and you can define subscripts with multiple input parameters
|
||||
to suit your custom type's needs.
|
||||
|
||||
<!--
|
||||
TODO: this chapter should provide an example of subscripting an enumeration,
|
||||
as per Joe Groff's example from rdar://16555559.
|
||||
-->
|
||||
|
||||
## Subscript Syntax
|
||||
|
||||
Subscripts enable you to query instances of a type
|
||||
by writing one or more values in square brackets after the instance name.
|
||||
Their syntax is similar to both instance method syntax and computed property syntax.
|
||||
You write subscript definitions with the `subscript` keyword,
|
||||
and specify one or more input parameters and a return type,
|
||||
in the same way as instance methods.
|
||||
Unlike instance methods, subscripts can be read-write or read-only.
|
||||
This behavior is communicated by a getter and setter
|
||||
in the same way as for computed properties:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
get {
|
||||
// Return an appropriate subscript value here.
|
||||
}
|
||||
set(newValue) {
|
||||
// Perform a suitable setting action here.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `subscriptSyntax`
|
||||
|
||||
```swifttest
|
||||
>> class Test1 {
|
||||
-> subscript(index: Int) -> Int {
|
||||
get {
|
||||
// Return an appropriate subscript value here.
|
||||
>> return 1
|
||||
}
|
||||
set(newValue) {
|
||||
// Perform a suitable setting action here.
|
||||
}
|
||||
}
|
||||
>> }
|
||||
```
|
||||
-->
|
||||
|
||||
The type of `newValue` is the same as the return value of the subscript.
|
||||
As with computed properties, you can choose not to specify
|
||||
the setter's `(newValue)` parameter.
|
||||
A default parameter called `newValue` is provided to your setter
|
||||
if you don't provide one yourself.
|
||||
|
||||
As with read-only computed properties,
|
||||
you can simplify the declaration of a read-only subscript
|
||||
by removing the `get` keyword and its braces:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
// Return an appropriate subscript value here.
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `subscriptSyntax`
|
||||
|
||||
```swifttest
|
||||
>> class Test2 {
|
||||
-> subscript(index: Int) -> Int {
|
||||
// Return an appropriate subscript value here.
|
||||
>> return 1
|
||||
}
|
||||
>> }
|
||||
```
|
||||
-->
|
||||
|
||||
Here's an example of a read-only subscript implementation,
|
||||
which defines a `TimesTable` structure to represent an *n*-times-table of integers:
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
let threeTimesTable = TimesTable(multiplier: 3)
|
||||
print("six times three is \(threeTimesTable[6])")
|
||||
// Prints "six times three is 18".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `timesTable`
|
||||
|
||||
```swifttest
|
||||
-> struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
-> let threeTimesTable = TimesTable(multiplier: 3)
|
||||
-> print("six times three is \(threeTimesTable[6])")
|
||||
<- six times three is 18
|
||||
```
|
||||
-->
|
||||
|
||||
In this example, a new instance of `TimesTable` is created
|
||||
to represent the three-times-table.
|
||||
This is indicated by passing a value of `3` to the structure's `initializer`
|
||||
as the value to use for the instance's `multiplier` parameter.
|
||||
|
||||
You can query the `threeTimesTable` instance by calling its subscript,
|
||||
as shown in the call to `threeTimesTable[6]`.
|
||||
This requests the sixth entry in the three-times-table,
|
||||
which returns a value of `18`, or `3` times `6`.
|
||||
|
||||
> Note: An *n*-times-table is based on a fixed mathematical rule.
|
||||
> It isn't appropriate to set `threeTimesTable[someIndex]` to a new value,
|
||||
> and so the subscript for `TimesTable` is defined as a read-only subscript.
|
||||
|
||||
## Subscript Usage
|
||||
|
||||
The exact meaning of “subscript” depends on the context in which it's used.
|
||||
Subscripts are typically used as a shortcut for accessing
|
||||
the member elements in a collection, list, or sequence.
|
||||
You are free to implement subscripts in the most appropriate way for
|
||||
your particular class or structure's functionality.
|
||||
|
||||
For example, Swift's `Dictionary` type implements a subscript
|
||||
to set and retrieve the values stored in a `Dictionary` instance.
|
||||
You can set a value in a dictionary
|
||||
by providing a key of the dictionary's key type within subscript brackets,
|
||||
and assigning a value of the dictionary's value type to the subscript:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
numberOfLegs["bird"] = 2
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `dictionarySubscript`
|
||||
|
||||
```swifttest
|
||||
-> var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
-> numberOfLegs["bird"] = 2
|
||||
```
|
||||
-->
|
||||
|
||||
The example above defines a variable called `numberOfLegs`
|
||||
and initializes it with a dictionary literal containing three key-value pairs.
|
||||
The type of the `numberOfLegs` dictionary is inferred to be `[String: Int]`.
|
||||
After creating the dictionary,
|
||||
this example uses subscript assignment to add
|
||||
a `String` key of `"bird"` and an `Int` value of `2` to the dictionary.
|
||||
|
||||
For more information about `Dictionary` subscripting,
|
||||
see <doc:CollectionTypes#Accessing-and-Modifying-a-Dictionary>.
|
||||
|
||||
> Note: Swift's `Dictionary` type implements its key-value subscripting
|
||||
> as a subscript that takes and returns an *optional* type.
|
||||
> For the `numberOfLegs` dictionary above,
|
||||
> the key-value subscript takes and returns a value of type `Int?`,
|
||||
> or “optional int”.
|
||||
> The `Dictionary` type uses an optional subscript type to model the fact that
|
||||
> not every key will have a value, and to give a way to delete a value for a key
|
||||
> by assigning a `nil` value for that key.
|
||||
|
||||
## Subscript Options
|
||||
|
||||
Subscripts can take any number of input parameters,
|
||||
and these input parameters can be of any type.
|
||||
Subscripts can also return a value of any type.
|
||||
|
||||
Like functions,
|
||||
subscripts can take a varying number of parameters
|
||||
and provide default values for their parameters,
|
||||
as discussed in <doc:Functions#Variadic-Parameters>
|
||||
and <doc:Functions#Default-Parameter-Values>.
|
||||
However, unlike functions,
|
||||
subscripts can't use in-out parameters.
|
||||
|
||||
<!--
|
||||
- test: `subscripts-can-have-default-arguments`
|
||||
|
||||
```swifttest
|
||||
>> struct Subscriptable {
|
||||
>> subscript(x: Int, y: Int = 0) -> Int {
|
||||
>> return 100
|
||||
>> }
|
||||
>> }
|
||||
>> let s = Subscriptable()
|
||||
>> print(s[0])
|
||||
<< 100
|
||||
```
|
||||
-->
|
||||
|
||||
A class or structure can provide as many subscript implementations as it needs,
|
||||
and the appropriate subscript to be used will be inferred based on
|
||||
the types of the value or values that are contained within the subscript brackets
|
||||
at the point that the subscript is used.
|
||||
This definition of multiple subscripts is known as *subscript overloading*.
|
||||
|
||||
While it's most common for a subscript to take a single parameter,
|
||||
you can also define a subscript with multiple parameters
|
||||
if it's appropriate for your type.
|
||||
The following example defines a `Matrix` structure,
|
||||
which represents a two-dimensional matrix of `Double` values.
|
||||
The `Matrix` structure's subscript takes two integer parameters:
|
||||
|
||||
```swift
|
||||
struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: [Double]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(repeating: 0.0, count: rows * columns)
|
||||
}
|
||||
func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
subscript(row: Int, column: Int) -> Double {
|
||||
get {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
return grid[(row * columns) + column]
|
||||
}
|
||||
set {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
grid[(row * columns) + column] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `matrixSubscript, matrixSubscriptAssert`
|
||||
|
||||
```swifttest
|
||||
-> struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: [Double]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(repeating: 0.0, count: rows * columns)
|
||||
}
|
||||
func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
subscript(row: Int, column: Int) -> Double {
|
||||
get {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
return grid[(row * columns) + column]
|
||||
}
|
||||
set {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
grid[(row * columns) + column] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
`Matrix` provides an initializer that takes two parameters called `rows` and `columns`,
|
||||
and creates an array that's large enough to store `rows * columns` values of type `Double`.
|
||||
Each position in the matrix is given an initial value of `0.0`.
|
||||
To achieve this, the array's size, and an initial cell value of `0.0`,
|
||||
are passed to an array initializer that creates and initializes a new array of the correct size.
|
||||
This initializer is described in more detail
|
||||
in <doc:CollectionTypes#Creating-an-Array-with-a-Default-Value>.
|
||||
|
||||
You can construct a new `Matrix` instance by passing
|
||||
an appropriate row and column count to its initializer:
|
||||
|
||||
```swift
|
||||
var matrix = Matrix(rows: 2, columns: 2)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `matrixSubscript, matrixSubscriptAssert`
|
||||
|
||||
```swifttest
|
||||
-> var matrix = Matrix(rows: 2, columns: 2)
|
||||
>> assert(matrix.grid == [0.0, 0.0, 0.0, 0.0])
|
||||
```
|
||||
-->
|
||||
|
||||
The example above creates a new `Matrix` instance with two rows and two columns.
|
||||
The `grid` array for this `Matrix` instance
|
||||
is effectively a flattened version of the matrix,
|
||||
as read from top left to bottom right:
|
||||
|
||||

|
||||
|
||||
Values in the matrix can be set by passing row and column values into the subscript,
|
||||
separated by a comma:
|
||||
|
||||
```swift
|
||||
matrix[0, 1] = 1.5
|
||||
matrix[1, 0] = 3.2
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `matrixSubscript, matrixSubscriptAssert`
|
||||
|
||||
```swifttest
|
||||
-> matrix[0, 1] = 1.5
|
||||
>> print(matrix[0, 1])
|
||||
<< 1.5
|
||||
-> matrix[1, 0] = 3.2
|
||||
>> print(matrix[1, 0])
|
||||
<< 3.2
|
||||
```
|
||||
-->
|
||||
|
||||
These two statements call the subscript's setter to set
|
||||
a value of `1.5` in the top right position of the matrix
|
||||
(where `row` is `0` and `column` is `1`),
|
||||
and `3.2` in the bottom left position
|
||||
(where `row` is `1` and `column` is `0`):
|
||||
|
||||

|
||||
|
||||
The `Matrix` subscript's getter and setter both contain an assertion
|
||||
to check that the subscript's `row` and `column` values are valid.
|
||||
To assist with these assertions,
|
||||
`Matrix` includes a convenience method called `indexIsValid(row:column:)`,
|
||||
which checks whether the requested `row` and `column`
|
||||
are inside the bounds of the matrix:
|
||||
|
||||
```swift
|
||||
func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `matrixSubscript`
|
||||
|
||||
```swifttest
|
||||
>> var rows = 2
|
||||
>> var columns = 2
|
||||
-> func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
An assertion is triggered if you try to access a subscript
|
||||
that's outside of the matrix bounds:
|
||||
|
||||
```swift
|
||||
let someValue = matrix[2, 2]
|
||||
// This triggers an assert, because [2, 2] is outside of the matrix bounds.
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `matrixSubscriptAssert`
|
||||
|
||||
```swifttest
|
||||
-> let someValue = matrix[2, 2]
|
||||
xx assert
|
||||
// This triggers an assert, because [2, 2] is outside of the matrix bounds.
|
||||
```
|
||||
-->
|
||||
|
||||
## Type Subscripts
|
||||
|
||||
Instance subscripts, as described above,
|
||||
are subscripts that you call on an instance of a particular type.
|
||||
You can also define subscripts that are called on the type itself.
|
||||
This kind of subscript is called a *type subscript*.
|
||||
You indicate a type subscript
|
||||
by writing the `static` keyword before the `subscript` keyword.
|
||||
Classes can use the `class` keyword instead,
|
||||
to allow subclasses to override the superclass’s implementation of that subscript.
|
||||
The example below shows how you define and call a type subscript:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
static subscript(n: Int) -> Planet {
|
||||
return Planet(rawValue: n)!
|
||||
}
|
||||
}
|
||||
let mars = Planet[4]
|
||||
print(mars)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `static-subscript`
|
||||
|
||||
```swifttest
|
||||
-> enum Planet: Int {
|
||||
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
static subscript(n: Int) -> Planet {
|
||||
return Planet(rawValue: n)!
|
||||
}
|
||||
}
|
||||
-> let mars = Planet[4]
|
||||
>> assert(mars == Planet.mars)
|
||||
-> print(mars)
|
||||
<< mars
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
2298
skills/programming-swift/LanguageGuide/TheBasics.md
Normal file
2298
skills/programming-swift/LanguageGuide/TheBasics.md
Normal file
File diff suppressed because it is too large
Load Diff
533
skills/programming-swift/LanguageGuide/TypeCasting.md
Normal file
533
skills/programming-swift/LanguageGuide/TypeCasting.md
Normal file
@@ -0,0 +1,533 @@
|
||||
# Type Casting
|
||||
|
||||
Determine a value's runtime type and give it more specific type information.
|
||||
|
||||
*Type casting* is a way to check the type of an instance,
|
||||
or to treat that instance as a different
|
||||
superclass or subclass from somewhere else in its own class hierarchy.
|
||||
|
||||
Type casting in Swift is implemented with the `is` and `as` operators.
|
||||
These two operators provide a simple and expressive way
|
||||
to check the type of a value or cast a value to a different type.
|
||||
|
||||
You can also use type casting to check whether a type conforms to a protocol,
|
||||
as described in <doc:Protocols#Checking-for-Protocol-Conformance>.
|
||||
|
||||
## Defining a Class Hierarchy for Type Casting
|
||||
|
||||
You can use type casting with a hierarchy of classes and subclasses
|
||||
to check the type of a particular class instance
|
||||
and to cast that instance to another class within the same hierarchy.
|
||||
The three code snippets below define a hierarchy of classes
|
||||
and an array containing instances of those classes,
|
||||
for use in an example of type casting.
|
||||
|
||||
The first snippet defines a new base class called `MediaItem`.
|
||||
This class provides basic functionality for any kind of item that appears
|
||||
in a digital media library.
|
||||
Specifically, it declares a `name` property of type `String`,
|
||||
and an `init(name:)` initializer.
|
||||
(It's assumed that all media items, including all movies and songs, will have a name.)
|
||||
|
||||
```swift
|
||||
class MediaItem {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting, typeCasting-err`
|
||||
|
||||
```swifttest
|
||||
-> class MediaItem {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The next snippet defines two subclasses of `MediaItem`.
|
||||
The first subclass, `Movie`, encapsulates additional information about a movie or film.
|
||||
It adds a `director` property on top of the base `MediaItem` class,
|
||||
with a corresponding initializer.
|
||||
The second subclass, `Song`, adds an `artist` property and initializer
|
||||
on top of the base class:
|
||||
|
||||
```swift
|
||||
class Movie: MediaItem {
|
||||
var director: String
|
||||
init(name: String, director: String) {
|
||||
self.director = director
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
class Song: MediaItem {
|
||||
var artist: String
|
||||
init(name: String, artist: String) {
|
||||
self.artist = artist
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting, typeCasting-err`
|
||||
|
||||
```swifttest
|
||||
-> class Movie: MediaItem {
|
||||
var director: String
|
||||
init(name: String, director: String) {
|
||||
self.director = director
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
-> class Song: MediaItem {
|
||||
var artist: String
|
||||
init(name: String, artist: String) {
|
||||
self.artist = artist
|
||||
super.init(name: name)
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The final snippet creates a constant array called `library`,
|
||||
which contains two `Movie` instances and three `Song` instances.
|
||||
The type of the `library` array is inferred
|
||||
by initializing it with the contents of an array literal.
|
||||
Swift's type checker is able to deduce that `Movie` and `Song` have
|
||||
a common superclass of `MediaItem`,
|
||||
and so it infers a type of `[MediaItem]` for the `library` array:
|
||||
|
||||
```swift
|
||||
let library = [
|
||||
Movie(name: "Casablanca", director: "Michael Curtiz"),
|
||||
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
|
||||
Movie(name: "Citizen Kane", director: "Orson Welles"),
|
||||
Song(name: "The One And Only", artist: "Chesney Hawkes"),
|
||||
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
|
||||
]
|
||||
// the type of "library" is inferred to be [MediaItem]
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting`
|
||||
|
||||
```swifttest
|
||||
-> let library = [
|
||||
Movie(name: "Casablanca", director: "Michael Curtiz"),
|
||||
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
|
||||
Movie(name: "Citizen Kane", director: "Orson Welles"),
|
||||
Song(name: "The One And Only", artist: "Chesney Hawkes"),
|
||||
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
|
||||
]
|
||||
>> print(type(of: library))
|
||||
<< Array<MediaItem>
|
||||
// the type of "library" is inferred to be [MediaItem]
|
||||
```
|
||||
-->
|
||||
|
||||
The items stored in `library` are still `Movie` and `Song` instances behind the scenes.
|
||||
However, if you iterate over the contents of this array,
|
||||
the items you receive back are typed as `MediaItem`,
|
||||
and not as `Movie` or `Song`.
|
||||
In order to work with them as their native type,
|
||||
you need to *check* their type,
|
||||
or *downcast* them to a different type,
|
||||
as described below.
|
||||
|
||||
## Checking Type
|
||||
|
||||
Use the *type check operator* (`is`) to check
|
||||
whether an instance is of a certain subclass type.
|
||||
The type check operator returns `true` if the instance is of that subclass type
|
||||
and `false` if it's not.
|
||||
|
||||
The example below defines two variables, `movieCount` and `songCount`,
|
||||
which count the number of `Movie` and `Song` instances in the `library` array:
|
||||
|
||||
```swift
|
||||
var movieCount = 0
|
||||
var songCount = 0
|
||||
|
||||
for item in library {
|
||||
if item is Movie {
|
||||
movieCount += 1
|
||||
} else if item is Song {
|
||||
songCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
// Prints "Media library contains 2 movies and 3 songs".
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting`
|
||||
|
||||
```swifttest
|
||||
-> var movieCount = 0
|
||||
-> var songCount = 0
|
||||
|
||||
-> for item in library {
|
||||
if item is Movie {
|
||||
movieCount += 1
|
||||
} else if item is Song {
|
||||
songCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
-> print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
<- Media library contains 2 movies and 3 songs
|
||||
```
|
||||
-->
|
||||
|
||||
This example iterates through all items in the `library` array.
|
||||
On each pass, the `for`-`in` loop sets the `item` constant
|
||||
to the next `MediaItem` in the array.
|
||||
|
||||
`item is Movie` returns `true` if the current `MediaItem`
|
||||
is a `Movie` instance and `false` if it's not.
|
||||
Similarly, `item is Song` checks whether the item is a `Song` instance.
|
||||
At the end of the `for`-`in` loop, the values of `movieCount` and `songCount`
|
||||
contain a count of how many `MediaItem` instances were found of each type.
|
||||
|
||||
## Downcasting
|
||||
|
||||
A constant or variable of a certain class type may actually refer to
|
||||
an instance of a subclass behind the scenes.
|
||||
Where you believe this is the case,
|
||||
you can try to *downcast* to the subclass type
|
||||
with a *type cast operator* (`as?` or `as!`).
|
||||
|
||||
Because downcasting can fail,
|
||||
the type cast operator comes in two different forms.
|
||||
The conditional form, `as?`, returns an optional value of the type you are trying to downcast to.
|
||||
The forced form, `as!`, attempts the downcast and force-unwraps the result
|
||||
as a single compound action.
|
||||
|
||||
Use the conditional form of the type cast operator (`as?`)
|
||||
when you aren't sure if the downcast will succeed.
|
||||
This form of the operator will always return an optional value,
|
||||
and the value will be `nil` if the downcast was not possible.
|
||||
This enables you to check for a successful downcast.
|
||||
|
||||
Use the forced form of the type cast operator (`as!`)
|
||||
only when you are sure that the downcast will always succeed.
|
||||
This form of the operator will trigger a runtime error
|
||||
if you try to downcast to an incorrect class type.
|
||||
|
||||
The example below iterates over each `MediaItem` in `library`,
|
||||
and prints an appropriate description for each item.
|
||||
To do this, it needs to access each item as a true `Movie` or `Song`,
|
||||
and not just as a `MediaItem`.
|
||||
This is necessary in order for it to be able to access
|
||||
the `director` or `artist` property of a `Movie` or `Song`
|
||||
for use in the description.
|
||||
|
||||
In this example, each item in the array might be a `Movie`,
|
||||
or it might be a `Song`.
|
||||
You don't know in advance which actual class to use for each item,
|
||||
and so it's appropriate to use the conditional form of the type cast operator (`as?`)
|
||||
to check the downcast each time through the loop:
|
||||
|
||||
```swift
|
||||
for item in library {
|
||||
if let movie = item as? Movie {
|
||||
print("Movie: \(movie.name), dir. \(movie.director)")
|
||||
} else if let song = item as? Song {
|
||||
print("Song: \(song.name), by \(song.artist)")
|
||||
}
|
||||
}
|
||||
|
||||
// Movie: Casablanca, dir. Michael Curtiz
|
||||
// Song: Blue Suede Shoes, by Elvis Presley
|
||||
// Movie: Citizen Kane, dir. Orson Welles
|
||||
// Song: The One And Only, by Chesney Hawkes
|
||||
// Song: Never Gonna Give You Up, by Rick Astley
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting`
|
||||
|
||||
```swifttest
|
||||
-> for item in library {
|
||||
if let movie = item as? Movie {
|
||||
print("Movie: \(movie.name), dir. \(movie.director)")
|
||||
} else if let song = item as? Song {
|
||||
print("Song: \(song.name), by \(song.artist)")
|
||||
}
|
||||
}
|
||||
|
||||
</ Movie: Casablanca, dir. Michael Curtiz
|
||||
</ Song: Blue Suede Shoes, by Elvis Presley
|
||||
</ Movie: Citizen Kane, dir. Orson Welles
|
||||
</ Song: The One And Only, by Chesney Hawkes
|
||||
</ Song: Never Gonna Give You Up, by Rick Astley
|
||||
```
|
||||
-->
|
||||
|
||||
The example starts by trying to downcast the current `item` as a `Movie`.
|
||||
Because `item` is a `MediaItem` instance, it's possible that it *might* be a `Movie`;
|
||||
equally, it's also possible that it might be a `Song`,
|
||||
or even just a base `MediaItem`.
|
||||
Because of this uncertainty, the `as?` form of the type cast operator returns an *optional* value
|
||||
when attempting to downcast to a subclass type.
|
||||
The result of `item as? Movie` is of type `Movie?`, or “optional `Movie`”.
|
||||
|
||||
Downcasting to `Movie` fails when applied to
|
||||
the `Song` instances in the library array.
|
||||
To cope with this, the example above uses optional binding
|
||||
to check whether the optional `Movie` actually contains a value
|
||||
(that is, to find out whether the downcast succeeded.)
|
||||
This optional binding is written “`if let movie = item as? Movie`”,
|
||||
which can be read as:
|
||||
|
||||
“Try to access `item` as a `Movie`.
|
||||
If this is successful,
|
||||
set a new temporary constant called `movie` to
|
||||
the value stored in the returned optional `Movie`.”
|
||||
|
||||
If the downcasting succeeds, the properties of `movie` are then used
|
||||
to print a description for that `Movie` instance, including the name of its `director`.
|
||||
A similar principle is used to check for `Song` instances,
|
||||
and to print an appropriate description (including `artist` name)
|
||||
whenever a `Song` is found in the library.
|
||||
|
||||
> Note: Casting doesn't actually modify the instance or change its values.
|
||||
> The underlying instance remains the same; it's simply treated and accessed
|
||||
> as an instance of the type to which it has been cast.
|
||||
|
||||
<!--
|
||||
TODO: This example should be followed by the same example written with switch,
|
||||
to introduce type casting in a pattern matching context
|
||||
and to set up the crazy Any example at the end of the chapter.
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: No section on upcasting because nobody can come up with
|
||||
an example that isn't excessively contrived.
|
||||
The reference shows the behavior in a contrived example.
|
||||
-->
|
||||
|
||||
## Type Casting for Any and AnyObject
|
||||
|
||||
Swift provides two special types for working with nonspecific types:
|
||||
|
||||
- `Any` can represent an instance of any type at all, including function types.
|
||||
- `AnyObject` can represent an instance of any class type.
|
||||
|
||||
Use `Any` and `AnyObject` only when you explicitly need
|
||||
the behavior and capabilities they provide.
|
||||
It's always better to be specific about the types you expect to work with in your code.
|
||||
|
||||
Here's an example of using `Any` to work with a mix of different types,
|
||||
including function types and nonclass types.
|
||||
The example creates an array called `things`, which can store values of type `Any`:
|
||||
|
||||
```swift
|
||||
var things: [Any] = []
|
||||
|
||||
things.append(0)
|
||||
things.append(0.0)
|
||||
things.append(42)
|
||||
things.append(3.14159)
|
||||
things.append("hello")
|
||||
things.append((3.0, 5.0))
|
||||
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
|
||||
things.append({ (name: String) -> String in "Hello, \(name)" })
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting, typeCasting-err`
|
||||
|
||||
```swifttest
|
||||
-> var things: [Any] = []
|
||||
|
||||
-> things.append(0)
|
||||
-> things.append(0.0)
|
||||
-> things.append(42)
|
||||
-> things.append(3.14159)
|
||||
-> things.append("hello")
|
||||
-> things.append((3.0, 5.0))
|
||||
-> things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
|
||||
-> things.append({ (name: String) -> String in "Hello, \(name)" })
|
||||
```
|
||||
-->
|
||||
|
||||
The `things` array contains
|
||||
two `Int` values, two `Double` values, a `String` value,
|
||||
a tuple of type `(Double, Double)`,
|
||||
the movie “Ghostbusters”,
|
||||
and a closure expression that takes a `String` value
|
||||
and returns another `String` value.
|
||||
|
||||
To discover the specific type of a constant or variable
|
||||
that's known only to be of type `Any` or `AnyObject`,
|
||||
you can use an `is` or `as` pattern in a `switch` statement's cases.
|
||||
The example below iterates over the items in the `things` array
|
||||
and queries the type of each item with a `switch` statement.
|
||||
Several of the `switch` statement's cases bind their matched value to
|
||||
a constant of the specified type to enable its value to be printed:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
switch thing {
|
||||
case 0 as Int:
|
||||
print("zero as an Int")
|
||||
case 0 as Double:
|
||||
print("zero as a Double")
|
||||
case let someInt as Int:
|
||||
print("an integer value of \(someInt)")
|
||||
case let someDouble as Double where someDouble > 0:
|
||||
print("a positive double value of \(someDouble)")
|
||||
case is Double:
|
||||
print("some other double value that I don't want to print")
|
||||
case let someString as String:
|
||||
print("a string value of \"\(someString)\"")
|
||||
case let (x, y) as (Double, Double):
|
||||
print("an (x, y) point at \(x), \(y)")
|
||||
case let movie as Movie:
|
||||
print("a movie called \(movie.name), dir. \(movie.director)")
|
||||
case let stringConverter as (String) -> String:
|
||||
print(stringConverter("Michael"))
|
||||
default:
|
||||
print("something else")
|
||||
}
|
||||
}
|
||||
|
||||
// zero as an Int
|
||||
// zero as a Double
|
||||
// an integer value of 42
|
||||
// a positive double value of 3.14159
|
||||
// a string value of "hello"
|
||||
// an (x, y) point at 3.0, 5.0
|
||||
// a movie called Ghostbusters, dir. Ivan Reitman
|
||||
// Hello, Michael
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting`
|
||||
|
||||
```swifttest
|
||||
-> for thing in things {
|
||||
switch thing {
|
||||
case 0 as Int:
|
||||
print("zero as an Int")
|
||||
case 0 as Double:
|
||||
print("zero as a Double")
|
||||
case let someInt as Int:
|
||||
print("an integer value of \(someInt)")
|
||||
case let someDouble as Double where someDouble > 0:
|
||||
print("a positive double value of \(someDouble)")
|
||||
case is Double:
|
||||
print("some other double value that I don't want to print")
|
||||
case let someString as String:
|
||||
print("a string value of \"\(someString)\"")
|
||||
case let (x, y) as (Double, Double):
|
||||
print("an (x, y) point at \(x), \(y)")
|
||||
case let movie as Movie:
|
||||
print("a movie called \(movie.name), dir. \(movie.director)")
|
||||
case let stringConverter as (String) -> String:
|
||||
print(stringConverter("Michael"))
|
||||
default:
|
||||
print("something else")
|
||||
}
|
||||
}
|
||||
|
||||
</ zero as an Int
|
||||
</ zero as a Double
|
||||
</ an integer value of 42
|
||||
</ a positive double value of 3.14159
|
||||
</ a string value of "hello"
|
||||
</ an (x, y) point at 3.0, 5.0
|
||||
</ a movie called Ghostbusters, dir. Ivan Reitman
|
||||
</ Hello, Michael
|
||||
```
|
||||
-->
|
||||
|
||||
> Note: The `Any` type represents values of any type, including optional types.
|
||||
> Swift gives you a warning if you use an optional value
|
||||
> where a value of type `Any` is expected.
|
||||
> If you really do need to use an optional value as an `Any` value,
|
||||
> you can use the `as` operator to explicitly cast the optional to `Any`,
|
||||
> as shown below.
|
||||
>
|
||||
> ```swift
|
||||
> let optionalNumber: Int? = 3
|
||||
> things.append(optionalNumber) // Warning
|
||||
> things.append(optionalNumber as Any) // No warning
|
||||
> ```
|
||||
|
||||
<!--
|
||||
- test: `typeCasting-err`
|
||||
|
||||
```swifttest
|
||||
-> let optionalNumber: Int? = 3
|
||||
-> things.append(optionalNumber) // Warning
|
||||
!$ warning: expression implicitly coerced from 'Int?' to 'Any'
|
||||
!! things.append(optionalNumber) // Warning
|
||||
!! ^~~~~~~~~~~~~~
|
||||
!$ note: provide a default value to avoid this warning
|
||||
!! things.append(optionalNumber) // Warning
|
||||
!! ^~~~~~~~~~~~~~
|
||||
!! ?? <#default value#>
|
||||
!$ note: force-unwrap the value to avoid this warning
|
||||
!! things.append(optionalNumber) // Warning
|
||||
!! ^~~~~~~~~~~~~~
|
||||
!! !
|
||||
!$ note: explicitly cast to 'Any' with 'as Any' to silence this warning
|
||||
!! things.append(optionalNumber) // Warning
|
||||
!! ^~~~~~~~~~~~~~
|
||||
!! as Any
|
||||
-> things.append(optionalNumber as Any) // No warning
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
Rejected examples to illustrate AnyObject:
|
||||
|
||||
Array of delegates which may conform to one or more of the class's delegate protocols.
|
||||
|
||||
```
|
||||
protocol MovieDelegate {
|
||||
func willPlay(movie: Movie)
|
||||
}
|
||||
|
||||
class Library {
|
||||
var delegates = [AnyObject]
|
||||
...
|
||||
}
|
||||
|
||||
for delegate in delegates {
|
||||
guard let delegate = delegate as MovieDelegate else { continue }
|
||||
delegate.willPlay(movie: m)
|
||||
}
|
||||
```
|
||||
|
||||
A userData object for associating some opaque piece of data or state with an API call.
|
||||
|
||||
```
|
||||
class C {
|
||||
// Not userInfo -- that's usually a Dictionary
|
||||
let userData: AnyObject? // In Cocoa APIs, userData is a void*
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
Reference in New Issue
Block a user