An Introduction to Swift Structures and Enumerations

Having covered Swift classes in the preceding chapters, this chapter will introduce the use of structures in Swift. Although at first glance structures and classes look similar, there are some important differences that need to be understood when deciding which to use. This chapter will outline how to declare and use structures, explore the differences between structures and classes and introduce the concepts of value and reference types.

An Overview of Swift Structures

As with classes, structures form the basis of object-oriented programming and provide a way to encapsulate data and functionality into re-usable instances. Structure declarations resemble classes with the exception that the struct keyword is used in place of the class keyword. The following code, for example, declares a simple structure consisting of a String variable, initializer and method:

struct SampleStruct {
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func buildHelloMsg() {
        "Hello " + name
    }
}

Consider the above structure declaration in comparison to the equivalent class declaration:

class SampleClass {
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func buildHelloMsg() {
        "Hello " + name
    }
}

Other than the use of the struct keyword instead of class, the two declarations are identical. Instances of each type are also created using the same syntax:

let myStruct = SampleStruct(name: "Mark")
let myClass = SampleClass(name: "Mark")

In common with classes, structures may be extended and are also able to adopt protocols and contain initializers.

Given the commonality between classes and structures, it is important to gain an understanding of how the two differ. Before exploring the most significant difference it is first necessary to understand the concepts of value types and reference types.

Value Types vs. Reference Types

While on the surface structures and classes look alike, major differences in behavior occur when structure and class instances are copied or passed as arguments to methods or functions. This occurs because structure instances are value type while class instances are reference type.

When a structure instance is copied or passed to a method, an actual copy of the instance is created, together with any data contained within the instance. This means that the copy has its own version of the data which is unconnected with the original structure instance. In effect, this means that there can be multiple copies of a structure instance within a running app, each with its own local copy of the associated data. A change to one instance has no impact on any other instances.

In contrast, when a class instance is copied or passed as an argument, the only thing duplicated or passed is a reference to the location in memory where that class instance resides. Any changes made to the instance using those references will be performed on the same instance. In other words, there is only one class instance but multiple references pointing to it. A change to the instance data using any one of those references changes the data for all other references.

To demonstrate reference and value types in action, consider the following code:

struct SampleStruct {
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func buildHelloMsg() {
        "Hello " + name
    }
}
 
let myStruct1 = SampleStruct(name: "Mark")
print(myStruct1.name) 

When the code executes, the name “Mark” will be displayed. Now change the code so that a copy of the myStruct1 instance is made, the name property changed and the names from each instance displayed:

let myStruct1 = SampleStruct(name: "Mark")
var myStruct2 = myStruct1
myStruct2.name = "David"
 
print(myStruct1.name)
print(myStruct2.name)

When executed, the output will read as follows:

Mark
David

Clearly, the change of name only applied to myStruct2 since this is an actual copy of myStruct1 containing its own copy of the data as shown in Figure 12-1:

Figure 12-1

Contrast this with the following class example:

class SampleClass {
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func buildHelloMsg() {
        "Hello " + name
    }
}
 
let myClass1 = SampleClass(name: "Mark")
var myClass2 = myClass1
myClass2.name = "David"
 
print(myClass1.name)
print(myClass2.name)

When this code executes, the following output will be generated:

David
David

In this case, the name property change is reflected for both myClass1 and myClass2 because both are references pointing to the same class instance as illustrated in Figure 12-2 below:

Figure 12-2

In addition to these value and reference type differences, structures do not support inheritance and sub-classing in the way that classes do. In other words, it is not possible for one structure to inherit from another structure. Unlike classes, structures also cannot contain a de-initializer (deinit) method. Finally, while it is possible to identify the type of a class instance at runtime, the same is not true of a struct.

When to Use Structures or Classes

In general, structures are recommended whenever possible because they are both more efficient than classes and safer to use in multi-threaded code. Classes should be used when inheritance is needed, only one instance of the encapsulated data is required, or extra steps need to be taken to free up resources when an instance is de-initialized.

An Overview of Enumerations

Enumerations (typically referred to as enums) are used to create custom data types consisting of pre-defined sets of values. Enums are typically used for making decisions within code such as when using switch statements. An enum might, for example be declared as follows:

enum Temperature {
    case hot
    case warm
    case cold
}

Note that in this example, none of the cases are assigned a value. An enum of this type is essentially used to reference one of a pre-defined set of states (in this case the current temperature being hot, warm or cold). Once declared, the enum may, for example, be used within a switch statement as follows:

func displayTempInfo(temp: Temperature) {
    switch temp {
        case .hot:
            print("It is hot.")
        case .warm:
            print("It is warm.")
        case .cold:
            print("It is cold.")
    }
}

