MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift观察者模式与通知中心优化

2023-05-223.2k 阅读

Swift 观察者模式概述

在软件开发中,观察者模式是一种行为设计模式,它允许对象(被观察对象)维护一系列依赖于它的对象(观察者),当被观察对象的状态发生变化时,自动通知所有观察者并使它们能够相应地更新。

在 Swift 中,观察者模式的实现通常依赖于协议(Protocol)和闭包(Closure)。通过定义一个协议,规定观察者需要实现的方法,被观察对象在状态变化时调用这些方法来通知观察者。

基本实现步骤

  1. 定义观察者协议:该协议声明观察者需要实现的方法,这些方法会在被观察对象状态改变时被调用。
  2. 创建被观察对象:被观察对象需要维护一个观察者列表,并提供方法来添加、移除观察者。当自身状态改变时,遍历观察者列表并调用观察者的相应方法。
  3. 实现观察者:具体的观察者类或结构体实现观察者协议中定义的方法,以便在接收到通知时执行相应的操作。

简单示例代码

// 1. 定义观察者协议
protocol Observer {
    func update(with message: String)
}

// 2. 创建被观察对象
class Subject {
    private var observers: [Observer] = []
    
    func addObserver(_ observer: Observer) {
        observers.append(observer)
    }
    
    func removeObserver(_ observer: Observer) {
        observers = observers.filter { $0!== observer }
    }
    
    func notifyObservers(with message: String) {
        for observer in observers {
            observer.update(with: message)
        }
    }
}

// 3. 实现观察者
class ConcreteObserver: Observer {
    func update(with message: String) {
        print("ConcreteObserver received: \(message)")
    }
}

// 使用示例
let subject = Subject()
let observer = ConcreteObserver()
subject.addObserver(observer)
subject.notifyObservers(with: "Hello, observers!")
subject.removeObserver(observer)

Swift 通知中心(Notification Center)

Swift 的通知中心是一种基于观察者模式的机制,它提供了一种全局的广播消息的方式。通知中心允许任何对象发布通知(Notification),其他感兴趣的对象可以注册接收这些通知。

通知中心的工作原理

  1. 发布通知:当某个特定事件发生时,一个对象(发布者)可以通过通知中心发布通知。通知包含一个名称(Notification.Name),可以携带一些用户信息(userInfo)。
  2. 注册接收通知:其他对象(观察者)可以向通知中心注册,指定感兴趣的通知名称和接收通知的方法。当通知发布时,通知中心会调用注册对象的相应方法,并传递通知对象。

通知中心示例代码

// 发布通知
NotificationCenter.default.post(
    name: Notification.Name("MyNotification"),
    object: nil,
    userInfo: ["key": "value"]
)

// 注册接收通知
NotificationCenter.default.addObserver(
    forName: Notification.Name("MyNotification"),
    object: nil,
    queue: nil
) { notification in
    if let userInfo = notification.userInfo,
       let value = userInfo["key"] as? String {
        print("Received notification with value: \(value)")
    }
}

观察者模式与通知中心的比较

  1. 耦合度
    • 观察者模式:被观察对象和观察者之间存在直接的关联,被观察对象需要知道观察者的具体类型并维护观察者列表。这使得它们之间的耦合度相对较高,但也更灵活,因为可以根据具体需求定制观察者和被观察对象的交互。
    • 通知中心:发布者和接收者之间解耦,发布者不需要知道谁会接收通知,接收者也不需要知道谁发布了通知。这种高度解耦的方式使得代码更易于维护和扩展,但可能在调试时更难追踪消息的来源和去向。
  2. 应用场景
    • 观察者模式:适用于对象之间存在明确的依赖关系,且需要紧密交互的场景。例如,一个视图模型(ViewModel)和它对应的视图(View)之间的关系,当视图模型的数据发生变化时,需要及时通知视图进行更新。
    • 通知中心:适用于更广泛的、松散耦合的消息传递场景。例如,应用程序中的不同模块之间需要进行通信,而这些模块可能不直接相关,但对某些特定事件感兴趣。

