
1) Bức tranh tổng quát
- Struct: kiểu giá trị (value type). Không kế thừa. Có memberwise initializer. Phương thức có thể
mutatingđể sửa trạng thái. Không códeinit. - Class: kiểu tham chiếu (reference type). Kế thừa được. Có ARC,
deinit, identity (===). Không có memberwise init tự động. - Protocol: cả struct và class đều conform. Kế thừa chỉ dành cho class; struct dùng protocol composition để mở rộng hành vi.
Gợi ý: Swift ưu tiên value semantics để dễ suy luận và an toàn luồng. Chỉ dùng class khi cần chia sẻ trạng thái sống lâu và đa nơi cùng tham chiếu.
2) Bảng so sánh nhanh
| Tiêu chí | Struct | Class |
|---|---|---|
| Semantics | Giá trị | Tham chiếu |
| Khởi tạo | Memberwise init tự sinh | Không tự sinh nếu có stored property không default |
| Kế thừa | Không | Có đa cấp, final để khóa |
| ARC | Không áp dụng trực tiếp (trừ khi chứa reference) | Có. Dễ tạo retain cycle |
deinit | Không | Có |
mutating | Cần khi đổi self hoặc property | Không cần |
| Gán/Truyền hàm | Sao chép theo giá trị (copy-on-write tối ưu) | Sao chép tham chiếu |
| Đồng nhất (identity) | Không có === | Có ===/!== |
| Thread-safety | Dễ suy luận hơn | Cần đồng bộ hóa |
| Interop Obj‑C | Không trực tiếp | Tốt (kế thừa từ NSObject, @objc) |
3) Giá trị vs Tham chiếu trong thực tế
struct Point {
var x: Int
var y: Int
}
var a = Point(x: 0, y: 0)
var b = a // copy giá trị
b.x = 10 // a không đổi
class Node {
var value: Int
init(_ v: Int) { value = v }
}
let n1 = Node(1)
let n2 = n1 // cùng tham chiếu
n2.value = 9 // n1.value == 9
- Với struct, gán cho biến mới là bản sao logic. Swift tối ưu Copy‑on‑Write (CoW) cho các collection chuẩn như
Array,Dictionary,String: chỉ copy khi có đột biến. - Với class, nhiều biến có thể trỏ tới cùng một đối tượng, thay đổi ở một nơi ảnh hưởng nơi khác.
4) let vs var và từ khóa mutating
struct Counter {
var count = 0
mutating func inc() { count += 1 }
}
var s = Counter()
s.inc() // OK
let s2 = Counter()
// s2.inc() // Lỗi: s2 là hằng số, không được mutate
class Box { var v = 0 }
let c = Box()
c.v = 1 // OK: tham chiếu hằng, nhưng trạng thái bên trong vẫn đổi
lettrên struct khóa toàn bộ giá trị.mutatingbuộc phải khai báo khi method thay đổiselfhoặc property.lettrên class reference không khóa nội dung; nó chỉ khóa bản thân tham chiếu.
5) ARC, retain cycle, và capture list
- Class dùng ARC để quản lý vòng đời. Dễ tạo retain cycle khi hai object giữ mạnh lẫn nhau hoặc khi closure giữ
selfmạnh.
class A {
var f: (() -> Void)?
func setup() {
f = { [weak self] in self?.doWork() }
}
func doWork() {}
}
- Struct không có ARC trực tiếp, nhưng nếu struct chứa property là reference type thì vòng đời vẫn phụ thuộc vào ARC của những phần tử đó.
6) Kế thừa vs Protocol Oriented
- Class hỗ trợ kế thừa,
override,final,required,convenience. Có two‑phase initialization vàdeinit. - Struct không kế thừa nhưng conform protocol rất linh hoạt. Mô hình Protocol‑Oriented Programming khuyến khích value type + extension + default implementation.
7) Khởi tạo
struct User { var id: Int; var name: String } // có memberwise init tự sinh
class Person {
let id: Int
var name: String
init(id: Int, name: String) { self.id = id; self.name = name }
}
- Struct: có memberwise initializer nếu không tự định nghĩa initializer khác.
- Class: nếu có stored property không có giá trị mặc định, bạn phải viết designated initializer.
8) So sánh bằng == vs đồng nhất ===
struct S: Equatable { let x: Int }
let s1 = S(x: 1), s2 = S(x: 1)
print(s1 == s2) // true
class C { let x: Int; init(_ x: Int) { self.x = x } }
let c1 = C(1), c2 = C(1)
print(c1 === c2) // false: khác instance
==so sánh giá trị.===so sánh đồng nhất tham chiếu và chỉ có ở class.
9) Hiệu năng và bộ nhớ
- Value type thường nhỏ, bất biến, dễ tối ưu, dễ suy luận về chi phí copy.
- Không mặc định đồng nhất “struct trên stack, class trên heap”. Trình biên dịch có thể tối ưu bố trí bộ nhớ. Điều quan trọng là semantics.
- Collection chuẩn dùng CoW để tránh copy thừa. Mutation mới kích hoạt copy.
10) Khi nào chọn Struct, khi nào chọn Class
Chọn Struct khi:
- Dữ liệu mô tả giá trị thuần, không cần chia sẻ tham chiếu.
- Muốn an toàn luồng và dễ test.
- Trạng thái nhỏ, bất biến, hoặc thay đổi theo copy.
- Ví dụ: model bất biến, DTO,
Point,Range,Money,Userthuần dữ liệu.
Chọn Class khi:
- Cần chia sẻ trạng thái chung giữa nhiều nơi.
- Cần kế thừa, polymorphism theo class.
- Cần ARC,
deinit, hoặc interop Obj‑C. - Ví dụ:
UIViewController, nguồn dữ liệu stateful, cache, coordinator.
Lưu ý: SwiftUI dùng
struct Viewđể mô tả UI bất biến; state được tách khỏi View qua@State,@ObservedObject(class),@StateObject(class),@EnvironmentObject.
11) Các bẫy thường gặp
- Dùng class cho mọi thứ → khó kiểm soát side‑effect, dễ race condition.
- Quên
[weak self]trong closure giữ dài hạn → retain cycle. - Nghĩ rằng
lettrên class chặn mọi thay đổi → sai; chỉ khóa tham chiếu. - Tự viết CoW sai cách cho struct tự thiết kế → copy không như kỳ vọng.
- Kỳ vọng
deinitở struct → không có.
12) Ví dụ CoW tối giản
struct CowBuffer {
private class Storage { var data: [Int] = [] }
private var storage = Storage()
var data: [Int] { storage.data }
// ensure unique before write
private mutating func makeUnique() {
if !isKnownUniquelyReferenced(&storage) {
storage = Storage()
}
}
mutating func append(_ x: Int) {
makeUnique()
storage.data.append(x)
}
}
13) Liên quan đến Concurrency
- Value type giúp tránh shared mutable state giữa task. Truyền dữ liệu theo copy.
- Shared state cần cô lập: dùng class + actor hoặc queue riêng.
actorlà reference type an toàn luồng ở mức ngôn ngữ, nhưng khác chủ đề bài này.
14) Bộ câu hỏi phỏng vấn mẫu và gợi ý trả lời
- Khác biệt cốt lõi giữa struct và class?
- Struct là value type, copy khi gán/truyền; không kế thừa, không
deinit. Class là reference type, có kế thừa, ARC,deinit, identity.
- Struct là value type, copy khi gán/truyền; không kế thừa, không
- Tại sao Swift ưu tiên value semantics?
- Dễ suy luận, ít side‑effect, an toàn luồng hơn, dễ test.
letkhác nhau thế nào với struct và class?letstruct khóa toàn bộ giá trị, không gọi methodmutating.letclass chỉ khóa tham chiếu, vẫn đổi property var bên trong.
- Copy‑on‑Write là gì?
- Trì hoãn copy đến lúc mutate.
Array,Dictionary,Stringáp dụng. Giảm chi phí copy không cần thiết.
- Trì hoãn copy đến lúc mutate.
- Khi nào chọn class thay vì struct?
- Cần chia sẻ trạng thái, kế thừa,
deinit, interop Obj‑C, hoặc vòng đời do ARC.
- Cần chia sẻ trạng thái, kế thừa,
- Làm sao tránh retain cycle với closure?
- Dùng capture list
[weak self]hoặc[unowned self]tùy vòng đời.
- Dùng capture list
- Có
===cho struct không?- Không.
===chỉ cho class. Struct so sánh bằng==nếu conformEquatable.
- Không.
- Struct có kế thừa không?
- Không. Dùng protocol để mở rộng hành vi.
- Có thể có
deinittrong struct?- Không. Chỉ class có.
- Memberwise initializer là gì?
- Khởi tạo được auto‑generate cho struct khi không có custom init.
- Hai‑giai‑đoạn khởi tạo (two‑phase init) áp cho loại nào?
- Class. Đảm bảo tất cả stored property được khởi tạo trước khi dùng.
- Tại sao
mutatingtồn tại ở struct?- Vì method có thể thay đổi
selflà giá trị. Ngôn ngữ yêu cầu khai báo rõ.
- Vì method có thể thay đổi
- Thread safety giữa struct và class?
- Struct dễ an toàn hơn do copy semantics. Class cần đồng bộ khi chia sẻ.
- SwiftUI vì sao dùng struct cho View?
- View là mô tả thuần. Dễ diff, rẻ khi copy, tối ưu render.
