Files
gh-kylehughes-the-unofficia…/skills/programming-swift/LanguageGuide/ClassesAndStructures.md
2025-11-30 08:36:15 +08:00

722 lines
22 KiB
Markdown

# 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:
![](sharedStateStruct)
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:
![](sharedStateClass)
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
-->