Observer — các khái niệm chính của mẫu đăng ký thay đổi

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

Observer — một mẫu hành vi trong đó một đối tượng (nhà xuất bản) thông báo cho nhiều người đăng ký về những thay đổi trạng thái của nó. Trong phát triển di động, Observer là nền tảng của các cơ chế phản ứng: UI đăng ký thay đổi dữ liệu và tự động cập nhật. Mẫu này được triển khai trong NotificationCenter trên iOS và LiveData/Flow trên Android. Chi tiết thêm tại Refactoring Guru: Observer.

Những điểm chính

  • Observer — mẫu đăng ký: một nhà xuất bản, nhiều người đăng ký
  • NotificationCenter — triển khai Observer tích hợp trong iOS/macOS
  • Flow và LiveData — triển khai Observer phản ứng trong Android
  • Push vs Pull — nhà xuất bản có thể gửi dữ liệu hoặc thông báo về sự kiện
  • Rò rỉ bộ nhớ — người đăng ký phải hủy đăng ký để ngăn rò rỉ

Observer là gì: bản chất của mẫu quan sát

Observer — một mẫu hành vi GoF xác định mối quan hệ một-nhiều giữa các đối tượng. Khi một đối tượng (Subject hoặc Observable) thay đổi trạng thái, tất cả các đối tượng phụ thuộc (Observers) tự động được thông báo và cập nhật. Mẫu này triển khai sự kết nối lỏng lẻo: nhà xuất bản không biết các lớp cụ thể của người đăng ký — chỉ biết rằng họ triển khai giao diện Observer.

Cấu trúc Observer bao gồm giao diện Subject với các phương thức attach(), detach(), notify() và giao diện Observer với phương thức update(). ConcreteSubject lưu trữ trạng thái và danh sách người đăng ký. ConcreteObserver triển khai update() và phản ứng với các thay đổi. Trong phát triển di động, triển khai GoF cổ điển hiếm gặp — nó được thay thế bằng các cơ chế tích hợp: NotificationCenter, Combine, Flow, LiveData, triển khai cùng ý tưởng với API hiện đại.

Mô hình Push vs Pull — trong mô hình Push, Subject gửi dữ liệu đến tất cả người đăng ký (NotificationCenter.post). Trong mô hình Pull, Subject chỉ thông báo, và người đăng ký tự yêu cầu dữ liệu. Android LiveData sử dụng Push (dữ liệu được truyền trong observe()), RxJava/Flow hỗ trợ cả hai mô hình. Lựa chọn phụ thuộc vào nhiệm vụ: Push đơn giản hơn cho cập nhật UI, Pull hiệu quả hơn cho khối lượng dữ liệu lớn mà người đăng ký có thể không muốn nhận.

Observer trong iOS: NotificationCenter, Combine và KVO

NotificationCenter — một cơ chế tích hợp iOS/macOS để triển khai Observer. Nhà xuất bản gửi một Notification qua NotificationCenter.default.post(name:, object:, userInfo:). Người đăng ký đăng ký qua addObserver(forName:, queue:, using:). NotificationCenter hỗ trợ thông báo có tên (Notification.Name) và có thể truyền bất kỳ dữ liệu nào trong userInfo. UIKeyboardWillShowNotification, UIApplicationDidEnterBackgroundNotification — các ví dụ hệ thống.

swift
extension Notification.Name {
    static let userDidLogin = Notification.Name("userDidLogin")
}

// Nhà xuất bản
NotificationCenter.default.post(
    name: .userDidLogin,
    object: nil,
    userInfo: ["userId": "123"]
)

// Người đăng ký
class ProfileViewModel {
    private var observers: [NSObjectProtocol] = []

    func startObserving() {
        let observer = NotificationCenter.default.addObserver(
            forName: .userDidLogin,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            guard let userId = notification.userInfo?["userId"] as? String else { return }
            // Người đăng ký phản ứng với sự kiện
            self?.loadProfile(userId: userId)
        }
        observers.append(observer)
    }

    func stopObserving() {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
        observers.removeAll()
    }
}

Framework Combine — một giải pháp thay thế phản ứng hiện đại cho NotificationCenter, được giới thiệu trong iOS 13. Publisher (NotificationCenter, URLSession, Timer) — nhà xuất bản, Subscriber (sink, assign) — người đăng ký. Combine thêm các toán tử (map, filter, combineLatest) để chuyển đổi luồng dữ liệu. @Published — một property wrapper tự động thông báo cho người đăng ký về các thay đổi. Trong MVVM với SwiftUI, Combine thay thế NotificationCenter để liên kết ViewModel và View.

