定义
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(主题/被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会收到通知并自动更新。
观察者模式也被称为发布-订阅模式(Publish-Subscribe Pattern)。
为什么需要观察者模式
观察者模式要解决的核心问题是:当一个对象的状态发生变化时,如何自动通知所有依赖它的对象。
问题场景:假设我们正在开发一个电商App,用户登录后需要更新多个页面的状态(首页显示用户名、购物车同步、个人中心更新等)。
最直接的方式可能是这样:
class LoginService {
func login(username: String, password: String) {
// 登录成功后...
let user = User(name: username)
// 直接调用各个需要更新的对象
HomeViewController.shared.updateUser(user)
CartViewController.shared.syncCart(for: user)
ProfileViewController.shared.refreshProfile(user)
// 如果还有其他需要通知的地方,继续添加...
}
}
这种方式有什么问题?
- 紧耦合:LoginService必须知道所有需要通知的对象,依赖关系错综复杂
- 难以维护:每新增一个需要监听登录事件的页面,都要修改LoginService
- 违反开闭原则:对扩展不开放,每次扩展都要修改已有代码
- 单例滥用:为了让LoginService能访问到各个页面,不得不使用大量单例
观察者模式的解决思路:
被观察者(Subject)只负责"广播"状态变化,不关心谁在监听;观察者(Observer)自己注册感兴趣的事件:
// 使用NotificationCenter实现
class LoginService {
func login(username: String, password: String) {
let user = User(name: username)
// 只负责发送通知,不关心谁在监听
NotificationCenter.default.post(
name: .userDidLogin,
object: nil,
userInfo: ["user": user]
)
}
}
// 各个页面自己注册监听
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleUserLogin),
name: .userDidLogin,
object: nil
)
}
@objc func handleUserLogin(_ notification: Notification) {
if let user = notification.userInfo?["user"] as? User {
updateUI(with: user)
}
}
}
观察者模式的好处:
- 松耦合:发布者和订阅者相互独立,互不知道对方的存在
- 易于扩展:新增订阅者不需要修改发布者的代码
- 支持广播:一次状态变化可以通知多个观察者
- 动态订阅:观察者可以随时注册和取消监听
模式结构
classDiagram
class Subject {
<<interface>>
+attach(observer: Observer)
+detach(observer: Observer)
+notify()
}
class ConcreteSubject {
-state
-observers: [Observer]
+attach(observer: Observer)
+detach(observer: Observer)
+notify()
+getState()
+setState()
}
class Observer {
<<interface>>
+update(subject: Subject)
}
class ConcreteObserverA {
+update(subject: Subject)
}
class ConcreteObserverB {
+update(subject: Subject)
}
Subject <|.. ConcreteSubject
Observer <|.. ConcreteObserverA
Observer <|.. ConcreteObserverB
ConcreteSubject --> Observer : notifies
基本实现
Swift实现
// 观察者协议
protocol Observer: AnyObject {
func update(subject: Subject)
}
// 主题协议
protocol Subject {
func attach(_ observer: Observer)
func detach(_ observer: Observer)
func notify()
}
// 具体主题
class ConcreteSubject: Subject {
var state: Int = 0 {
didSet {
notify()
}
}
private var observers: [Observer] = []
func attach(_ observer: Observer) {
observers.append(observer)
}
func detach(_ observer: Observer) {
observers.removeAll { $0 === observer }
}
func notify() {
observers.forEach { $0.update(subject: self) }
}
}
// 具体观察者A
class ConcreteObserverA: Observer {
func update(subject: Subject) {
if let concreteSubject = subject as? ConcreteSubject {
print("Observer A: Reacted to state \(concreteSubject.state)")
}
}
}
// 具体观察者B
class ConcreteObserverB: Observer {
func update(subject: Subject) {
if let concreteSubject = subject as? ConcreteSubject {
print("Observer B: Reacted to state \(concreteSubject.state)")
}
}
}
// 使用
let subject = ConcreteSubject()
let observerA = ConcreteObserverA()
let observerB = ConcreteObserverB()
subject.attach(observerA)
subject.attach(observerB)
subject.state = 1
// Output:
// Observer A: Reacted to state 1
// Observer B: Reacted to state 1
subject.detach(observerA)
subject.state = 2
// Output:
// Observer B: Reacted to state 2
iOS中的观察者模式
1. KVO(Key-Value Observing)
KVO是Cocoa框架提供的观察者模式实现,允许对象观察另一个对象特定属性的变化。
class Person: NSObject {
@objc dynamic var name: String = ""
@objc dynamic var age: Int = 0
}
class PersonObserver: NSObject {
private var observation: NSKeyValueObservation?
private var observations: [NSKeyValueObservation] = []
func observe(person: Person) {
// 使用新API观察name
let nameObservation = person.observe(\.name, options: [.new, .old]) { person, change in
print("Name changed from \(change.oldValue ?? "") to \(change.newValue ?? "")")
}
observations.append(nameObservation)
// 观察age
let ageObservation = person.observe(\.age, options: [.new]) { person, change in
print("Age changed to \(change.newValue ?? 0)")
}
observations.append(ageObservation)
}
func stopObserving() {
observations.forEach { $0.invalidate() }
observations.removeAll()
}
}
// 使用
let person = Person()
let observer = PersonObserver()
observer.observe(person: person)
person.name = "John" // Name changed from to John
person.age = 30 // Age changed to 30
KVO注意事项
// 1. 必须继承NSObject
// 2. 属性需要@objc dynamic修饰
// 3. 必须在dealloc时移除观察者(旧API)
// 4. 使用新API时,需要保持observation对象
// 手动触发KVO
class ManualKVOExample: NSObject {
private var _value: Int = 0
@objc dynamic var value: Int {
get { return _value }
set {
willChangeValue(forKey: "value")
_value = newValue
didChangeValue(forKey: "value")
}
}
}
2. NotificationCenter
NotificationCenter是iOS中另一种观察者模式的实现,它是一个广播机制,允许对象在不知道接收者是谁的情况下发送通知。
基本使用
// 定义通知名称
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
static let userDidLogout = Notification.Name("userDidLogout")
static let dataDidUpdate = Notification.Name("dataDidUpdate")
}
// 发送通知
class AuthService {
func login(user: User) {
// 登录逻辑...
// 发送通知
NotificationCenter.default.post(
name: .userDidLogin,
object: self,
userInfo: ["user": user]
)
}
func logout() {
NotificationCenter.default.post(name: .userDidLogout, object: self)
}
}
// 接收通知
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 添加观察者
NotificationCenter.default.addObserver(
self,
selector: #selector(handleUserLogin(_:)),
name: .userDidLogin,
object: nil
)
}
@objc private func handleUserLogin(_ notification: Notification) {
if let user = notification.userInfo?["user"] as? User {
updateUI(with: user)
}
}
deinit {
// iOS 9+自动移除,但显式移除是好习惯
NotificationCenter.default.removeObserver(self)
}
}
使用闭包方式
class SettingsViewController: UIViewController {
private var loginObserver: NSObjectProtocol?
private var logoutObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
loginObserver = NotificationCenter.default.addObserver(
forName: .userDidLogin,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleLogin(notification)
}
logoutObserver = NotificationCenter.default.addObserver(
forName: .userDidLogout,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleLogout(notification)
}
}
private func handleLogin(_ notification: Notification) {
print("User logged in")
}
private func handleLogout(_ notification: Notification) {
print("User logged out")
}
deinit {
if let observer = loginObserver {
NotificationCenter.default.removeObserver(observer)
}
if let observer = logoutObserver {
NotificationCenter.default.removeObserver(observer)
}
}
}
自定义NotificationCenter
class CustomNotificationCenter {
static let `default` = CustomNotificationCenter()
private var observers: [Notification.Name: [(object: AnyObject?, block: (Notification) -> Void)]] = [:]
private let queue = DispatchQueue(label: "notification.queue", attributes: .concurrent)
private init() {}
func addObserver(
forName name: Notification.Name,
object: AnyObject? = nil,
block: @escaping (Notification) -> Void
) {
queue.async(flags: .barrier) {
if self.observers[name] == nil {
self.observers[name] = []
}
self.observers[name]?.append((object: object, block: block))
}
}
func post(name: Notification.Name, object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) {
let notification = Notification(name: name, object: object, userInfo: userInfo)
queue.sync {
guard let observerList = observers[name] else { return }
for observer in observerList {
if observer.object == nil || observer.object === object as AnyObject {
observer.block(notification)
}
}
}
}
}
3. Combine框架
Combine是Apple在iOS 13引入的响应式编程框架,提供了声明式的方式处理异步事件和数据流。
Publisher和Subscriber
import Combine
// 使用PassthroughSubject
class DataService {
// 可以手动发送值的Publisher
let dataPublisher = PassthroughSubject<[String], Error>()
// 带有当前值的Publisher
let currentUser = CurrentValueSubject<User?, Never>(nil)
func fetchData() {
// 模拟网络请求
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.dataPublisher.send(["Item 1", "Item 2", "Item 3"])
}
}
func login(user: User) {
currentUser.send(user)
}
func logout() {
currentUser.send(nil)
}
}
class DataViewController: UIViewController {
private let dataService = DataService()
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// 订阅数据变化
dataService.dataPublisher
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed")
case .failure(let error):
print("Error: \(error)")
}
},
receiveValue: { data in
print("Received data: \(data)")
}
)
.store(in: &cancellables)
// 订阅用户状态
dataService.currentUser
.sink { user in
if let user = user {
print("User logged in: \(user.name)")
} else {
print("User logged out")
}
}
.store(in: &cancellables)
}
}
@Published属性包装器
import Combine
class UserViewModel: ObservableObject {
@Published var username: String = ""
@Published var isLoggedIn: Bool = false
@Published var errorMessage: String?
}
class LoginViewController: UIViewController {
private let viewModel = UserViewModel()
private var cancellables = Set<AnyCancellable>()
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var errorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// 观察username变化
viewModel.$username
.receive(on: DispatchQueue.main)
.sink { [weak self] username in
self?.usernameLabel.text = username
}
.store(in: &cancellables)
// 观察错误消息
viewModel.$errorMessage
.compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] message in
self?.errorLabel.text = message
}
.store(in: &cancellables)
// 组合多个Publisher
viewModel.$username
.combineLatest(viewModel.$isLoggedIn)
.sink { username, isLoggedIn in
print("Username: \(username), Logged in: \(isLoggedIn)")
}
.store(in: &cancellables)
}
}
Combine操作符
import Combine
class SearchViewModel {
@Published var searchText: String = ""
private var cancellables = Set<AnyCancellable>()
let searchResults = PassthroughSubject<[SearchResult], Never>()
init() {
// 使用操作符处理搜索
$searchText
.debounce(for: .milliseconds(300), scheduler: RunLoop.main) // 防抖
.removeDuplicates() // 去重
.filter { !$0.isEmpty } // 过滤空字符串
.map { text -> AnyPublisher<[SearchResult], Never> in
// 执行搜索
return self.performSearch(query: text)
}
.switchToLatest() // 只关心最新的搜索结果
.sink { [weak self] results in
self?.searchResults.send(results)
}
.store(in: &cancellables)
}
private func performSearch(query: String) -> AnyPublisher<[SearchResult], Never> {
// 模拟搜索API
return Future { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
let results = [SearchResult(title: "Result for \(query)")]
promise(.success(results))
}
}
.eraseToAnyPublisher()
}
}
struct SearchResult {
let title: String
}
4. RxSwift(第三方库)
RxSwift是ReactiveX的Swift实现,提供了强大的响应式编程能力:
import RxSwift
import RxCocoa
class RxViewModel {
let items = BehaviorRelay<[String]>(value: [])
let isLoading = BehaviorRelay<Bool>(value: false)
let error = PublishRelay<Error>()
private let disposeBag = DisposeBag()
func loadData() {
isLoading.accept(true)
// 模拟网络请求
Observable<[String]>.create { observer in
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
observer.onNext(["Item 1", "Item 2", "Item 3"])
observer.onCompleted()
}
return Disposables.create()
}
.observe(on: MainScheduler.instance)
.subscribe(
onNext: { [weak self] items in
self?.items.accept(items)
},
onError: { [weak self] error in
self?.error.accept(error)
},
onCompleted: { [weak self] in
self?.isLoading.accept(false)
}
)
.disposed(by: disposeBag)
}
}
class RxViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
private let viewModel = RxViewModel()
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 绑定数据到TableView
viewModel.items
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, item, cell in
cell.textLabel?.text = item
}
.disposed(by: disposeBag)
// 绑定加载状态
viewModel.isLoading
.bind(to: activityIndicator.rx.isAnimating)
.disposed(by: disposeBag)
// 处理错误
viewModel.error
.subscribe(onNext: { [weak self] error in
self?.showError(error)
})
.disposed(by: disposeBag)
viewModel.loadData()
}
private func showError(_ error: Error) {
// 显示错误
}
}
使用场景
- 一对多的依赖关系:当一个对象的改变需要通知多个其他对象时
- 松耦合通信:当对象之间需要通信但又不想紧密耦合时
- 事件驱动系统:需要响应各种事件的系统
- 数据绑定:UI与数据模型之间的同步
- 跨模块通信:不同模块之间的消息传递
优缺点
优点
- 松耦合:观察者和被观察者之间松散耦合
- 支持广播通信:一个主题可以通知多个观察者
- 建立触发机制:状态变化自动触发更新
缺点
- 调试困难:事件流难以追踪
- 性能开销:大量观察者可能影响性能
- 循环依赖风险:不当使用可能导致循环调用
- 内存泄漏:忘记移除观察者可能导致内存泄漏
最佳实践
- 及时移除观察者:在对象销毁前移除观察者
- 使用弱引用:避免循环引用
- 合理选择实现方式:根据场景选择KVO、NotificationCenter或Combine
- 注意线程安全:在正确的线程处理回调
- 避免过度使用:不是所有场景都需要观察者模式
面试常见问题
Q1: KVO的实现原理是什么?
答:KVO基于isa-swizzling实现。当一个对象被观察时,系统会动态创建一个继承自该对象类的子类(NSKVONotifying_ClassName),并将对象的isa指针指向这个子类。子类重写了被观察属性的setter方法,在setter中调用willChangeValueForKey和didChangeValueForKey来通知观察者。
Q2: NotificationCenter和KVO的区别?
答:
- KVO是一对一或一对多的属性观察,自动触发,需要继承NSObject
- NotificationCenter是广播机制,一对多,需要手动发送通知,不限制类类型
- KVO观察的是属性变化,NotificationCenter观察的是自定义事件
Q3: Combine相比传统观察者模式有什么优势?
答:
- 类型安全,编译时检查
- 丰富的操作符支持数据转换和组合
- 内置背压处理和线程调度
- 声明式API,代码更简洁
- 统一的异步编程模型