It is also worth noting that because an enum has a definitive set of valid member values, the switch statement does not need to include a default case. An attempt to pass an invalid enum case through the switch will be An Introduction to Swift Structures and Enumerations caught by the compiler long before it has a chance to cause a runtime error.

To test out the enum, the displayTempInfo() function must be passed an instance of the Temperature enum with one of the following three possible states selected:

Temperature.hot
Temperature.warm
Temperature.cold

For example:

displayTempInfo(temp: Temperature.warm)

When executed, the above function call will output the following information:

It is warm.

Individual cases within an enum may also have associated values. Assume, for example, that the “cold” enum case needs to have associated with it a temperature value so that the app can differentiate between cold and freezing conditions. This can be defined within the enum declaration as follows:

enum Temperature {
    case hot
    case warm
    case cold(centigrade: Int)
}

This allows the switch statement to also check for the temperature for the cold case as follows:

func displayTempInfo(temp: Temperature) {
    switch temp {
        case .hot:
            print("It is hot")
        case .warm:
            print("It is warm")
        case.cold(let centigrade) where centigrade <= 0:
            print("Ice warning: \(centigrade) degrees.")
        case .cold:
            print("It is cold but not freezing.")
    }
}

When the cold enum value is passed to the function, it now does so with a temperature value included:

displayTempInfo(temp: Temperature.cold(centigrade: -10))

The output from the above function all will read as follows:

Ice warning: -10 degrees

Summary

Swift structures and classes both provide a mechanism for creating instances that define properties, store values and define methods. Although the two mechanisms appear to be similar, there are significant behavioral differences when structure and class instances are either copied or passed to a method. Classes are categorized as being reference type instances while structures are value type. When a structure instance is copied or passed, an entirely new copy of the instance is created containing its own data. Class instances, on the other hand, are passed and copied by reference, with each reference pointing to the same class instance. Other features unique to classes include support for inheritance and deinitialization and the ability to identify the class type at runtime. Structures should typically be used in place of classes unless specific class features are required.

Enumerations are used to create custom types consisting of a pre-defined set of state values and are of particular use in identifying state within switch statements.

SwiftUI Essentials – iOS 14 Edition

  1. An Introduction to Swift Structures
  2. An Introduction to Swift Property Wrappers
  3. An Overview of SwiftUI
  4. Using Xcode in SwiftUI Mode
  5. SwiftUI Architecture
  6. The Anatomy of a Basic SwiftUI Project
  7. Creating Custom Views with SwiftUI
  8. SwiftUI Stacks and Frames
  9. SwiftUI Lifecycle Event Modifiers
  10. Working with SwiftUI State, Observable and Environment Objects
  11. A SwiftUI Example Tutorial
  12. SwiftUI Observable and Environment Objects – A Tutorial
  13. SwiftUI Data Persistence using AppStorage and SceneStorage
  14. SwiftUI Stack Alignment and Alignment Guides
  15. SwiftUI Lists and Navigation
  16. A SwiftUI List and Navigation Tutorial
  17. An Overview of SwiftUI List, OutlineGroup and DisclosureGroup
  18. A SwiftUI List, OutlineGroup and DisclosureGroup Tutorial
  19. Building SwiftUI Grids with LazyVGrid and LazyHGrid
  20. Building Tabbed Views in SwiftUI
  21. Building Context Menus in SwiftUI
  22. Basic SwiftUI Graphics Drawing
  23. SwiftUI Animation and Transitions
  24. Working with Gesture Recognizers in SwiftUI
  25. Creating a Customized SwiftUI ProgressView
  26. An Overview of SwiftUI DocumentGroup Scenes
  27. A SwiftUI DocumentGroup Tutorial
  28. An Introduction to SwiftUI and SiriKit
  29. A SwiftUI SiriKit Tutorial
  30. Customizing the SiriKit Intent User Interface
  31. A SwiftUI SiriKit NSUserActivity Tutorial
  32. An Overview of SwiftUI Siri Shortcut Integration
  33. A SwiftUI Siri Shortcut Tutorial
  34. Building Widgets with SwiftUI and WidgetKit
  35. A SwiftUI WidgetKit Tutorial
  36. Supporting WidgetKit Size Families in SwiftUI
  37. A SwiftUI WidgetKit Deep Link Tutorial
  38. Adding Configuration Options to a WidgetKit Widget
  39. Integrating UIViews with SwiftUI
  40. Integrating UIViewControllers with SwiftUI
  41. Integrating SwiftUI with UIKit