KVO (Key-Value Observing) — một cơ chế ObjC/Swift cũ để quan sát các thuộc tính riêng lẻ của đối tượng. @objc dynamic var name: String — một thuộc tính có thể quan sát. observe(.name) — đăng ký. KVO chỉ hoạt động với các lớp tương thích @objc và kế thừa ObjC. Apple khuyến nghị Combine và @Published thay vì KVO trong các dự án mới. KVO vẫn phù hợp cho khả năng tương thích UIKit trong các dự án kết hợp.

Observer trong Android: LiveData, StateFlow và SharedFlow

LiveData — một thành phần của Android Architecture Components để triển khai Observer. Một lớp có thể quan sát thông báo cho người đăng ký về thay đổi dữ liệu. LiveData nhận biết vòng đời: người đăng ký (LifecycleOwner) tự động hủy đăng ký khi bị hủy. LiveData sử dụng mô hình Push: dữ liệu được truyền trong observe(). LiveData là khối xây dựng cơ bản của MVVM trong Android trước Jetpack Compose.

kotlin
// ViewModel — nhà xuất bản
class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User?>(null)
    val user: LiveData<User?> = _user

    fun loadUser(id: String) {
        viewModelScope.launch {
            val result = userRepository.getUser(id)
            _user.value = result
        }
    }
}

// Fragment — người đăng ký
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.user.observe(viewLifecycleOwner) { user ->
            // Người đăng ký phản ứng với thay đổi
            userName.text = user?.name
            userEmail.text = user?.email
        }
    }
}

StateFlow và SharedFlow — các kiểu phản ứng từ Kotlin Coroutines đã thay thế LiveData trong Jetpack Compose. StateFlow — một trình giữ trạng thái có thể quan sát với giá trị hiện tại cố định. SharedFlow — một luồng nóng có thể cấu hình không có trạng thái, phù hợp cho các sự kiện một lần (điều hướng, toast). Cả hai kiểu đều tích hợp chặt chẽ với Compose: collectAsState(), collectAsEffect(). StateFlow là bắt buộc trong các dự án Android hiện đại với Compose.

LiveData vs StateFlow — LiveData gắn liền với vòng đời Android, StateFlow độc lập nền tảng. StateFlow hỗ trợ coroutines, toán tử (map, filter) và có thể kiểm tra mà không cần phụ thuộc Android. LiveData đơn giản hơn cho tương thích Java. Google khuyến nghị StateFlow cho các dự án mới trên Kotlin + Compose, LiveData để duy trì các dự án cũ hoặc mã Java.

Quản lý đăng ký và rò rỉ bộ nhớ

Rò rỉ bộ nhớ — vấn đề chính của Observer khi không có quản lý đăng ký phù hợp. Nếu người đăng ký (Activity, Fragment, UIViewController) bị hủy nhưng không hủy đăng ký, nhà xuất bản vẫn giữ tham chiếu đến nó và bộ thu gom rác không thể giải phóng bộ nhớ. Trong Android, LifecycleOwner (Activity/Fragment) nên gọi removeObserver() hoặc sử dụng observe(viewLifecycleOwner). Trong iOS — removeObserver trong deinit hoặc disposeBag trong Combine.

Nền tảngCơ chế ObserverHủy đăng ký tự độngHủy đăng ký thủ công
iOSNotificationCenterKhôngremoveObserver() trong deinit
iOSCombine (sink)Khôngstore(in: &bag) — DisposeBag
iOSKVOKhôngremoveObserver() trong deinit
AndroidLiveDataCó (LifecycleOwner)removeObserver() tùy chọn
AndroidStateFlowQua viewModelScopecancel() Job khi hủy
AndroidRxJavaKhôngdispose() trong CompositeDisposable

Tham chiếu yếu trong người đăng ký — khi sử dụng closure trong khối đăng ký, hãy sử dụng [weak self] trong Swift và tham chiếu phạm vi vòng đời trong Kotlin. LiveData tự động quản lý đăng ký qua LifecycleOwner — đăng ký chỉ hoạt động khi Lifecycle ở trạng thái STARTED hoặc RESUMED. StateFlow trong Compose sử dụng collectAsState() với nhận biết vòng đời. NotificationCenter trong iOS yêu cầu [weak self] rõ ràng vì closure giữ tham chiếu mạnh đến self.

Observer vs Nhà xuất bản-Người đăng ký: khác biệt

