Class là một kiểu tham chiếu (reference type) trong ngôn ngữ Swift, mà các thể hiện của nó được truyền theo tham chiếu thay vì sao chép khi gán. Không giống như struct, lớp hỗ trợ kế thừa, hủy khởi tạo và đếm tham chiếu tự động ARC để quản lý bộ nhớ. Theo Swift Programming Language Guide, 2026, class là cần thiết để làm việc với các framework UI của Apple (UIKit, AppKit) và triển khai các mẫu yêu cầu nhận dạng đối tượng dùng chung. Lựa chọn giữa class và struct là một trong những quyết định kiến trúc quan trọng trong Swift.
Các điểm chính
Class — một kiểu tham chiếu tổng hợp trong Swift nhóm các thuộc tính và phương thức thành một thực thể duy nhất với hỗ trợ kế thừa và điều phối động. Không giống như struct, một thể hiện lớp được tạo trong heap và biến lưu trữ tham chiếu đến thể hiện này, chứ không phải dữ liệu tự thân.
Khi bạn gán một biến lớp cho một biến khác, cả hai đều tham chiếu đến cùng một đối tượng trong bộ nhớ. Thay đổi thông qua một tham chiếu có thể thấy được qua tham chiếu kia — đây là thuộc tính cơ bản của kiểu tham chiếu, được sử dụng trong các mẫu ủy quyền, quan sát và trạng thái chia sẻ.
Theo Tài liệu Apple Swift, lớp là cách duy nhất để làm việc với UIKit và AppKit, nơi tất cả các thành phần UI kế thừa từ UIView và UIViewController. Ngoài ra, lớp cần thiết để triển khai các mẫu yêu cầu nhận dạng đối tượng (hai tham chiếu đến một đối tượng) và vòng đời được kiểm soát.
Ngữ nghĩa kiểu tham chiếu — thuộc tính chính của lớp. Khi gán một lớp cho một biến mới, Swift sao chép tham chiếu, chứ không phải dữ liệu. Tất cả các biến tham chiếu đến cùng một thể hiện đều thấy trạng thái hiện tại của nó và có thể sửa đổi nó. Hành vi này khác về cơ bản so với kiểu giá trị, nơi mỗi biến nhận được một bản sao độc lập.
// Ví dụ về ngữ nghĩa kiểu tham chiếu
class User {
var name: String
init(name: String) { self.name = name }
}
let user1 = User(name: "Alice")
let user2 = user1 // user2 — cùng tham chiếu
user2.name = "Bob"
print(user1.name) // "Bob" — đã thay đổi qua user2!
// Kiểm tra nhận dạng với toán tử ===
print(user1 === user2) // true — cùng đối tượng
Các toán tử === (nhận dạng) và !== kiểm tra xem hai biến có tham chiếu đến cùng một thể hiện lớp hay không. Điều này khác với == (bằng nhau), so sánh giá trị thuộc tính. Toán tử === không khả dụng cho struct — kiểu giá trị không có nhận dạng.
Kế thừa — cơ chế mà một lớp có thể tiếp nhận các thuộc tính và phương thức của lớp cha. Trong Swift, một lớp chỉ có thể kế thừa từ một cha (đơn kế thừa), nhưng có thể triển khai nhiều giao thức. Từ khóa override cho phép ghi đè phương thức hoặc thuộc tính được kế thừa.
Bất kỳ lớp nào không kế thừa từ lớp khác sẽ tự động trở thành lớp cơ sở (không nhầm với NSObject). Lớp con chỉ định cha bằng dấu hai chấm sau tên của nó. Nếu lớp con ghi đè phương thức của cha, nó phải gọi super.phương thức() để bảo toàn hành vi của cha — đây là yêu cầu của trình biên dịch.
Từ khóa final trước class ngăn chặn kế thừa. Trình biên dịch có thể tối ưu hóa các lời gọi phương thức của lớp final thông qua điều phối tĩnh, cải thiện hiệu suất. Sử dụng final cho các lớp không nhằm mục đích mở rộng — điều này ghi lại ý định của bạn và tăng tốc mã.
// Ví dụ về kế thừa lớp trong Swift
class Vehicle {
var speed: Double = 0
func description() -> String {
"Speed: \(speed) km/h"
}
}
// Car kế thừa Vehicle
class Car: Vehicle {
var brand: String = "Unknown"
override func description() -> String {
"\(brand) - \(speed) km/h"
}
}
// Lớp final — ngăn kế thừa
final class ElectricCar: Car {
var batteryLevel: Double = 100
}
let tesla = ElectricCar()
tesla.brand = "Tesla"
tesla.speed = 120
print(tesla.description()) // "Tesla - 120.0 km/h"
Kế thừa là một cơ chế mạnh mẽ nhưng có trách nhiệm. Hệ thống phân cấp sâu (hơn 5 cấp) làm phức tạp việc bảo trì và kiểm thử. Để tái sử dụng chức năng mà không cần kế thừa, hãy sử dụng giao thức với extension và lập trình hướng giao thức — một cách tiếp cận mà Apple khuyến khích như một giải pháp thay thế cho hệ thống phân cấp lớp sâu.
Swift sử dụng ARC (Đếm tham chiếu tự động) để quản lý bộ nhớ lớp. Mỗi thể hiện lớp có một bộ đếm tham chiếu mạnh. Khi một tham chiếu mạnh mới được tạo, bộ đếm tăng lên; khi bị phá hủy, nó giảm xuống. Khi bộ đếm đạt đến 0, bộ nhớ được giải phóng.
Deinit — phương thức được gọi tự động trước khi giải phóng một thể hiện lớp. Nó giải phóng tài nguyên: đóng tệp, hủy đăng ký thông báo, dừng bộ hẹn giờ. Deinit chỉ khả dụng cho lớp — struct và enum không có nó.
Để ngăn chặn chu trình tham chiếu mạnh (retain cycles), Swift cung cấp tham chiếu weak và unowned. Weak là tham chiếu tùy chọn tự động trở thành nil khi đối tượng được giải phóng. Unowned không phải tùy chọn nhưng tham chiếu đến một đối tượng được đảm bảo sống lâu hơn ngữ cảnh hiện tại. Một retain cycle điển hình xảy ra trong mối quan hệ cha-con: con giữ một tham chiếu mạnh đến cha.
// Ví dụ về ARC và tham chiếu yếu
class Parent {
var name: String
var child: Child?
init(name: String) { self.name = name }
deinit { print("\(name) đã được giải phóng") }
}
class Child {
var name: String
weak var parent: Parent? // weak ngăn chặn retain cycle
init(name: String) { self.name = name }
deinit { print("\(name) đã được giải phóng") }
}
var parent: Parent? = Parent(name: "Anna")
parent?.child = Child(name: "Mia")
parent?.child?.parent = parent
parent = nil // Cả hai đối tượng đã được giải phóng!
// Không có weak sẽ tạo ra retain cycle
Luôn sử dụng weak cho tham chiếu từ đối tượng con đến cha và cho danh sách capture trong closure. Chỉ sử dụng unowned khi bạn chắc chắn rằng đối tượng sẽ tồn tại lâu hơn ngữ cảnh hiện tại — việc sử dụng unowned không đúng cách có thể gây ra lỗi khi truy cập bộ nhớ đã được giải phóng.
Lựa chọn giữa class và struct là một quyết định kiến trúc ảnh hưởng đến hiệu suất, an toàn luồng và thiết kế API. Hãy xem xét bảng khác biệt chính để đưa ra quyết định đúng đắn trong mỗi trường hợp.
| Đặc điểm | class | struct |
|---|---|---|
| Kiểu | Kiểu tham chiếu | Kiểu giá trị (bản sao) |
| Bộ nhớ | Heap + ARC | Ngăn xếp / inlined |
| Kế thừa | Hỗ trợ (một cha) | Không hỗ trợ |
| Deinit | Có | Không |
| Nhận dạng (===) | Hỗ trợ | Không hỗ trợ |
| Mutating | Không cần (luôn mutating) | Chỉ với mutating |
| Init memberwise | Không được tạo | Được tạo tự động |
| An toàn luồng | Không đảm bảo (trạng thái chia sẻ) | Đảm bảo (sao chép) |
Sử dụng lớp khi bạn cần nhận dạng chia sẻ (nhiều phần mã làm việc với một đối tượng), kế thừa hoặc tương tác với Objective-C runtime. Đối với mọi thứ khác, struct được ưu tiên — chúng nhanh hơn, an toàn hơn trong mã đa luồng và không yêu cầu quản lý bộ nhớ.
Mặc dù Apple khuyến nghị sử dụng struct theo mặc định, lớp là cần thiết trong một số kịch bản cụ thể. Hãy xem xét từng kịch bản với các ví dụ thực tế.
Tất cả các thành phần UI trên iOS và macOS đều là lớp kế thừa từ UIView (iOS) hoặc NSView (macOS). Bạn không thể thay thế UIViewController bằng struct — nó yêu cầu kế thừa và deinit để giải phóng tài nguyên. Khi làm việc với UIKit, hãy sử dụng lớp cho bộ điều khiển, view và delegate của chúng.
Mẫu Singleton (một thể hiện lớp cho toàn bộ ứng dụng) yêu cầu ngữ nghĩa tham chiếu. Các trình quản lý: NetworkManager, SettingsManager, AnalyticsService — thường được triển khai dưới dạng lớp với thuộc tính static dùng chung. Struct không phù hợp vì mỗi bản sao sẽ là một thể hiện độc lập.
Khi một đối tượng phải là nguồn sự thật duy nhất và được truyền giữa các mô-đun theo tham chiếu — hãy sử dụng class. Điều này áp dụng cho quản lý trạng thái, nơi thay đổi đối tượng ở một nơi phải hiển thị ở tất cả các thành phần phụ thuộc. Đối với ObservableObject trong SwiftUI, lớp là bắt buộc.
// ObservableObject — class bắt buộc cho SwiftUI
import SwiftUI
class AppViewModel: ObservableObject {
@Published var isLoggedIn: Bool = false
@Published var username: String = ""
func login(user: String) {
isLoggedIn = true
username = user
}
}
// Sử dụng trong SwiftUI View
struct ContentView: View {
@StateObject var viewModel = AppViewModel()
var body: some View {
Text(viewModel.isLoggedIn ? "Chào mừng" : "Đăng nhập")
}
}
Quy tắc: nếu một đối tượng phải có nhận dạng (hai tham chiếu -> một đối tượng), là một thể hiện duy nhất hoặc làm việc với UIKit/Objective-C — chọn class. Nếu một đối tượng chỉ đơn giản chứa dữ liệu — chọn struct.
Câu hỏi thường gặp
Class là kiểu tham chiếu trong Swift hỗ trợ kế thừa, hủy khởi tạo và ARC để quản lý bộ nhớ. Các thể hiện lớp được lưu trữ trong heap và biến chứa tham chiếu đến đối tượng, không phải bản sao của nó.
Class — kiểu tham chiếu (được truyền theo tham chiếu), hỗ trợ kế thừa và deinit. Struct — kiểu giá trị (được sao chép), không hỗ trợ kế thừa, nhưng triển khai giao thức và nhận init memberwise tự động. Swift khuyến nghị struct là kiểu mặc định.
ARC (Đếm tham chiếu tự động) — cơ chế quản lý bộ nhớ cho các lớp Swift. Mỗi thể hiện có một bộ đếm tham chiếu mạnh. Khi bộ đếm đạt đến 0, bộ nhớ được giải phóng. Tham chiếu weak và unowned ngăn chặn retain cycles giữa các đối tượng.
Deinit — một phương thức lớp được gọi tự động trước khi giải phóng bộ nhớ của nó. Được sử dụng để đóng tệp, hủy đăng ký thông báo và các thao tác dọn dẹp khác. Deinit chỉ khả dụng trong lớp — struct không có nó.
Sử dụng class cho các thành phần UI UIKit/AppKit, singleton, ObservableObject trong SwiftUI, đối tượng có nhận dạng chia sẻ (delegate, observer) và khi làm việc với Objective-C runtime. Đối với mô hình dữ liệu, DTO và cấu hình, struct được ưu tiên.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm