NotificationCenter là một cơ chế hệ thống trong iOS để gửi và nhận thông báo giữa các thành phần ứng dụng mà không cần kết nối trực tiếp giữa người gửi và người nhận. Dựa trên mẫu Observer, NotificationCenter cho phép các đối tượng đăng ký sự kiện và phản hồi chúng một cách bất đồng bộ. Theo Tài liệu Apple (2025), NSNotificationCenter hỗ trợ cả gửi thông báo đồng bộ qua post(name:object:) và gửi trì hoãn qua NotificationQueue. Trung tâm thông báo hoạt động trong một tiến trình duy nhất và không vượt qua ranh giới ứng dụng.
Các điểm chính
NotificationCenter (NSNotificationCenter) là một cơ chế tích hợp trong iOS để triển khai giao tiếp kết nối lỏng lẻo giữa các đối tượng. Mẫu Observer cho phép một đối tượng (người gửi) thông báo cho nhiều đối tượng khác (người quan sát) về một sự kiện mà không cần tham chiếu trực tiếp đến chúng. NotificationCenter hoạt động với ba thực thể: Notification.Name (định danh thông báo), Notification (vùng chứa dữ liệu) và NotificationCenter (bộ phân phối). Mỗi ứng dụng có một default center dùng chung.
Notification.Name là một cấu trúc xác định loại thông báo. Được tạo qua extension Name: Notification.Name(“MyNotification”). Notification là một đối tượng chứa name, object (người gửi) và userInfo (từ điển dữ liệu). Các thông báo hệ thống được khai báo dưới dạng hằng số: UIApplication.didBecomeActiveNotification, UIResponder.keyboardWillShowNotification. Các thông báo tùy chỉnh nên được nhóm qua extension để tránh xung đột tên. Tên nên có định dạng tên miền ngược.
// Định nghĩa thông báo tùy chỉnh
extension Notification.Name {
static let dataDidUpdate =
Notification.Name("com.app.dataDidUpdate")
static let userLoggedOut =
Notification.Name("com.app.userLoggedOut")
}
// Gửi thông báo với dữ liệu
let userInfo: [String: Any] = [
"userId": 123,
"timestamp": Date()
]
NotificationCenter.default.post(
name: .dataDidUpdate,
object: nil,
userInfo: userInfo
)
Người quan sát đăng ký nhận thông báo qua phương thức addObserver(_:selector:name:object:). Selector là phương thức sẽ được gọi khi nhận được thông báo. Tham số object cho phép lọc thông báo từ một người gửi cụ thể. Nếu object là nil, người quan sát nhận tất cả thông báo có tên chỉ định từ bất kỳ người gửi nào. Từ iOS 9, addObserver không yêu cầu xóa thủ công cho API block-based, nhưng selector-based vẫn yêu cầu removeObserver.
// Đăng ký thông báo (dựa trên selector)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleDataUpdate),
name: .dataDidUpdate,
object: nil
)
@objc func handleDataUpdate(_ notification: Notification) {
guard let userId = notification.userInfo?["userId"] as? Int else { return }
updateUI(for: userId)
}
// Đăng ký thông báo (dựa trên block, iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
forName: .dataDidUpdate,
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
self.handleNotification(notification)
}
NotificationCenter lưu trữ một bảng ánh xạ (tên → tập hợp người quan sát). Khi người gửi gọi post(name:object:), trung tâm thông báo đồng bộ duyệt qua tất cả người quan sát đã đăng ký tên đó và gọi selector hoặc block của họ. Đặc điểm chính: post chặn luồng hiện tại cho đến khi tất cả trình xử lý hoàn thành. Nếu trình xử lý thực hiện các thao tác nặng, điều này làm chậm người gửi. NotificationQueue giải quyết vấn đề này bằng cách trì hoãn việc phân phối thông báo.
Phương thức post(name:object:userInfo:) gửi thông báo ngay lập tức đến tất cả người quan sát. Lời gọi là đồng bộ — mã sau post chỉ thực thi sau khi tất cả trình xử lý hoàn thành. Thứ tự gọi người quan sát không được đảm bảo và có thể thay đổi giữa các lần chạy. Để xử lý tuần tự, hãy sử dụng NotificationQueue với coalescing. Không gọi post bên trong trình xử lý của cùng một thông báo — điều này dẫn đến đệ quy vô hạn.
NotificationQueue thêm thông báo vào hàng đợi để phân phối bất đồng bộ. Nó hỗ trợ coalescing (hợp nhất các thông báo giống hệt nhau) và chọn hàng đợi phân phối (asap, idle, modal). Coalescing hữu ích cho các sự kiện thường xuyên (tiến trình tải xuống) khi bạn chỉ cần thông báo với giá trị mới nhất. NotificationQueue sử dụng vòng lặp run để kích hoạt, do đó chỉ hoạt động trong các luồng có vòng lặp run đang hoạt động.
// Gửi trì hoãn qua NotificationQueue
let notification = Notification(
name: .dataDidUpdate,
object: self,
userInfo: ["progress": 0.5]
)
// Coalescing: nhiều thông báo được hợp nhất thành một
NotificationQueue.default.enqueue(
notification,
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
// Phân phối bất đồng bộ qua DispatchQueue
DispatchQueue.main.async {
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}
iOS cung cấp ba cơ chế chính để giao tiếp giữa các đối tượng: NotificationCenter, Delegate và KVO (Key-Value Observing). Mỗi cơ chế giải quyết vấn đề thông báo nhưng với các đánh đổi khác nhau về mức độ kết nối, hiệu suất và an toàn kiểu. Việc lựa chọn cơ chế phụ thuộc vào mối quan hệ một-một hoặc một-nhiều và nhu cầu truyền dữ liệu.
| Đặc tính | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| Kết nối | Lỏng lẻo (tên thông báo) | Chặt chẽ (giao thức) | Trung bình (khóa) |
| Quan hệ | Một-nhiều | Một-một | Một-nhiều |
| An toàn kiểu | Thấp (userInfo là Dictionary) | Cao (phương thức giao thức) | Trung bình (Any?) |
| Hiệu suất | Trung bình (duyệt bảng) | Cao (gọi trực tiếp) | Thấp (NSObject) |
| Bất đồng bộ | Đồng bộ (post chặn) | Đồng bộ trong luồng người gửi | Đồng bộ khi thay đổi |
NotificationCenter lý tưởng cho các sự kiện mà nhiều thành phần độc lập cần phản hồi. Ví dụ: thay đổi cài đặt ứng dụng, đăng xuất người dùng, nhận thông báo push trong nền. NotificationCenter cũng phù hợp cho các mô-đun kết nối lỏng lẻo (tính năng A không cần biết về tính năng B). Nhược điểm là thiếu an toàn kiểu: khóa userInfo là chuỗi, không phải enum.
Delegate là lựa chọn cho mối quan hệ một-một với hợp đồng rõ ràng (tableView.delegate). Delegate nhanh hơn và an toàn hơn về kiểu. KVO là lựa chọn để quan sát thay đổi của một thuộc tính cụ thể của mô hình (isLoading, progress). KVO yêu cầu kế thừa từ NSObject và có thể gây khó khăn khi gỡ lỗi (chuỗi khóa ma thuật). Trong Swift hiện đại, Combine và chuỗi async thay thế cả ba cách tiếp cận.
Phương thức addObserver hỗ trợ hai biến thể đăng ký: selector-based (truyền thống) và block-based (với closure). Selector-based yêu cầu tương thích @objc và xóa thủ công người quan sát. Block-based (iOS 9+) cho phép sử dụng capture list và được OS tự động quản lý khi sử dụng block không có tham chiếu mạnh. Block-based cũng hỗ trợ queue — người quan sát nhận thông báo trong hàng đợi được chỉ định.
Cách đăng ký truyền thống qua selector. Phương thức xử lý phải được đánh dấu @objc và chấp nhận một Notification tùy chọn. Ưu điểm: có thể được sử dụng bởi bất kỳ lớp nào, bao gồm Objective-C kế thừa. Nhược điểm: thiếu an toàn kiểu của selector, nguy cơ sai chính tả trong tên selector, removeObserver bắt buộc trong deinit. Nếu người quan sát bị xóa trước đối tượng, trình xử lý sẽ không được gọi.
API block-based chấp nhận một closure thực thi khi nhận được thông báo. Tham số queue xác định hàng đợi nào block sẽ chạy — hàng đợi chính cho cập nhật UI hoặc hàng đợi nền cho xử lý dữ liệu. Giá trị trả về NSObjectProtocol được sử dụng để xóa người quan sát: NotificationCenter.default.removeObserver(observer). Block-based được ưu tiên trong Swift hiện đại.
protocol NotificationToken {
func dispose()
}
extension NotificationCenter {
func observe(
name: NSNotification.Name,
object: Any? = nil,
queue: OperationQueue? = .main,
using block: @escaping (Notification) -> Void
) -> NotificationToken {
let observer = addObserver(forName: name, object: object,
queue: queue, using: block)
return NotificationTokenWrapper(observer: observer, center: self)
}
}
// Sử dụng với xóa tự động
class ViewModel {
private var tokens: [NotificationToken] = []
func startObserving() {
let token = NotificationCenter.default.observe(
name: .dataDidUpdate,
queue: .main
) { [weak self] notification in
self?.handleUpdate(notification)
}
tokens.append(token)
}
deinit {
tokens.forEach { $0.dispose() }
}
}
Rò rỉ bộ nhớ là một trong những vấn đề chính khi làm việc với NotificationCenter. Nếu người quan sát không bị xóa trước khi giải phóng bộ nhớ, khi gửi thông báo, trung tâm sẽ cố gọi một phương thức trên đối tượng đã được giải phóng, dẫn đến EXC_BAD_ACCESS. Từ iOS 9, block-based addObserver sử dụng tham chiếu yếu, nhưng selector-based vẫn yêu cầu removeObserver thủ công. Thực hành tốt nhất: xóa người quan sát trong deinit.
Selector-based: luôn gọi NotificationCenter.default.removeObserver(self) trong deinit. Nếu người quan sát đăng ký nhiều thông báo, bạn có thể xóa tất cả cùng lúc (không tham số) hoặc một thông báo cụ thể theo tên. Block-based: xóa qua removeObserver với token trả về từ addObserver. Đối với block-based trên iOS 9+, không xảy ra rò rỉ, nhưng việc xóa vẫn được khuyến nghị cho hiệu suất: người quan sát đã giải phóng sẽ không bị duyệt trong quá trình post.
class SafeObserver {
private var observers: [NSObjectProtocol] = []
func addSubscriptions() {
let token1 = NotificationCenter.default.addObserver(
forName: .dataDidUpdate, object: nil,
queue: .main) { [weak self] _ in
self?.refreshData()
}
let token2 = NotificationCenter.default.addObserver(
forName: .userLoggedOut, object: nil,
queue: .main) { [weak self] _ in
self?.logout()
}
observers.append(contentsOf: [token1, token2])
}
deinit {
observers.forEach { NotificationCenter.default.removeObserver($0) }
}
private func refreshData() { }
private func logout() { }
}
Mẫu Token tự động hóa việc quản lý người quan sát. Khi đăng ký, một đối tượng token (NSObjectProtocol) được trả về, tự động xóa người quan sát khi giải phóng bộ nhớ. NotificationTokenWrapper lưu trữ tham chiếu yếu đến NotificationCenter và token người quan sát, gọi removeObserver trong deinit. Điều này đưa NotificationCenter đến gần hơn với cách tiếp cận Combine, nơi AnyCancellable quản lý vòng đời đăng ký.
An toàn luồng: NotificationCenter đảm bảo rằng post có thể được gọi từ bất kỳ luồng nào và tất cả người quan sát sẽ nhận thông báo trên cùng luồng nơi post được gọi. Điều này rất quan trọng cho các ứng dụng đa luồng: nếu thông báo được gửi từ luồng nền, trình xử lý cũng sẽ thực thi trên luồng nền. Để cập nhật UI, hãy điều phối xử lý đến hàng đợi chính qua DispatchQueue.main.async.
NotificationCenter an toàn luồng cho các lời gọi post và addObserver từ các luồng khác nhau. Đồng bộ hóa nội bộ sử dụng khóa, do đó các post thường xuyên từ nhiều luồng có thể tạo ra tranh chấp. Đối với các kịch bản tải cao (tiến trình tải xuống 1000 tệp), hãy sử dụng hàng đợi thông báo riêng hoặc publisher Combine. NotificationQueue với postingStyle .now tương đương với post trực tiếp.
NotificationCenter hỗ trợ publisher Combine qua NotificationCenter.default.publisher(for:object:). Publisher biến mỗi thông báo thành một sự kiện Combine có thể được biến đổi qua map, filter, debounce và throttle. Điều này giải quyết vấn đề phân phối đồng bộ: Combine xử lý thông báo bất đồng bộ trên Scheduler được chỉ định. NotificationCenter.publisher là cầu nối giữa cơ chế kế thừa và lập trình phản ứng hiện đại.
import Combine
class ReactiveViewModel {
private var cancellables = Set<AnyCancellable>()
func setupCombineSubscription() {
NotificationCenter.default
.publisher(for: .dataDidUpdate)
.receive(on: DispatchQueue.main)
.compactMap { $0.userInfo?["progress"] as? Float }
.debounce(for: .seconds(0.3), scheduler: RunLoop.main)
.sink { [weak self] progress in
self?.progressLabel.text = "\(Int(progress * 100))%"
}
.store(in: &cancellables)
}
}
Câu hỏi thường gặp
Có, NotificationCenter an toàn luồng cho các lời gọi post và addObserver từ bất kỳ luồng nào. Tuy nhiên, trình xử lý thực thi trên cùng luồng nơi post được gọi. Để cập nhật UI, hãy sử dụng queue: .main trong block-based addObserver hoặc DispatchQueue.main.async bên trong trình xử lý. Publisher Combine với receive(on:) cũng giải quyết vấn đề luồng.
Selector-based: crash EXC_BAD_ACCESS khi gửi thông báo sau khi người quan sát được giải phóng. Block-based (iOS 9+): không rò rỉ nhờ tham chiếu yếu, nhưng trung tâm thông báo vẫn giữ block trong bộ nhớ cho đến khi removeObserver rõ ràng. Nên luôn xóa người quan sát trong deinit hoặc sử dụng mẫu Token để quản lý tự động.
NotificationCenter là cơ chế phát sóng cho các sự kiện tùy ý giữa các thành phần không liên quan. KVO quan sát thay đổi của một thuộc tính cụ thể của một đối tượng cụ thể. KVO yêu cầu kế thừa NSObject và tự động thông báo khi thay đổi thuộc tính qua setter. NotificationCenter chỉ thông báo khi post được gọi rõ ràng. Để quan sát mô hình, KVO hoặc Combine được ưa chuộng hơn.
Một default center cho mỗi tiến trình ứng dụng. Có thể tạo thêm trung tâm qua NotificationCenter(), nhưng trong thực tế, default dùng chung được sử dụng. Mỗi trung tâm hoạt động độc lập — post trong một trung tâm không được phân phối đến người quan sát của trung tâm khác. Để cách ly mô-đun, hãy sử dụng không gian tên Name riêng biệt qua tên thông báo tên miền ngược.
Một phần. Combine cung cấp NotificationCenter.Publisher, bao bọc NotificationCenter trong một luồng phản ứng. Combine giải quyết vấn đề đồng bộ (qua receive(on:)), thêm toán tử biến đổi và quản lý đăng ký tự động (AnyCancellable). Tuy nhiên, NotificationCenter vẫn cần cho các thông báo hệ thống iOS (UIApplication, UIKeyboard) và mã kế thừa. Combine là sự cải tiến, không phải sự thay thế.
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