Observer (GoF) và Nhà xuất bản-Người đăng ký (PubSub) — các mẫu tương tự nhưng khác nhau. Trong Observer, nhà xuất bản thông báo trực tiếp cho người đăng ký bằng cách gọi phương thức của họ. Nhà xuất bản biết về người đăng ký (lưu trữ danh sách). Trong PubSub, nhà xuất bản và người đăng ký không biết nhau — một người trung gian (Event Bus, Message Queue, NotificationCenter) ở giữa họ. Nhà xuất bản gửi tin nhắn đến một kênh, người đăng ký lắng nghe kênh đó. PubSub có kết nối lỏng lẻo hơn.

Ví dụ về PubSub trong phát triển di động — NotificationCenter trong iOS có thể được coi là PubSub: nhà xuất bản không biết người đăng ký — nó chỉ đăng một thông báo. EventBus hoặc Otto trong Android (lỗi thời). SharedFlow với BroadcastChannel — PubSub trong thế giới Kotlin. Trong các hệ thống phân tán, PubSub được triển khai qua RabbitMQ, Kafka, Google PubSub. Đối với phát triển di động, PubSub hữu ích trong kiến trúc mô-đun nơi các mô-đun không nên phụ thuộc lẫn nhau.

Nên chọn gì — cho cập nhật UI (ViewModel → View), hãy sử dụng Observer (LiveData, StateFlow, @Published). Cho các sự kiện giữa các mô-đun (xác thực, đăng xuất, thay đổi chủ đề) — PubSub (SharedFlow, NotificationCenter, EventBus). Observer đơn giản và hiệu quả hơn trong một màn hình, PubSub linh hoạt hơn cho các sự kiện toàn cục nhưng khó gỡ lỗi hơn do các phụ thuộc ngầm.

Các câu hỏi thường gặp

StateFlow khác LiveData như thế nào?

StateFlow là một kiểu độc lập nền tảng từ Kotlin Coroutines, trong khi LiveData gắn liền với vòng đời Android. StateFlow hỗ trợ coroutines và toán tử, và có thể kiểm tra mà không cần Android. LiveData tự động quản lý đăng ký qua LifecycleOwner. Google khuyến nghị StateFlow cho các dự án mới trên Kotlin + Compose, và LiveData cho tương thích Java.

Làm thế nào để tránh rò rỉ bộ nhớ với NotificationCenter?

Sử dụng [weak self] trong closure xử lý và gọi removeObserver() trong deinit. Lưu trữ tham chiếu đến observer (NSObjectProtocol) và xóa nó khi đối tượng bị hủy. Trong Combine, sử dụng AnyCancellable và store(in:) để tự động hủy đăng ký khi DisposeBag được giải phóng.

Có thể sử dụng Observer trong SwiftUI mà không cần Combine không?

Có, SwiftUI hỗ trợ ObservableObject với @Published và @StateObject/@ObservedObject — đây là triển khai Observer tích hợp. @Published tự động thông báo cho View về các thay đổi. Combine không bắt buộc: ObservableObject sử dụng Publisher objectWillChange tích hợp trong SwiftUI. Combine thêm các toán tử để chuyển đổi luồng.

Khi nào nên sử dụng SharedFlow thay vì StateFlow?

SharedFlow — cho các sự kiện một lần (điều hướng, toast, Snackbar) nơi không cần giá trị hiện tại. StateFlow — cho trạng thái UI (danh sách dữ liệu, tiến trình tải) nơi cần ảnh chụp hiện tại. SharedFlow không có thuộc tính value và không trả về giá trị cuối cùng cho người đăng ký mới.

Sự khác biệt giữa KVO và Combine trong iOS là gì?

KVO là một cơ chế ObjC cũ, yêu cầu @objc dynamic và chỉ hoạt động với các lớp kế thừa từ NSObject. Combine là một framework Swift hiện đại, an toàn kiểu, với các toán tử và tích hợp SwiftUI. Combine thay thế KVO và NotificationCenter. Apple khuyến nghị Combine cho các dự án mới, KVO chỉ cho hỗ trợ kế thừa.

Tóm tắt

  • Observer — một mẫu hành vi để thông báo cho người đăng ký về thay đổi
  • iOS NotificationCenter — triển khai PubSub với thông báo có tên
  • iOS Combine — một framework phản ứng với Publisher và Subscriber
  • Android LiveData — Observer nhận biết vòng đời từ Android Architecture Components
  • Android StateFlow — một trình giữ trạng thái phản ứng cho Compose và coroutines
  • Quản lý bộ nhớ — hủy đăng ký bắt buộc để ngăn rò rỉ
  • Observer vs PubSub — đăng ký trực tiếp vs người trung gian cho kết nối lỏng lẻo

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