通知中心优化

尽管通知中心提供了一种方便的消息传递机制,但在实际应用中,可能会出现一些问题,需要进行优化。

内存管理问题

如果观察者在注册通知后没有及时移除,可能会导致内存泄漏。特别是在视图控制器(ViewController)等生命周期有限的对象中,如果在deinit方法中没有移除通知,当视图控制器被销毁时,通知中心仍然持有对它的引用,使得视图控制器无法被释放。

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleNotification(_:)),
            name: Notification.Name("MyNotification"),
            object: nil
        )
    }
    
    @objc func handleNotification(_ notification: Notification) {
        // 处理通知
    }
    
    deinit {
        NotificationCenter.default.removeObserver(
            self,
            name: Notification.Name("MyNotification"),
            object: nil
        )
    }
}

通知名称冲突

当应用程序规模较大时,不同模块可能会使用相同的通知名称,导致冲突。为了避免这种情况,可以使用唯一的命名空间。例如,可以将通知名称与模块名结合,或者使用 UUID 作为通知名称的一部分。

let Module1NotificationName = Notification.Name("com.example.module1.MyNotification")
let Module2NotificationName = Notification.Name("com.example.module2.MyNotification")

过多的通知导致性能问题

如果应用程序中频繁发布和接收通知,可能会影响性能。为了优化性能,可以考虑以下几点:

  1. 减少不必要的通知发布:确保只有在真正需要通知的情况下才发布通知,避免过度发布。
  2. 批量处理通知:如果可能,将多个相关的通知合并为一个通知,减少通知的数量。
  3. 优化通知处理逻辑:确保观察者处理通知的方法尽可能高效,避免在通知处理方法中执行复杂的、耗时的操作。

结合使用观察者模式和通知中心

在实际项目中,通常会结合使用观察者模式和通知中心。对于对象之间紧密相关的交互,使用观察者模式;对于松散耦合的、跨模块的消息传递,使用通知中心。

例如,在一个电商应用中,购物车模块内部的商品数量变化等操作,可以使用观察者模式来通知相关的视图进行更新。而当用户完成订单支付后,需要通知多个不同模块(如库存管理模块、用户积分模块等)进行相应的处理,这时可以使用通知中心。

// 购物车模块使用观察者模式
protocol CartObserver {
    func cartUpdated()
}

class Cart {
    private var observers: [CartObserver] = []
    var items: [String] = []
    
    func addObserver(_ observer: CartObserver) {
        observers.append(observer)
    }
    
    func removeObserver(_ observer: CartObserver) {
        observers = observers.filter { $0!== observer }
    }
    
    func addItem(_ item: String) {
        items.append(item)
        notifyObservers()
    }
    
    func notifyObservers() {
        for observer in observers {
            observer.cartUpdated()
        }
    }
}

class CartViewController: CartObserver {
    func cartUpdated() {
        // 更新购物车界面
    }
}

// 订单支付后使用通知中心
NotificationCenter.default.post(
    name: Notification.Name("OrderPaidNotification"),
    object: nil,
    userInfo: ["orderId": "12345"]
)

NotificationCenter.default.addObserver(
    forName: Notification.Name("OrderPaidNotification"),
    object: nil,
    queue: nil
) { notification in
    if let userInfo = notification.userInfo,
       let orderId = userInfo["orderId"] as? String {
        // 库存管理模块处理订单支付后的库存更新
        print("Inventory module: Update inventory for order \(orderId)")
    }
}

高级应用与最佳实践

  1. 使用泛型增强观察者模式的灵活性
    • 通过使用泛型,可以使观察者模式更加通用,适用于不同类型的被观察对象和通知数据。
    protocol GenericObserver<T> {
        func update(with data: T)
    }
    
    class GenericSubject<T> {
        private var observers: [GenericObserver<T>] = []
        
        func addObserver(_ observer: GenericObserver<T>) {
            observers.append(observer)
        }
        
        func removeObserver(_ observer: GenericObserver<T>) {
            observers = observers.filter { $0!== observer }
        }
        
        func notifyObservers(with data: T) {
            for observer in observers {
                observer.update(with: data)
            }
        }
    }
    
    class StringObserver: GenericObserver<String> {
        func update(with data: String) {
            print("StringObserver received: \(data)")
        }
    }
    
    let genericSubject = GenericSubject<String>()
    let stringObserver = StringObserver()
    genericSubject.addObserver(stringObserver)
    genericSubject.notifyObservers(with: "Generic message")
    
  2. 通知中心的分层架构
    • 在大型应用中,可以将通知中心进行分层管理,不同层次负责不同类型的通知。例如,可以有一个全局通知中心负责整个应用级别的通知,各个模块内部也可以有自己的局部通知中心,负责模块内的通知。这样可以减少全局通知的数量,提高可维护性。
    class ModuleNotificationCenter {
        private var observers: [Notification.Name: [(Notification) -> Void]] = [:]
        
        func addObserver(
            forName name: Notification.Name,
            queue: OperationQueue?,
            using block: @escaping (Notification) -> Void
        ) {
            if observers[name] == nil {
                observers[name] = []
            }
            observers[name]?.append(block)
        }
        
        func post(
            name: Notification.Name,
            object: Any?,
            userInfo: [AnyHashable: Any]?
        ) {
            let notification = Notification(
                name: name,
                object: object,
                userInfo: userInfo
            )
            if let blocks = observers[name] {
                for block in blocks {
                    block(notification)
                }
            }
        }
    }
    
    let moduleNotificationCenter = ModuleNotificationCenter()
    moduleNotificationCenter.addObserver(
        forName: Notification.Name("ModuleSpecificNotification"),
        queue: nil
    ) { notification in
        print("Module received specific notification")
    }
    moduleNotificationCenter.post(
        name: Notification.Name("ModuleSpecificNotification"),
        object: nil,
        userInfo: nil
    )
    
  3. 使用 Key - Value Observing(KVO)作为观察者模式的补充
    • KVO 是一种基于观察者模式的机制,它允许观察对象属性值的变化。在 Swift 中,可以通过NSObject的子类并使用@objc dynamic修饰属性来实现 KVO。
    class ObservableObject: NSObject {
        @objc dynamic var value: Int = 0
    }
    
    class KVOObserver: NSObject {
        var observedObject: ObservableObject
        
        init(observedObject: ObservableObject) {
            self.observedObject = observedObject
            super.init()
            self.observedObject.addObserver(
                self,
                forKeyPath: "value",
                options: [.new, .old],
                context: nil
            )
        }
        
        override func observeValue(
            forKeyPath keyPath: String?,
            of object: Any?,
            change: [NSKeyValueChangeKey : Any]?,
            context: UnsafeMutableRawPointer?
        ) {
            if keyPath == "value",
               let new = change?[.newKey] as? Int,
               let old = change?[.oldKey] as? Int {
                print("Value changed from \(old) to \(new)")
            }
        }
        
        deinit {
            observedObject.removeObserver(self, forKeyPath: "value")
        }
    }
    
    let observable = ObservableObject()
    let kvoObserver = KVOObserver(observedObject: observable)
    observable.value = 10
    

通过深入理解和优化 Swift 中的观察者模式与通知中心,可以使应用程序的架构更加清晰、可维护,提高代码的质量和性能。在实际项目中,应根据具体需求选择合适的机制,并遵循最佳实践,以实现高效、健壮的消息传递和对象间的交互。无论是简单的视图更新,还是复杂的跨模块通信,都可以通过合理运用这些技术来实现优雅的解决方案。