willSet và didSet là các bộ quan sát thuộc tính (property observers) trong Swift cho phép thực thi mã trước và sau khi thay đổi giá trị của thuộc tính. Không giống như computed properties, các bộ quan sát không tính toán giá trị mới mà chỉ phản ứng với sự thay đổi. Theo tài liệu của Apple, The Swift Programming Language (2026), các bộ quan sát không thể thiếu trong việc xác thực dữ liệu, đồng bộ hóa giao diện và ghi nhật ký thay đổi trong mã.
Những điểm chính
willSet và didSet là các bộ quan sát thuộc tính (property observers) trong Swift, một cơ chế tích hợp để theo dõi các thay đổi của stored properties.
Không giống như các ngôn ngữ khác yêu cầu triển khai setter thủ công hoặc hệ thống callback, Swift cung cấp cú pháp khai báo để phản ứng với các thay đổi. Các bộ quan sát được thêm trực tiếp sau khai báo thuộc tính và không yêu cầu gọi riêng.
Theo Tài liệu dành cho nhà phát triển Apple (2026), property observers được hỗ trợ cho stored properties của bất kỳ lớp, cấu trúc hoặc liệt kê nào. Chúng không hoạt động với computed properties vì các thuộc tính này không lưu trữ giá trị — đối với chúng, phản ứng với thay đổi được triển khai trực tiếp trong setter.
willSet được gọi ngay trước khi gán giá trị mới cho thuộc tính. Bên trong willSet, tham số ngầm định newValue có sẵn, chứa giá trị sẽ được đặt. Tại thời điểm này, giá trị hiện tại của thuộc tính chưa thay đổi — bạn có thể đọc giá trị cũ trực tiếp qua thuộc tính.
Theo đề xuất Swift Evolution SE-0001 (2024), willSet cung cấp khả năng thực hiện xác thực hoặc ghi nhật ký trước khi thay đổi thực tế. Nếu một ngoại lệ được ném ra trong willSet, giá trị mới sẽ không được áp dụng, biến các bộ quan sát thành cơ chế bảo vệ dữ liệu.
didSet được gọi ngay sau khi gán giá trị mới. Bên trong didSet, tham số ngầm định oldValue có sẵn, chứa giá trị trước khi thay đổi. Tại thời điểm này, thuộc tính đã chứa giá trị mới và bạn có thể so sánh nó với giá trị cũ.
Theo Swift by Sundell (2025), didSet là bộ quan sát phổ biến nhất vì nó thường cần thiết nhất cho xử lý hậu kỳ: cập nhật giao diện người dùng, tính toán lại các trường phụ thuộc hoặc gửi dữ liệu lên máy chủ sau khi thay đổi.
Property observers được khai báo ngay sau thuộc tính bằng cách sử dụng dấu ngoặc nhọn. Cú pháp tối thiểu chỉ yêu cầu một bộ quan sát, nhưng bạn có thể khai báo cả hai.
var score: Int = 0 {
willSet {
print("Điểm số sẽ thay đổi thành \(newValue)")
}
didSet {
print("Điểm số đã thay đổi từ \(oldValue) thành \(score)")
}
}
Cả hai bộ quan sát đều tùy chọn — bạn có thể chỉ định chỉ willSet hoặc chỉ didSet. Đối với willSet, bạn có thể đổi tên newValue bằng cách chỉ định tên trong ngoặc đơn.
var username: String = "guest" {
willSet(newName) {
print("Sắp đặt \(newName)")
}
didSet(oldName) {
print("Trước là \(oldName), bây giờ là \(username)")
}
}
Theo Swift Language Guide (2026), việc đổi tên tham số cải thiện khả năng đọc mã, đặc biệt khi thuộc tính và các bộ quan sát được sử dụng trong một dự án lớn với tên dài.
Thứ tự thực thi được xác định nghiêm ngặt: đầu tiên willSet (giá trị cũ có sẵn), sau đó gán, sau đó didSet (giá trị mới có sẵn).
class Temperature {
var celsius: Double = 0.0 {
willSet {
print("Nhiệt độ sẽ thay đổi từ \(celsius) thành \(newValue)")
}
didSet {
if celsius > 100.0 {
print("Đã vượt quá điểm sôi!")
}
}
}
}
Một hạn chế quan trọng: các bộ quan sát không được gọi trong quá trình khởi tạo thuộc tính khi tạo một phiên bản. Bên trong init, việc gán giá trị không kích hoạt willSet và didSet — điều này ngăn chặn các tác dụng phụ không mong muốn ở giai đoạn xây dựng.
Theo Apple Swift Blog (2025), hành vi này khác với nhiều ngôn ngữ khác nơi setter được gọi ngay cả trong hàm tạo. Swift chọn sự an toàn: các bộ quan sát chỉ bắt đầu hoạt động sau khi quá trình khởi tạo đối tượng hoàn tất.
Việc thay đổi thuộc tính bên trong didSet có thể dẫn đến các cuộc gọi bộ quan sát lặp đi lặp lại. Swift không chặn đệ quy — lập trình viên phải tự kiểm soát nó.
var counter: Int = 0 {
didSet {
if counter < 5 {
counter += 1
}
}
}
Mã như vậy sẽ tạo ra đệ quy hoàn toàn với tràn bộ nhớ đệm nếu không có điều kiện thoát được cung cấp. Theo Cộng đồng Stack Overflow Swift (2025), đây là một trong những lỗi phổ biến nhất mà người mới bắt đầu mắc phải khi làm việc với property observers.
Cấu trúc hỗ trợ willSet và didSet cho stored properties mà không có hạn chế. Điều quan trọng cần nhớ là cấu trúc là kiểu giá trị và việc sửa đổi thuộc tính bên trong một phương thức mutating cũng kích hoạt các bộ quan sát.
struct Point {
var x: Double = 0.0 {
didSet {
print("X đã thay đổi thành \(x)")
}
}
var y: Double = 0.0 {
didSet {
print("Y đã thay đổi thành \(y)")
}
}
}
var point = Point()
point.x = 5.0
Các bộ quan sát thuộc tính được sử dụng trong các dự án Swift cho nhiều tác vụ: từ đồng bộ hóa giao diện người dùng đến xác thực dữ liệu và ghi nhật ký.
didSet cho phép bạn quay lại hoặc sửa giá trị không hợp lệ ngay sau khi nó được đặt. Điều này thay thế các setter cồng kềnh trong Objective-C và đảm bảo tính toàn vẹn dữ liệu ở cấp độ mô hình.
var age: Int = 0 {
didSet {
if age < 0 || age > 150 {
age = oldValue
}
}
}
Khi một thuộc tính liên quan thay đổi, bạn có thể tự động cập nhật các phần tử giao diện mà không cần gọi phương thức cập nhật riêng.
var userName: String = "" {
didSet {
nameLabel.text = userName
}
}
willSet thuận tiện cho việc ghi nhật ký nhằm mục đích gỡ lỗi hoặc kiểm toán. Bạn có thể ghi lại thời gian và giá trị mới trước khi nó được áp dụng, đảm bảo nhật ký chứa dữ liệu gốc để phân tích.
Theo objc.io (2025), property observers đặc biệt hữu ích trong các kiến trúc có luồng dữ liệu một chiều, nơi mọi thay đổi thuộc tính đều được ghi lại để tái tạo trạng thái sau này.
Computed properties tính toán giá trị ngay lập tức và không có bộ nhớ lưu trữ, trong khi willSet và didSet hoạt động trên stored property với giá trị thực tế.
| Đặc điểm | Property Observers | Computed Properties |
|---|---|---|
| Lưu trữ giá trị | Có | Không |
| Thực thi mã khi thay đổi | Có | Không |
| Kiểu khai báo | var | var (get/set) |
| Tham số truy cập | newValue, oldValue | newValue (trong set) |
| Khởi tạo | Yêu cầu giá trị ban đầu | Không yêu cầu |
Sự khác biệt chính: computed property tính toán giá trị mỗi lần truy cập, trong khi property observer phản ứng với sự thay đổi của giá trị hiện có. Sự lựa chọn giữa chúng được quyết định bởi ngữ nghĩa — nếu thuộc tính được suy ra từ dữ liệu khác, hãy sử dụng computed; nếu nó là một giá trị độc lập cần theo dõi các thay đổi, hãy sử dụng willSet/didSet.
Lỗi phổ biến nhất là gọi đệ quy didSet mà không có điều kiện thoát. Mỗi lần thay đổi thuộc tính bên trong didSet sẽ kích hoạt lại bộ quan sát, dẫn đến vòng lặp vô hạn.
Lỗi phổ biến thứ hai là cố gắng sử dụng bộ quan sát trên các thuộc tính let. Trình biên dịch Swift sẽ báo lỗi vì let là hằng số.
Lỗi thứ ba là bỏ qua việc bộ quan sát không được gọi trong quá trình khởi tạo. Các nhà phát triển mong đợi willSet kích hoạt bên trong init sẽ gặp hành vi không mong đợi.
Vấn đề thứ tư là áp dụng bộ quan sát cho các thuộc tính trong extension. Swift cấm thêm willSet/didSet vào stored properties trong extension.
Theo Ray Wenderlich (2025), hiểu những hạn chế này giúp tránh lỗi ở giai đoạn đầu và làm cho mã Swift dễ dự đoán hơn.
Câu hỏi thường gặp
Có, mỗi bộ quan sát là tùy chọn. Bạn có thể khai báo chỉ willSet, chỉ didSet hoặc cả hai cùng lúc.
Không, Swift cấm thêm willSet và didSet trong extension cho stored properties. Các bộ quan sát chỉ được khai báo trong định nghĩa kiểu ban đầu.
Không, các bộ quan sát không được gọi trong quá trình khởi tạo. Đây là cơ chế bảo vệ ngăn chặn các tác dụng phụ ở giai đoạn xây dựng đối tượng.
willSet là bộ quan sát stored property thực thi trước khi giá trị thay đổi. Setter trong computed property là cách tính toán giá trị mới, không phải quan sát giá trị hiện có.
Việc gọi didSet lặp lại sẽ gây ra đệ quy. Nếu không có điều kiện thoát, điều này dẫn đến tràn bộ nhớ đệm và kết thúc chương trình.
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