Skip to main content
April 1, 2026Noble Desktop Publishing Team/9 min read

Enumerations With Functions

Master Swift Enumerations with Functions and Classes

Core Concepts You'll Learn

Enumeration Basics

Create and use enums within classes to define specific types and states. Learn how enumerations provide type safety and structure to your iOS applications.

Function Integration

Add functions to classes that manipulate enum values. Understand how to create methods that work seamlessly with enumerated types.

Memory Management

Explore weak references and property observers. Learn how Swift handles memory allocation and prevents reference cycles in class relationships.

Topics Covered in This iOS Development Tutorial:

Creating & Using Enumerations, Adding Functions, Instantiating a Class

Exercise Overview

In this exercise, we'll explore advanced enumeration concepts by demonstrating how they integrate seamlessly with classes and methods to enforce type safety. You'll master complex property patterns including computed properties, dive deep into memory management with weak references, and understand how Swift's automatic reference counting prevents memory leaks in real-world applications.

Exercise Workflow

1

Environment Setup

Launch Xcode and create a new Playground file for hands-on coding practice

2

Enum Creation

Build a MaritalStatus enumeration within a Person class structure

3

Property Implementation

Add computed properties, weak references, and property observers

4

Function Development

Create marry and divorce functions that manipulate enum states

5

Testing & Validation

Test the implementation with multiple Person instances and verify functionality

Getting Started

  1. Launch Xcode if it isn't already open.

  2. Navigate to File > New > Playground.

  3. Under iOS, double–click on Blank.

  4. Navigate into Desktop > Class Files > yourname-iOS App Dev 1 Class.

  5. Save the file as: Enums.playground

  6. Click Create.

Xcode Playground Benefits

Using Xcode Playground allows for immediate code execution and testing. You can see results in real-time as you build your enumeration and function implementations.

Setup Requirements

0/4

Creating & Using Enumerations

Enumerations are fundamental to writing maintainable Swift code. By encapsulating related values within a single type, they eliminate the possibility of invalid states and make your code self-documenting. Let's build a practical example that showcases their power.

  1. In your new Playground file, delete all the existing code.

  2. Create a new class that contains an enum called MaritalStatus, as shown below in bold:

    class Person {
    
       enum MaritalStatus {
    
       }
    
    }
  3. Add the following cases to the MaritalStatus enum:

    enum MaritalStatus {
       case single
       case married
       case divorced
       case complicated
    }

    By nesting this MaritalStatus enum inside the Person class, we create a logical namespace. This pattern is particularly valuable in larger codebases where you need to avoid naming conflicts while maintaining clear semantic relationships.

  4. Now we'll add properties to the Person class. Below the enumeration, add these familiar variables:

    enum MaritalStatus {
       case single
       case married
       case divorced
       case complicated
    }
    
    var name: String
    var age: Int

    Don't worry about the red error for now—we'll initialize these properties shortly to resolve it.

  5. Add the maritalStatus property shown in bold below:

    var name: String
    var age: Int
    var maritalStatus: MaritalStatus

    By defining this property with our custom MaritalStatus type, we guarantee compile-time safety. The property can only ever hold one of our four defined cases, making invalid states impossible.

  6. Next, we'll create an optional partner variable with sophisticated logic that adapts based on relationship status:

    var name: String
    var age: Int
    var maritalStatus: MaritalStatus
    var partner: String? { return }

    This is a computed property, indicated by the return statement within curly braces. Unlike stored properties, computed properties derive their values from other properties, making them ideal for presenting data in different formats without duplication.

  7. This partner computed property will intelligently derive information from a spouse property we'll create next. Add the bold code as shown below:

    var name: String
    var age: Int
    var maritalStatus: MaritalStatus
    var partner: String? { return spouse?.name ?? "No one" }

    We're using Swift's nil-coalescing operator (??), which provides elegant nil-handling. The expression spouse?.name attempts to access the name property, while the operator provides "No one" as a fallback if spouse is nil. This pattern is essential for building robust iOS applications that handle optional data gracefully.

    The question mark in spouse?.name demonstrates optional chaining—a safer alternative to force unwrapping that prevents runtime crashes when dealing with potentially nil values.

  8. Now we'll add the stored spouse variable, which references another Person instance:

    var partner: String? { return spouse?.name ?? "No one"}
    
    var spouse: Person? {
    
    }

    This Person? optional creates a relationship between two person instances. This self-referential pattern is common in modeling real-world relationships but requires careful memory management, which we'll address next.

  9. Make this property private to encapsulate internal implementation details:

    var partner: String? { return spouse?.name ?? "No one"}
    
    private var spouse: Person? {
    
    }

    Setting spouse as private follows the principle of encapsulation. External code can read partner information through the computed property, but cannot directly manipulate the internal spouse reference, preventing inconsistent states.

  10. Add the critical weak keyword to prevent memory leaks:

    var partner: String? { return spouse?.name ?? "No one"}
    
    private weak var spouse: Person? {
    
    }

    The weak keyword is essential here because we're creating a potential retain cycle. When two Person instances reference each other through spouse properties, strong references would create a deadlock where neither can be deallocated. Weak references allow one side of the relationship to be deallocated, automatically setting the weak reference to nil and preventing memory leaks—a critical consideration in iOS development where memory management directly impacts app performance.

  11. Add a didSet property observer to track relationship changes:

    private weak var spouse: Person? {
       didSet {
    
       }
    }

    Property observers are powerful tools for maintaining data consistency and triggering side effects. The didSet observer executes whenever the property value changes, making it perfect for logging, updating UI, or maintaining related state.

  12. Implement the logic that executes when someone enters a relationship:

    private weak var spouse: Person? {
       didSet {
          if spouse != nil { print("\(name) is now with \(spouse!.name)") }
       }
    }

    When spouse is assigned a value, this condition triggers, providing immediate feedback about relationship changes. In production apps, this is where you might update a database, refresh the UI, or trigger notifications.

  13. Handle the case when someone becomes single:

    private weak var spouse: Person? {
       didSet {
          if spouse != nil { print("\(name) is now with \(spouse!.name)") }
          else { print("\(name) is now sadly alone") }
       }
    }
  14. Now we'll initialize our properties with a comprehensive initializer:

    private weak var spouse: Person? {

    Code Omitted To Save Space

    }
    
    init(name: String, age: Int, maritalStatus: MaritalStatus = .single) {
       self.name = name
       self.age = age
       self.maritalStatus = maritalStatus
    }

    We're providing a default value of .single for maritalStatus, which reflects the most common initial state. This demonstrates how enums with default values can simplify object creation while maintaining type safety.

    Note that we don't initialize the partner property because computed properties derive their values at runtime rather than storing data directly.

