Swift Structures vs. Classes: Properties, Memberwise Initializers, Value vs. Reference & Identity
  • July 24, 2025

Modeling Custom Types with Structures and Classes

In Swift, structures (struct) and classes (class) are the two primary building blocks for creating your own data types. Both let you encapsulate related data (properties) and behavior (methods) under a single, coherent type. Use them to model domain concepts—points in a game world, user profiles, network responses, and more.

struct UserProfile {
    var username: String
    var age: Int
    func greet() {
        print("Hi, I'm \(username), \(age) years old.")
    }
}

class GameCharacter {
    var name: String
    var health: Int
    init(name: String, health: Int) {
        self.name = name
        self.health = health
    }
    func takeDamage(_ amount: Int) {
        health -= amount
        print("\(name) took \(amount) damage, health is now \(health).")
    }
}
  • Structures are value types; when you assign or pass them, you get a copy.
  • Classes are reference types; assignments and passes share a single instance.

Comparing Structures and Classes

Feature Structure (Value Type) Class (Reference Type)
Memory Semantics Copied on assignment—each variable has its own independent copy. Shared reference—multiple variables can point to the same instance.
Inheritance Cannot inherit from other types. Supports single inheritance; can subclass and override behavior.
Deinitializers No deinitializer; cleanup isn’t necessary since values go out of scope. Can implement deinit to run code when an instance is deallocated.
Type Casting No runtime casting; cannot use is/as to check or convert. Can check & downcast with is, as?, as!.
Mutability Control Mark with var to allow mutation; let instances are entirely immutable (all var properties become read-only). Even a let class instance can have its var properties changed; let only prevents rebinding.
Use Case Guidance Best for lightweight, small data types without shared mutable state (points, colors, simple data containers). Ideal when identity matters or you need shared, mutable state and inheritance hierarchies (view controllers, data managers, delegates).

Definition Syntax

Structure

struct MyStruct {
    // Stored properties
    var text: String
    let count: Int
  
    // Computed property
    var isEmpty: Bool { text.isEmpty }
  
    // Method
    func describe() -> String {
        return "‘\(text)’ has \(count) items."
    }

    // Mutating method (modifies self)
    mutating func update(text newText: String) {
        text = newText
    }
}
  • Stored properties hold data; let for constants, var for variables.
  • Computed properties calculate their value on demand.
  • Methods define behavior.
  • mutating methods are required when a method changes self or its properties.

Class

class MyClass {
    // Stored properties
    var title: String
    let maxCount: Int
  
    // Initializer
    init(title: String, maxCount: Int) {
        self.title = title
        self.maxCount = maxCount
    }
  
    // Computed property
    var isFull: Bool { title.count >= maxCount }
  
    // Method
    func describe() -> String {
        return "‘\(title)’ (max \(maxCount))"
    }
  
    // Deinitializer
    deinit {
        print("MyClass titled ‘\(title)’ is being deallocated")
    }
}
  • Initializer (init) must set all stored properties before self is used.
  • deinit runs just before the instance is deallocated (no arguments, no return).
  • Inheritance and type casting available; see next section for more.

Creating and Using Instances

Structures (Value Semantics)

var a = MyStruct(text: "Hello", count: 5)
var b = a            // b is a copy of a
b.update(text: "Bye")
print(a.text)        // "Hello"
print(b.text)        // "Bye"
  • Modifying b doesn’t affect a; each has its own storage.

Classes (Reference Semantics)

let obj1 = MyClass(title: "Level 1", maxCount: 3)
let obj2 = obj1      // obj2 references the same instance
obj2.title = "Boss"
print(obj1.title)    // "Boss"
  • Changing obj2 affects obj1; they share identity.

When to Choose Which

  • Use a struct when:
    • You want predictable, independent copies.
    • The data is small, encapsulated, and logically immutable (or mutability is local).
    • You don’t need inheritance or identity.
  • Use a class when:
    • You need shared, mutable state across multiple parts of the app.
    • You require inheritance or polymorphic behavior.
    • You want to leverage deinit for cleanup (e.g., removing observers).

