classes and structs are a common and flexible construct that people use to build code. We can extend the functionality of classes and structs by using exactly the same syntax rules to define properties (constants, variables) and methods for classes and struct bodies.
Unlike other programming languages, Swift does not require you to create separate interfaces and implementation files for your custom classes and structs. All you have to do is define a class or struct in a single file, and the system will automatically generate external interfaces for other code.
Attention
Typically an 类
instance of a is called 对象
. However, in Swift, classes and structs are more closely related than in other languages, and most of the features discussed in this chapter can be used on classes and structs. Therefore, we will mainly use 实例
rather than 对象
.
Class and struct comparison
Classes and structs in Swift have a lot in common. The Common place is:
- Defining properties for storing values
- Define methods to provide functionality
- Defining satellite scripts for accessing values
- Defining constructors for generating initialization values
- Extended to add functionality to the default implementation
- Implement protocols to provide some standard functionality
For more information, see Properties, methods, subscript scripts, construction procedures, extensions, and protocols.
Compared to structs, classes have the following additional functions:
- Inheritance allows one class to inherit the characteristics of another class
- Type conversions allow you to check and interpret the type of a class instance at run time
- The destructor allows an instance of a class to release any resources it has been assigned
- Reference count allows multiple references to a class
For more information, see Inheritance, type conversions, destructors, and automatic reference counting.
Attention
Structs are passed in the code in a copied way, without using reference counting.
Definition syntax
Classes and structs are defined in a similar way. We class
struct
represent classes and structs separately by keyword and, and define their specifics in a pair of curly braces:
class SomeClass { // class definition goes here}struct SomeStructure { // structure definition goes here}
Attention
Each time you define a new class or struct, you are actually defining a new Swift type. So use UpperCamelCase
this method to name (such as SomeClass
and SomeStructure
so on) in order to conform to the uppercase naming style of the standard Swift type ( String
for example, Int
and Bool
). Instead, use lowerCamelCase
this method to name properties and methods (such as framerate
and incrementCount
) so that they are distinguished from the type name.
The following is an example of defining a struct and defining a class:
struct Resolution { var width = 0 var height = 0}class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String?}
In the example above, we define a Resolution
struct named structure to describe the pixel resolution of a display. This struct contains two width
height
storage properties named and. A stored property is a constant or variable that is bundled and stored in a class or struct. When these two properties are initialized to integers 0
, they are inferred as Int
types.
In the example above, we also defined a class named to VideoMode
describe a particular pattern of a video display. This class contains four variable storage properties. The first is 分辨率
that it is initialized to an instance of a new Resolution
struct, and the attribute type is inferred Resolution
. The new instance also initializes the other three properties, namely, the initial value, the initial value, VideoMode
false
and the value is interlaced
0.0
frameRate
optional String
name
. The name
property is automatically assigned a default value, nil
meaning "no name
value" because it is an optional type.
Classes and struct-body instances
Resolution
VideoMode
the definitions of structs and classes describe only what is Resolution
and VideoMode
. They do not describe a specific resolution (resolution) or video mode. To describe a particular resolution or video pattern, we need to generate an instance of them.
The syntax for generating struct and class instances is very similar:
let someResolution = Resolution()let someVideoMode = VideoMode()
Both structs and classes use the constructor syntax to generate new instances. The simplest form of constructor syntax is to follow a pair of empty parentheses, such as or, after the type name of the struct or class Resolution()
VideoMode()
. The properties of the class or struct instance created in this way are initialized to the default values. The construction process chapters discuss the initialization of classes and structs in more detail.
Property access
By using dot syntax (dot Syntax), you can access the properties of an instance. The syntax rule is that the instance name is followed by the property name, and both are connected by a dot ( .
):
print("The width of someResolution is \(someResolution.width)")// 输出 "The width of someResolution is 0"
In the example above, the someResolution.width
referenced someResolution
width
property returns width
the initial value 0
.
You can also access child properties, such as properties in the properties VideoMode
Resolution
width
:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")// 输出 "The width of someVideoMode is 0"
You can also assign values to variable attributes using DOT syntax:
1280print("The width of someVideoMode is now \(someVideoMode.resolution.width)")// 输出 "The width of someVideoMode is now 1280"
Attention
Unlike the Objective-c language, Swift allows you to directly set child properties of a struct property. The last example above is to set the sub- someVideoMode
resolution
property of the property directly width
, which does not require the new value to be set again for the entire resolution
property.
struct-type member-by-Constructor (memberwise initializers for Structure Types)
All structs have an automatically generated member-by- constructor that initializes the properties of members in the new struct instance. The initial values for each property in the new instance can be passed to the member-by-constructor by the name of the property:
let vga = Resolution(width:640, height: 480)
Unlike structs, class instances do not have a default member-by-constructor. The constructor is discussed in more detail in the Construction process section.
Structs and enumerations are value types
When a value type is assigned to a variable, constant, or passed to a function, its value is copied .
In the previous chapters, we have used a lot of value types. In fact, in Swift, all the basic types: integers, floating-point numbers (floating-point), Boolean (Boolean), Strings (string), arrays (array), and dictionaries (dictionary) are all value types. And at the bottom are implemented in the form of structs.
In Swift, all structs and enum types are value types. This means that their instances, as well as any value type attributes contained in the instance, are copied when passed in the code.
Take a look at the following example, which uses the struct body in the previous example Resolution
:
let hd = Resolution(width: 1920, height: 1080)var cinema = hd
In the example above, a constant named is declared hd
, and its value is an instance initialized to full HD video resolution ( 1920
pixel width, 1080
high pixel) Resolution
.
The example then declares a cinema
variable named and hd
assigns it to it. Because Resolution
it is a struct, cinema
the value is actually hd
a copy copy, not hd
itself. Although hd
cinema
they have the same width (width) and height (height), they are two completely different instances behind the scenes.
Below, in order to meet the demand for digital cinema screenings ( 2048
pixel width, 1080
high pixel), cinema
the width
properties need to be modified as follows:
cinema.width = 2048
Here, the property that will be displayed cinema
width
has been changed to 2048
:
print("cinema is now \(cinema.width) pixels wide")// 输出 "cinema is now 2048 pixels wide"
However, the hd
attributes in the initial instance width
are 1920
:
print("hd is still \(hd.width) pixels wide")// 输出 "hd is still 1920 pixels wide"
When it hd
is given cinema
, it is actually copying the hd
values stored in and then storing the copied data in the new cinema
instance. The result is that two completely independent instances happen to contain the same values. Because they are independent of each other, they are cinema
width
modified to 2048
not affect the hd
width
values in.
Enumerations also follow the same Code of conduct:
enum CompassPoint { case North, South, East, West}var currentDirection = CompassPoint.Westlet rememberedDirection = currentDirectioncurrentDirection = .Eastif rememberedDirection == .West { print("The remembered direction is still .West")}// 输出 "The remembered direction is still .West"
The value given in the above example rememberedDirection
currentDirection
is actually given a copy of the value. Values that are modified at the end of the assignment process currentDirection
do not affect the copy of the rememberedDirection
stored original value.
Class is a reference type
Unlike value types, a reference type is assigned to a variable, constant, or passed to a function, and its value is not copied. Therefore, the reference is made to the existing instance itself, not to its copy.
Take a look at the following example, which uses the class that was previously defined VideoMode
:
let tenEighty = VideoMode()tenEighty.resolution = hdtenEighty.interlaced = truetenEighty.name = "1080i"tenEighty.frameRate = 25.0
In the example above, a tenEighty
constant named, which references a VideoMode
new instance of a class, is declared. In the previous example, the video mode was given a copy of the HD resolution ( 1920
* 1080
) (i.e., an hd
instance). Also set to interlaced
, named “1080i”
. Finally, its frame rate is 25.0
frames per second.
Then, it tenEighty
is given a alsoTenEighty
new constant named, and the alsoTenEighty
frame rate is modified:
let alsoTenEighty = tenEightyalsoTenEighty.frameRate = 30.0
Because a class is a reference type tenEight
, alsoTenEight
it is the same instance that is actually referenced VideoMode
. In other words, they are two kinds of names for the same instance.
Below, by looking tenEighty
at the frameRate
properties, we will find that it correctly shows the VideoMode
new frame rate of the referenced instance, with the value 30.0
:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")// 输出 "The frameRate property of theEighty is now 30.0"
It is important to note that it is tenEighty
alsoTenEighty
declared as a constant instead of as a variable. However you can still change tenEighty.frameRate
and alsoTenEighty.frameRate
because the tenEighty
alsoTenEighty
values of these two constants have not changed. They do not "store" this VideoMode
instance, but simply refer to the VideoMode
instance. So, the value of the referenced property is changed instead of the VideoMode
frameRate
reference VideoMode
constant.
Identity operator
Because a class is a reference type, there may be multiple constants and variables that reference the same class instance at the same time behind the scenes. (This is not true for structs and enumerations.) Because they are value types, their values are always copied when assigned to a constant, variable, or passed to a function. )
It would be helpful to be able to determine whether two constants or a variable references the same class instance. In order to achieve this, Swift has built two identity operators:
- Equivalent to (
===
)
- The price is equal to (
!==
)
Use these two operators to detect whether two constants or variables refer to the same instance:
if tenEighty === alsoTenEighty { print("tenEighty and alsoTenEighty refer to the same Resolution instance.")}//输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
Note that "equivalent to" (denoted by three equals ===
) differs from "equals" (denoted by two equals ==
):
- A constant or variable that is equivalent to a class type representing two classes is referring to the same class instance.
- "Equals" means that the value of two instances is "equal" or "identical" and is judged to conform to the criteria defined by the designer, so this is a more appropriate term relative to "equality".
When you define your custom classes and structs, you are obligated to decide on the criteria for determining the "equality" of two instances. The process of implementing custom "equals" and "not equals" operators is described in detail in the chapter equivalence operator.
Pointer
If you have experience with c,c++ or objective-c language, then you might know that these languages use pointers to refer to addresses in memory. A Swift constant or variable that references an instance of a reference type, similar to a pointer in the C language, but does not point directly to a memory address, nor does it require you to use an asterisk ( *
) to indicate that you are creating a reference. These references in Swift are defined in the same way as other constants or variables.
Selection of classes and structure bodies
In your code, you can use classes and structs to define your custom data types.
However, struct instances are always passed by value, and class instances are always passed by reference. This means that the two apply different tasks. When you consider the data structure and function of a project, you need to decide whether each data structure is defined as a class or a struct.
As a general guideline, consider building a struct when one or more of the following conditions are met:
- The main purpose of this data structure is to encapsulate a small number of related simple data values.
- It is reasonable to expect that when an instance of the data structure is assigned or passed, the encapsulated data will be copied instead of referenced.
- The value type attribute stored in the data structure should also be copied, not referenced.
- The data structure does not need to inherit another property or behavior of an existing type.
For example, the following scenarios are appropriate for the use of structs:
- The size of the geometry, encapsulating an
width
attribute and a height
property, both Double
types.
- A path within a range that encapsulates a
start
property and length
attribute, both of which are Int
types.
- In a three-dimensional coordinate system, a point, package
x
, y
and z
attribute, all of them are Double
types.
In all other cases, define a class, generate an instance of it, and manage and pass it by reference. In practice, this means that the vast majority of custom data constructs should be classes, not structs.
Assignment of String (string), array, and dictionary (Dictionary) types to replication behavior
In Swift, many basic types, such as String
, Array
and types, Dictionary
are implemented in the form of structs. This means that when assigned to a new constant or variable, or passed into a function or method, their values are copied.
Objective-c NSString
, NSArray
and NSDictionary
types are implemented in the form of classes, not structs. When they are assigned or passed into a function or method, no value copy occurs, but the reference to the existing instance is passed.
Attention
The above is a description of the "copy" behavior of strings, arrays, and dictionaries. In your code, the copy behavior seems to always happen. However, Swift performs the actual copy behind the scenes only when absolutely necessary. Swift manages all of the value copies to ensure performance optimization, so you don't have to avoid assigning values to ensure performance optimization.
Attack on Swift-------------class and structure