MaritalStatus Enum Cases

Single25%
Married25%
Divorced25%
Complicated25%
Computed Properties vs Stored Properties

The partner property is computed and draws information from the spouse property without storing data itself. This pattern allows for dynamic value calculation based on other property states.

Property Types in Person Class

Stored Properties

Name, age, and maritalStatus variables that hold actual data values. These properties require initialization and consume memory storage.

Computed Properties

Partner property that calculates its value from spouse property. Uses nil-coalescing operator to handle optional values gracefully.

Private Properties

Spouse property marked as private and weak to prevent memory leaks. Only accessible within the Person class for internal operations.

Adding Functions

With our data model established, let's add behavior that demonstrates how methods can work with enums to maintain consistent object states.

  1. Add a marry function that handles relationship logic:

    self.maritalStatus = maritalStatus
    }
    
    func marry(personToMarry: Person) {
    
    }
  2. Implement validation logic to prevent invalid marriages:

    func marry(personToMarry: Person) {
       if spouse != nil || personToMarry.spouse != nil {
          print("Oh, I wish… but no, \(name) is already married. Please divorce first ;)")
          return
       }
    }

    This guard condition demonstrates defensive programming—we validate preconditions before executing business logic. The return statement provides an early exit, eliminating the need for complex nested else statements and keeping the happy path code clean.

  3. Implement the marriage logic when validation passes:

    return
       }
    
       spouse = personToMarry; maritalStatus = .married
       personToMarry.spouse = self; personToMarry.maritalStatus = .married
    }

    This code updates both Person instances atomically, ensuring relationship consistency. The self keyword explicitly refers to the current instance, while the semicolon keeps related statements visually grouped. Notice how we use enum dot syntax (**.married**) for clean, readable code.

  4. Add a companion divorce function for relationship dissolution:

    func marry(personToMarry: Person) {

    Code Omitted To Save Space

    }
    func divorce() {
    
    }
  5. Implement the divorce logic with proper cleanup:

    func divorce() {
      spouse?.spouse = nil; spouse?.maritalStatus = .divorced
      spouse = nil; maritalStatus = .divorced
    }

    This function uses optional chaining to safely update the former spouse's state, then clears the local spouse reference. The careful ordering ensures both sides of the relationship are properly updated, demonstrating how weak references and proper state management work together.

Strong vs Weak References

