Structures and Classes

Version: Swift 5.5 Source: swift-book: Structures and Classes Digest Date: January 16, 2022

Structures and classes are general-purpose, flexible constructs that become the building blocks of your program’s code.

Unlike other programming languages, Swift doesn’t require you to create separate interface (.h) and implementation (.m) 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

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.

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 data types you define will be structures and enumerations. For a more detailed comparison, see Choosing Between Structures and Classes.

NOTE: Classes and actors share many of the same characteristics and behaviors. For information about actors, see Concurrency.

Definition Syntax

Structures and classes have a similar definition syntax.

struct SomeStructure {
    // structure definition goes here
}
class SomeClass {
    // class definition goes here
}

Here’s an example of a structure definition and a class definition:

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

Structure and Class Instances

The syntax for creating instances is very similar for both structures and classes:

let someResolution = Resolution()
let someVideoMode = VideoMode()

Accessing Properties

You can access the properties of an instance using dot syntax.

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

You can drill down into subproperties, such as the width property in the resolution property of a VideoMode:

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"

You can also use dot syntax to assign a new value to a variable property:

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "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.

let vga = Resolution(width: 640, height: 480)

Unlike structures, class instances don’t receive a default memberwise initializer.

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.

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.

NOTE (copy on write):

Collections defined by the 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:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

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):

cinema.width = 2048

Checking the width property of cinema shows that it has indeed changed to be 2048:

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

However, the width property of the original hd instance still has the old value of 1920:

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

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:

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"

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:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

Next, tenEighty is assigned to a new constant, called alsoTenEighty, and the frame rate of alsoTenEighty is modified:

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:

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

Identity Operators

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:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

Note that identical to (represented by three equals signs, or ===) doesn’t mean the same thing as equal to (represented by two equals 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 Equivalence Operators.

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 standard library provides pointer and buffer types that you can use if you need to interact with pointers directly—see Manual Memory Management.

Last updated

Was this helpful?