RxSwift:这可能是Notification最优雅的封装方式之一了

书接上回

上篇文章我们从RxSwift体系下NotificationCenter无需管理观察者的生命周期出发,剖析了其回收机制的原理。回看原文请戳RxSwift:为何NotificationCenter无需管理观察者的生命周期?

RxSwift核心优势在于:

  • RxSwift 会在你订阅通知时自动注册观察者,并在订阅释放(如 DisposeBag 销毁)时自动移除观察者。
  • 你无需再手动管理观察者的生命周期,极大降低了出错概率和维护成本。

以上特性是本次封装的基础。下面我开始"秀"代码。

Talk is cheap, Show me the code

这个 Eventable.swift 文件的核心作用是对 iOS 通知(Notification)机制进行协议化和 RxSwift 化的封装,让通知的发送和监听更加类型安全、简洁、易用,尤其适合响应式编程场景。

swift 复制代码
import Foundation

import RxSwift
import RxCocoa

protocol Eventable: RawRepresentable where RawValue == String {
    
    func post(object: AnyObject?, userInfo: [AnyHashable: Any]?)

    func rx(object: AnyObject?) -> Observable<Notification>
    
    var rx: Observable<Notification> { get }
}

extension Eventable {
    
    func post(object: AnyObject? = nil, userInfo: [AnyHashable: Any]? = nil) {
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: rawValue), object: object, userInfo: userInfo)
    }
    
    func rx(object: AnyObject? = nil) -> Observable<Notification> {
        return NotificationCenter.default.rx.notification(Notification.Name(rawValue: rawValue), object: object)
    }
    
    var rx: Observable<Notification> {
        return NotificationCenter.default.rx.notification(Notification.Name(rawValue: rawValue))
    }
}

1. 结构与实现思路

1.1 协议定义

swift 复制代码
protocol Eventable: RawRepresentable where RawValue == String {
    func post(object: AnyObject?, userInfo: [AnyHashable: Any]?)
    func rx(object: AnyObject?) -> Observable<Notification>
    var rx: Observable<Notification> { get }
}
  • Eventable 协议要求实现者是 RawRepresentableRawValueString,这意味着它通常用于 enum
  • 提供了发送通知post)和响应式监听通知rx)的方法。

1.2 默认实现

swift 复制代码
extension Eventable {
    func post(object: AnyObject? = nil, userInfo: [AnyHashable: Any]? = nil) { ... }
    func rx(object: AnyObject? = nil) -> Observable<Notification> { ... }
    var rx: Observable<Notification> { ... }
}
  • 通过扩展提供了默认实现,简化了使用。
  • post 方法用于发送通知。
  • rx 方法和属性用于通过 RxSwift 监听通知,返回 Observable<Notification>,方便响应式处理。

2. 设计思路

  • 类型安全:用枚举代替字符串,避免通知名拼写错误。
  • 协议扩展:通过协议和扩展,减少重复代码,统一通知的发送和监听方式。
  • 响应式支持:集成 RxSwift,方便在响应式编程场景下监听通知。
  • 易用性:调用简单,后面示例用法会给出具体例子。

3. 应用场景

  1. 模块间解耦通信
    不同模块、控制器、View 之间通过通知传递事件,避免直接引用,降低耦合。
  2. 响应式通知监听
    在 RxSwift 项目中,直接用 .rx 监听通知,配合 subscribebind 等操作符,代码更简洁。
  3. 避免硬编码
    所有通知名集中管理,便于维护和查找,减少出错概率。
  4. 便于测试和扩展
    统一的事件管理方式,便于单元测试和后续扩展。

4. 示例用法

EventBus.userLogin.post()EventBus.userLogin.rx.subscribe(...)

dart 复制代码
enum EventBus: String {
    case userLogin
    case userLogout
}

extension EventBus: Eventable {}

发送通知:

scss 复制代码
EventBus.userLogin.post()

响应式监听通知:

less 复制代码
EventBus.userLogin.rx
    .subscribe(onNext: { notification in
        // 处理事件
    })
    .disposed(by: disposeBag)

如果需要更多业务隔离,书写方式可以这样:

dart 复制代码
enum EventBus {
    enum User: String {
        case login
        case logout
    }
    
    enum Other: String {
        case hello
        case byebye
    }
}