FeatureStrong ReferenceWeak Reference
Memory RetentionPrevents deallocationAllows deallocation
Reference CyclesCan create deadlocksPrevents cycles
Default BehaviorSwift defaultExplicit keyword needed
Use CasePrimary ownershipSecondary references
Recommended: Use weak references for spouse property to prevent memory cycles between Person instances
Property Observer Pattern

The didSet property observer monitors spouse property changes and automatically prints relationship status updates. This provides immediate feedback when relationships are established or broken.

Testing the Implementation

Now let's validate our implementation with realistic test scenarios that demonstrate the robustness of our enum-based design.

  1. Create test instances below the Person class:

    class Person {

    Code Omitted To Save Space

    }
    
    let june = Person(name: "June", age: 23)
    let march = Person(name: "March", age: 18)
    let jane = Person(name: "Jane", age: 22, maritalStatus: .divorced)
    let jim = Person(name: "Jim", age: 17)

    Notice how our default parameter allows most instances to omit maritalStatus, while Jane explicitly starts as divorced. This flexibility is a key benefit of well-designed initializers.

  2. Test the marriage functionality:

    let jim = Person(name: "Jim", age: 17)
    
    jane.marry(personToMarry: jim)
  3. View the debug output by clicking the top right middle button show hide debug area to show the Debug area.

  4. Observe the output showing both Jane is now with Jim and Jim is now with Jane, demonstrating how our property observers track both sides of the relationship.

  5. Let's improve the API by removing verbose parameter labels. Locate the marry function definition:

    func marry(personToMarry: Person) {

  6. Add an underscore to eliminate the external parameter label:

    func marry(_ personToMarry: Person) {

    The underscore tells Swift we don't want an explicit argument label, creating cleaner call sites—a common pattern in iOS development where method names are already descriptive.

  7. Update the method call to use the cleaner syntax:

    let jim = Person(name: "Jim", age: 17)
    
    jane.marry(jim)

    Much more readable! This demonstrates how thoughtful API design improves code clarity.

  8. Test the computed property functionality:

    jane.marry(jim)
    jane.partner

    The right sidebar should display "Jim", demonstrating how computed properties provide clean access to derived data.

  9. Verify the enum state management:

    jane.marry(jim)
    jane.partner
    jane.maritalStatus

    The sidebar should show that Jane is now married, proving our enum-based state management is working correctly.

  10. Check initial states before marriage:

    let jim = Person(name: "Jim", age: 17)
    
    jane.maritalStatus
    jim.maritalStatus
    
    jane.marry(jim)
    jane.partner
    jane.maritalStatus

    You should see divorced for Jane and single for Jim initially, then married for both after the ceremony.

  11. Verify Jim's updated status:

    jane.marry(jim)
    jane.partner
    jane.maritalStatus
    jim.maritalStatus

    Jim should now show as married, confirming our bidirectional relationship logic.

  12. Test Jim's partner property as well:

    jane.marry(jim)
    jane.partner
    jane.maritalStatus
    jim.partner
    jim.maritalStatus

    The sidebar should display "Jane", demonstrating perfect symmetry in our relationship model.

  13. Test the divorce functionality:

    jim.maritalStatus
    
    jane.divorce()
  14. Observe both Jim is now sadly alone and Jane is now sadly alone in the Debug area, showing how property observers track relationship dissolution.

  15. Verify the final states after divorce:

    jane.divorce()
    jane.partner
    jane.maritalStatus

    The sidebar should show "No one" and divorced, demonstrating how our computed properties and enums work together to maintain consistent state.

    This exercise showcases the power of combining enumerations with classes to create robust, type-safe data models. Enumerations are ubiquitous in iOS development—from representing UI states to handling network responses—making these patterns essential for professional iOS development in 2026.

  16. Save and close the file. Keep Xcode open for the next exercise.

Key Takeaways

1Enumerations provide type safety by restricting variables to predefined cases like single, married, divorced, and complicated
2Computed properties calculate values dynamically from other properties without storing data, using nil-coalescing operators for optional handling
3Weak references prevent memory cycles when objects reference each other, essential for proper memory management in Swift
4Property observers like didSet automatically execute code when property values change, enabling reactive programming patterns
5Private properties restrict access to class internals, maintaining encapsulation and preventing external manipulation of sensitive data
6Functions can manipulate enum states through conditional logic, checking existing relationships before creating new ones
7Argument labels can be omitted using underscore for cleaner function calls when parameter purpose is obvious
8Optional chaining allows safe property access on potentially nil objects without unwrapping, preventing runtime crashes

RELATED ARTICLES