class — khái niệm, các khái niệm chính và nguyên lý hoạt động trong Swift

Tác giả: IT Sectr Đã đăng: 2026-06-17 Thời gian đọc: 8 phút

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 — kiểu tham chiếu trong Swift nơi biến lưu trữ tham chiếu đến một đối tượng trong heap, chứ không phải giá trị tự thân.
  • Kế thừa — lớp có thể kế thừa thuộc tính và phương thức từ lớp cha và ghi đè chúng thông qua override.
  • ARC — đếm tham chiếu tự động giải phóng bộ nhớ khi không còn tham chiếu mạnh đến đối tượng.
  • Deinit — phương thức đặc biệt được gọi trước khi giải phóng đối tượng để dọn dẹp tài nguyên.
  • Nhận dạng — các toán tử === 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.

Class trong Swift là gì?

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.

Class như một kiểu tham chiếu

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.

swift
// 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 lớp

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.

Lớp cơ sở và lớp con

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.

Lớp final

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ã.

swift
// 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.

Hủy khởi tạo và quản lý bộ nhớ

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

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ó.

Tham chiếu yếu và không sở hữu

Để 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.

swift
// 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.

Class vs Struct: khác biệt chính

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ểmclassstruct
KiểuKiểu tham chiếuKiểu giá trị (bản sao)
Bộ nhớHeap + ARCNgăn xếp / inlined
Kế thừaHỗ trợ (một cha)Không hỗ trợ
DeinitKhông
Nhận dạng (===)Hỗ trợKhông hỗ trợ
MutatingKhông cần (luôn mutating)Chỉ với mutating
Init memberwiseKhông được tạoĐược tạo tự động
An toàn luồngKhô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ớ.

Khi nào sử dụng class trong Swift

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ế.

Thành phần UIKit và AppKit

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.

Singleton và trạng thái chia sẻ

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.

Tiêm phụ thuộc và đối tượng tham chiếu

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.

swift
// 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 trong Swift là gì?

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 khác struct trong Swift như thế nào?

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 trong Swift là gì?

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 trong Swift là gì?

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ó.

Khi nào sử dụng class trong Swift?

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

  • Class là kiểu tham chiếu trong Swift với ngữ nghĩa truyền theo tham chiếu: tất cả các biến trỏ đến một đối tượng đều thấy trạng thái hiện tại của nó và có thể sửa đổi nó.
  • ARC quản lý bộ nhớ lớp thông qua đếm tham chiếu mạnh; weak và unowned ngăn chặn retain cycles trong quan hệ cha-con và closure.
  • Kế thừa cho phép lớp tái sử dụng và ghi đè hành vi của cha, nhưng 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ì mã.
  • Deinit chạy khi một đối tượng được giải phóng và cần thiết để giải phóng tài nguyên — tệp, bộ hẹn giờ, đăng ký NotificationCenter.
  • UIKit và AppKit yêu cầu lớp cho tất cả các thành phần UI — UIView, UIViewController, delegate và data source của chúng.
  • ObservableObject trong SwiftUI — class là bắt buộc vì SwiftUI theo dõi các thay đổi thông qua @Published và @StateObject theo tham chiếu.
  • Apple khuyến nghị struct làm kiểu mặc định cho các kiểu dữ liệu mới — chỉ sử dụng class khi cần nhận dạng, kế thừa hoặc tích hợp Objective-C.

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.

Thảo luận dự án

Đọc thêm