extension EventBus.User: Eventable {}

extension EventBus.Other: Eventable {}

业务隔离的使用:

php 复制代码
EventBus.User.login.rx().subscribe { notification in
    print("rx() login")
    guard let userInfo = notification.userInfo as? [String: String] else {
        return
    }
    
    let name = userInfo["name"]
    print(name)
    
}.disposed(by: rx.disposeBag)

EventBus.User.login.rx.subscribe { _ in
    print("rx login")
}.disposed(by: rx.disposeBag)

EventBus.User.login.post(userInfo: ["name": "season"])

5. 加餐,思考Combine是否可行?

思考,既然RxSwift的这种思路可以支持这种写法,那么Swift原生的Combine,是否可行呢?

答案是:当然可以!

在Eventable协议中添加相关属性与方法:

swift 复制代码
public protocol Eventable: RawRepresentable where RawValue == String {
    
    func post(object: AnyObject?, userInfo: [AnyHashable: Any]?)
    
    var publisher: AnyPublisher<Notification, Never> { get }
    
    func publisher(object: AnyObject?) -> AnyPublisher<Notification, Never>
    
}

在Eventable中编写Combine的分类:

css 复制代码
extension Eventable {
    var publisher: AnyPublisher<Notification, Never> {
        NotificationCenter.default
            .publisher(for: Notification.Name(rawValue: rawValue))
            .eraseToAnyPublisher()
    }
    
    func publisher(object: AnyObject? = nil) -> AnyPublisher<Notification, Never> {
        NotificationCenter.default
            .publisher(for: Notification.Name(rawValue: rawValue), object: object)
            .eraseToAnyPublisher()
    }
}

使用Combine进行监听:

swift 复制代码
var cancellables = Set<AnyCancellable>()

EventBus.User.logout.publisher()
    .sink { notification in
        print("publisher() logout")
        
        guard let userInfo = notification.userInfo as? [String: String] else {
            return
        }
        
        let name = userInfo["name"]
        print(name ?? "empty")
    }
    .store(in: &cancellables)

EventBus.User.logout.publisher
    .sink { notification in
        print("publisher logout")
    }
    .store(in: &cancellables)

EventBus.User.logout.post(userInfo: ["name": "zhu"])

总结

这个思路之所以能够实现类型安全、响应式、自动释放的通知机制,根本原因在于 RxSwiftNotificationCenter 的封装极大简化了观察者的生命周期管理。开发者只需专注于事件本身的业务逻辑,无需再为资源释放和内存安全担忧。

通过协议和扩展的方式,结合 RxSwift,把原本繁琐、易错的通知机制变得类型安全、响应式、易维护,非常适合中大型 Swift 项目,尤其是使用 RxSwift 的项目。提高的可维护性和开发效率。

推荐在需要模块解耦、事件驱动、响应式开发的场景下使用。

最后我们从RxSwift联想的Swift原生Combine框架,发现这种思路也是可行,也进一步说明,编程思路为导向的重要性。


文章引用

RxSwift:为何NotificationCenter无需管理观察者的生命周期?

iOS:NSNotification.Name从OC到Swift的写法演进


Eventable库

github.com/seasonZhu/E...

相关推荐
高松燈1 小时前
若伊项目学习 后端分页源码分析
后端·架构
二流小码农1 小时前
鸿蒙开发:资讯项目实战之项目框架设计
android·ios·harmonyos
hepherd2 小时前
Flutter - 原生交互 - 相机Camera - 曝光,缩放,录制视频
flutter·ios·dart
维基框架3 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构
Android研究员3 小时前
HarmonyOS实战:List拖拽位置交换的多种实现方式
android·ios·harmonyos
水泥工boss4 小时前
🚀微前端与模块联邦的深度结合(基于vue+vite)
前端·架构
zfj3216 小时前
什么时候使用微服务,什么时候不用
微服务·架构
YungFan6 小时前
Xcode26新特性与iOS26适配指南
ios·xcode
程序猿DD7 小时前
告别微服务,迎接SCS(Self-Contained Systems)?新概念还是炒冷饭?
后端·微服务·架构
曼岛_7 小时前
[架构之美]解决Windows 10主机与Windows 10虚拟机之间无法拖拽复制问题
windows·架构