Accessing Properties

Both structures and classes expose their data via properties, accessed with dot syntax.

struct Rectangle {
    var width: Double
    var height: Double
    // Computed property
    var area: Double {
        return width * height
    }
}

var rect = Rectangle(width: 10, height: 5)
print(rect.width)       // 10
print(rect.height)      // 5
print(rect.area)        // 50
  • Stored properties (width, height) hold data.
  • Computed properties (area) calculate on-the-fly.
  • You can read and write via rect.width = 12 (if var), but if rect were declared with let, you couldn’t mutate any of its properties.

Classes work the same way:

class Circle {
    var radius: Double
    var circumference: Double { 2 * .pi * radius }
    init(radius: Double) {
        self.radius = radius
    }
}

let c = Circle(radius: 3)
print(c.circumference)  // ~18.85
c.radius = 5            // OK, even though c is a let-constant

Memberwise Initializers for Structure Types

Swift automatically generates a memberwise initializer for structs that don’t define any custom initializers. This saves you from writing boilerplate.

struct Point {
    var x: Double
    var y: Double
}

let p = Point(x: 2.0, y: 4.5)
  • You get Point(x:y:) “for free” as long as you don’t write your own init.
  • If you add default values, the initializer still exists but those parameters become optional at the call site:
    struct Color {
        var red:   Double = 0
        var green: Double = 0
        var blue:  Double = 0
    }
    
    let black = Color()              // uses all defaults
    let magenta = Color(red: 1, blue: 1)
    
  • Classes do not receive memberwise initializers automatically; you must write initializers manually for every stored property (unless you give them default values, in which case you might rely on the compiler’s synthesized init()).

Structures and Enumerations Are Value Types

  • Value semantics: when you assign or pass a struct/enum, you get a copy, not a shared reference.
struct User {
    var name: String
}
var u1 = User(name: "Alice")
var u2 = u1           // copy made here
u2.name = "Bob"
print(u1.name)        // "Alice"
print(u2.name)        // "Bob"
  • This makes reasoning about data safer: changes to u2 never affect u1.
  • Enums follow the same rule:
    enum Status {
        case active, inactive
    }
    var s1 = Status.active
    var s2 = s1
    s2 = .inactive
    print(s1)  // .active
    

Value types are ideal for small, immutable pieces of data (points, ranges, settings).


Classes Are Reference Types

  • Reference semantics: assigning or passing a class instance shares the same underlying object.
class Player {
    var score: Int = 0
}
let p1 = Player()
let p2 = p1        // both refer to the same Player
p2.score = 10
print(p1.score)    // 10
  • Multiple variables can point to the same instance; changes via any alias are visible to all.
  • Use this when you need shared, mutable state (view controllers, data managers, caches).

Identity Operators

Swift provides two operators to compare whether two references point to exactly the same class instance:

  • === returns true if both sides refer to the identical object.
  • !== is the inverse.
let a = Player()
let b = Player()
let c = a

print(a === b)  // false: different instances
print(a === c)  // true: c is the same instance as a
print(a !== b)  // true
  • These operators only apply to class types (reference types), not to structs or enums.
  • Use identity checks when you must confirm that two references share the same backing object (e.g., avoiding duplicate registrations, caching, or de-duplication logic).

 

YOU MIGHT ALSO LIKE...
SwiftUI Router

With SwiftUI Router you can power your SwiftUI app with path-based routing. By utilizing a path-based system, navigation in your app becomes ...

FlowStacks

This package takes SwiftUI's familiar and powerful NavigationStack API and gives it superpowers, allowing you to use the same API not just ...

Concepts

This framework uses three main concepts: Route - an identifiable value that represents a destination in your app Transition - a visual ...

SideMenu – SwiftUI

Install Swift Package Manager Open Xcode, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/akardas16/SideMenu.git as Branch main You need to add import ...

dot-globe