Swift: فئات Swift وبناها
Classes and structs are the two core types for building data models in Swift. They share many similarities but differ fundamentally in memory management and how values are passed. This lesson helps you clarify the selection strategy for each.
1. What You'll Learn
- Basic syntax for defining classes and structs
- Core differences between value types (struct) and reference types (class)
- The purpose of the
mutatingkeyword - Initialization rules for classes and structs
- When to use a class vs a struct
2. A Real-World iOS Developer Story
(1) Pain Point: Changing a User Account in One Place Affects Another
Alice chose class to model a user account:
class UserAccount {
var name: String
var balance: Double
init(name: String, balance: Double) {
self.name = name
self.balance = balance
}
}
let account1 = UserAccount(name: "Alice", balance: 1000)
let account2 = account1 // References the same instance
account2.name = "Bob"
print(account1.name) // "Bob" — account1 was also changed!
Alice just wanted to create a new user but accidentally mutated the original data. The side effects of reference types propagated throughout the codebase without her realizing it.
(2) Solution: Struct as a Value Type
struct UserAccount {
var name: String
var balance: Double
}
var account1 = UserAccount(name: "Alice", balance: 1000)
var account2 = account1 // Copies an independent set of data
account2.name = "Bob"
print(account1.name) // "Alice" — account1 is unaffected
Struct assignment performs a value copy — each instance is independent.
(3) Benefit: Predictable Data Behavior
| Dimension | class (Reference Type) | struct (Value Type) |
|---|---|---|
| Assignment behavior | Shares the same instance | Independent copy |
| Accidental mutation risk | High (aliasing) | None |
| Memory allocation | Heap allocation | Stack allocation (more efficient) |
| Thread safety | Requires manual synchronization | Naturally isolated |
| Suitable for | Shared mutable state | Data models, value semantics |
3. Defining Classes and Structs
(1) Basic Syntax Comparison
graph TB
subgraph class
A["class Person {"]
B[" var name: String"]
C[" var age: Int"]
D[" func greet() { }"]
E["}"]
end
subgraph struct
F["struct Person {"]
G[" var name: String"]
H[" var age: Int"]
I[" func greet() { }"]
J["}"]
end
| Feature | class | struct | Notes |
|---|---|---|---|
| Properties | Yes | Yes | Stored and computed properties |
| Methods | Yes | Yes | Instance and type methods |
| Initializers | Yes | Yes | class does not auto-generate memberwise init |
| Inheritance | Yes | No | struct cannot inherit |
| Deinitializer | Yes | No | struct has no deinit |
| Reference counting | Yes | No | struct does not need ARC |
| Type casting | Yes | No | as?/as! only for class |
▶ Example: Defining Classes and Structs
// ============================================
// Class and struct definition comparison
// ============================================
// Class definition — must write init manually
class PersonClass {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce() {
print("Hi, I'm \(name), \(age) years old")
}
}
// Struct definition — automatically gets a memberwise initializer
struct PersonStruct {
var name: String
var age: Int
func introduce() {
print("Hi, I'm \(name), \(age) years old")
}
}
let p1 = PersonClass(name: "Alice", age: 28)
p1.introduce()
let p2 = PersonStruct(name: "Bob", age: 22) // Auto-generated init
p2.introduce()
Output:
TEXT 📖 للعرض فقطHi, I'm Alice, 28 years old Hi, I'm Bob, 22 years old
4. Value Types vs Reference Types
(1) Core Differences
graph LR
subgraph "Value Type (struct)"
A["var a = Point(x: 1, y: 2)"]
B["var b = a"]
C["b.x = 5"]
D["a.x → 1 (independent)"]
A --> B --> C --> D
end
subgraph "Reference Type (class)"
E["var a = Point(x: 1, y: 2)"]
F["var b = a"]
G["b.x = 5"]
H["a.x → 5 (shared)"]
E --> F --> G --> H
end
| Dimension | Value Type (struct) | Reference Type (class) |
|---|---|---|
| Assignment semantics | Copy contents | Copy reference |
| Equality check | == compares values |
=== compares identity |
| Storage location | Stack (faster) | Heap (ARC managed) |
| let constant | Properties are immutable | Reference is immutable but properties can change |
| Function parameters | Copy by default | Share reference by default |
▶ Example: Assignment Behavior Comparison
// ============================================
// Value type vs reference type assignment behavior
// ============================================
// Struct — value type
struct Position {
var x: Int
var y: Int
}
var posA = Position(x: 10, y: 20)
var posB = posA
posB.x = 99
print("posA: (\(posA.x), \(posA.y))") // Unchanged
print("posB: (\(posB.x), \(posB.y))") // Only affects posB
// Class — reference type
class Location {
var x: Int
var y: Int
init(x: Int, y: Int) { self.x = x; self.y = y }
}
var locA = Location(x: 10, y: 20)
var locB = locA
locB.x = 99
print("locA: (\(locA.x), \(locA.y))") // Also changed!
print("locB: (\(locB.x), \(locB.y))")
Output:
TEXT 📖 للعرض فقطposA: (10, 20) posB: (99, 20) locA: (99, 20) locB: (99, 20)
(2) The Identity Operator ===
Reference types can use === to check whether two variables point to the same instance:
let loc1 = Location(x: 5, y: 10)
let loc2 = loc1
let loc3 = Location(x: 5, y: 10) // Different instance
print(loc1 === loc2) // true — same instance
print(loc1 === loc3) // false — different instances
Tip:
===is the identity operator, distinct from==(the equality operator). Value types can only use==(must implement Equatable) and cannot use===.
5. mutating Methods and Selection Strategy
(1) The mutating Keyword
Struct instance methods cannot modify properties by default. To do so, they must be marked mutating:
| Method Type | class | struct (non-mutating) | struct (mutating) |
|---|---|---|---|
| Modify property | Allowed | Compile error | Allowed |
| Modify self | Allowed | Not allowed | Allowed |
| Call requirement | No restriction | Can be called on let constant | Must be called on var |
▶ Example: mutating Methods
// ============================================
// Struct mutating methods
// ============================================
struct Counter {
var count = 0
// Non-mutating — read-only method
func display() {
print("Count: \(count)")
}
// mutating — modifies property
mutating func increment() {
count += 1
}
// mutating — replaces the entire instance
mutating func reset() {
self = Counter() // Replace self
}
}
var counter = Counter() // Must be var to call mutating methods
counter.increment()
counter.increment()
counter.display() // Count: 2
counter.reset()
counter.display() // Count: 0
Output:
TEXT 📖 للعرض فقطCount: 2 Count: 0
(2) Selection Strategy
| Scenario | Recommended | Reason |
|---|---|---|
| Data models (Point, Size, User) | struct | Value semantics, safe and predictable |
| Need inheritance (UIView subclass, BaseViewController) | class | Inheritance is class-only |
| Need shared mutable state | class | Reference types share automatically |
| Need ObjC interop | class | @objc requires class |
| Lightweight wrapper (wrapping a few values) | struct | Stack allocation, zero overhead |
| Need deinit cleanup | class | struct has no deinit |
▶ Example: Struct as the Default Choice for Models
// ============================================
// Why modern Swift prefers struct for models
// ============================================
struct User {
let id: Int
var name: String
var email: String
var score: Int
mutating func addScore(_ points: Int) {
score += points
}
}
var user = User(id: 1, name: "Alice", email: "alice@example.com", score: 100)
user.addScore(50)
// Copy-and-mutate pattern — safely create changed versions
let admin = User(id: 2, name: "Admin", email: "admin@example.com", score: 0)
// admin.addScore(10) // Compile error! let constant cannot call mutating methods
var editor = admin
editor.name = "Editor" // Independent modification, doesn't affect admin
Output:
TEXT 📖 للعرض فقط(No console output — demonstrates value semantics: editor.name = "Editor" does not affect admin.name)
6. Complete Example: Bank Account System
// ============================================
// Complete example: Bank account system
// Features: class for account management, struct for transaction records
// ============================================
import Foundation
// 1. Transaction record — value type struct
struct Transaction {
let id: Int
let amount: Double
let type: String // "deposit" or "withdraw"
let date: Date
func description() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return "[\(formatter.string(from: date))] \(type): $\(amount)"
}
}
// 2. Account — reference type class (shared state)
class BankAccount {
let accountNumber: String
var balance: Double
private var transactions: [Transaction] = []
private var nextId = 1
init(accountNumber: String, initialBalance: Double = 0) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
func deposit(amount: Double) {
guard amount > 0 else { return }
balance += amount
let tx = Transaction(id: nextId, amount: amount, type: "deposit", date: Date())
transactions.append(tx)
nextId += 1
}
func withdraw(amount: Double) -> Bool {
guard amount > 0 && amount <= balance else { return false }
balance -= amount
let tx = Transaction(id: nextId, amount: amount, type: "withdraw", date: Date())
transactions.append(tx)
nextId += 1
return true
}
func printStatement() {
print("=== Account \(accountNumber) ===")
print("Balance: $\(balance)")
print("Transactions:")
for tx in transactions {
print(" \(tx.description())")
}
}
}
// 3. Usage
let account = BankAccount(accountNumber: "ACC-001", initialBalance: 1000)
account.deposit(amount: 500)
account.withdraw(amount: 200)
account.deposit(amount: 100)
account.printStatement()
Output:
TEXT 📖 للعرض فقط=== Account ACC-001 === Balance: $1400.0 Transactions: [2026-07-30] deposit: $500.0 [2026-07-30] withdraw: $200.0 [2026-07-30] deposit: $100.0
❓ FAQ
lazy var works in structs too, but because lazy properties cannot be accessed on immutable structs, practical usage is limited. lazy is more natural in classes.mutating only applies to value types (struct and enum). Class methods can modify properties by default — no mutating keyword needed.📖 Summary
- Both classes and structs can define properties, methods, subscripts, and extensions
- Structs are value types (copy on assignment); classes are reference types (copy the reference)
- Structs get a memberwise initializer automatically; classes must write init manually
- The
mutatingkeyword allows struct methods to modify their own properties - Use
===to check whether two reference type variables point to the same instance - Prefer struct; use class when you need inheritance or shared mutable state
📝 Exercises
- Basic: Define a
Bookstruct withtitle,author,pagesproperties and asummary()method. Create two instances and test value type copy behavior. - Intermediate: Define a
TemperatureSensorclass with acurrentTempproperty and areadings: [Double]history array. ImplementrecordTemp(_:)andaverage()methods. Create an instance, record 5 temperatures, and print the average. - Challenge: Design a "Team Collaboration System": use a
Teamclass to manage a shared member list and aMemberstruct for individual profiles.TeamprovidesaddMember(_:)andlistMembers()methods.Memberhas amutatingmethod to update roles. Verify the shared reference behavior of class.