iOS中Notification (或者NSNotification)是一对多通信的常用手段。但是开发者们长期以来对Notification的诟病不断,一不小心,整个app就会变得notification满天飞,加上它本身是基于String和Dictionary作为信息载体的,使得它难以被管理。
Swift中的Notification虽然增加了Notification.Name
,使其有了一定类型安全性,但对它的信息载荷userInfo
依然使用了任意类型的Dictionary,使其在解析上很难有类型安全的保证。本文将介绍一种方法,使其在通知名称和信息载荷上都保证类型安全,但又不过多更改Notification的使用方法。
首先我们创建一个protocol来约束Notification.Name
和userInfo
的关系。
protocol NotificationType {
associatedtype UserInfoType
static var name: Notification.Name { get }
var userInfo: UserInfoType { set get }
init(userInfo: UserInfoType)
}
以上的代码,我们使用了associatedtype
使这个protocol范型化,而UserInfoType
便是强类型userInfo
的类型。这个类型可以在定义实际的Notification时定义。
接下来,我们对这个NotificationType进行扩展,使其使用起来更简单。
extension NotificationType {
static var name: Notification.Name {
return Notification.Name("ProjectName.Notification.\(self)")
}
/// Post a notification with the poster
///
/// - Parameter object: The object posting the notification.
/// - Parameter notificationCenter: The notification center, if the value is nil, the default notification center will be used.
func post(by object: Any?, via notificationCenter: NotificationCenter? = nil) {
let center = notificationCenter ?? NotificationCenter.default
let userInfoDict = ["userInfo": userInfo]
center.post(name: Self.name, object: object, userInfo: userInfoDict)
}
}
以上的扩展,先是定义了默认的Notification.Name生成的方法,即通过NotificationType的名称来推导。下面的post
方法是用来规范notification发送的结构。从代码中我们能看出来,发送时强类型的信息载荷对象被装到一个字典里,并强制使用"userInfo"
作为key,这样在解析的时候我们可以直接从userInfo
里拿出强类型的object了。
有了这两段代码,我们便可以通过下面的方法定义和发送Notification了:
有载荷的Notificaiton:
struct UserCreated: NotificationType {
struct UserInfo {
var userId: String
var userName: String
}
var userInfo: UserInfo
}
这里,我们创建了UserCreated,它实现了NotificationType。我们在struct中定义了另一个struct UserInfo
来作为其载荷类型(这样做使得UserInfo有了namespace,避免冲突),并且在下面显式指定userInfo
的类型为UserInfo
。这时,Swift的类型推断能够正确识别associatedtype为UserCreated.UserInfo
。这样这个Notification就被构造好了。
我们要发送UserCreated很简单,只需要
UserCreated(userInfo: UserCreated.UserInfo(userId: "123", userName: "abc")).post(by: nil)
接下来我们要解决接收的问题了。我们要充分利用之前在实现protocol时的约定,使得解析userInfo的内容简化。我们通过扩展NotificationType的方式,使其可以辅助userInfo的解析,并且扩展NotificationCenter方法,使得接收部分变得强类型。
extension NotificationType where UserInfoType: Any {
/// Get user info with strong type
static func parse(notification: Notification)-> UserInfoType {
return notification.userInfo!["userInfo"] as! UserInfoType
}
}
extension NotificationCenter {
@discardableResult
func addObserver(for type: T.Type,
object: Any?,
queue: OperationQueue?,
using block: @escaping ((Notification, T.UserInfoType)-> Void)) -> NSObjectProtocol {
return self.addObserver(forName: T.name, object: object, queue: queue) { (notification) in
let userInfo = T.parse(notification: notification)
block(notification, userInfo)
}
}
}
有了上面的扩展,我们就可以将接收部分写成:
NotificationCenter.default.addObserver(for: UserCreated.self, object: nil, queue: nil) { (_, userInfo) in
print(userInfo.userId)
print(userInfo.userName)
}
这样以来,notification在发送和接收过程中都保持了类型安全。
另外,我们可以增加一个扩展,使得无载荷的notification变得更简单:
extension NotificationType where UserInfoType == Void {
/// for whose user info type is void, make a covenient initializer
init() {
self.init(userInfo: ())
}
}
这样,无载荷的notification使用就变成
SomeOperationCompleted().post(by: nil)
另外,如果你想对Notification.Name定制,可以在实现NotificationType时覆盖默认生成方法:
struct NameOverrided: NotificationType {
static var name: Notification.Name = Notification.Name(rawValue: "Something else")
var userInfo: Void
}
完整的例子
import UIKit
protocol NotificationType {
associatedtype UserInfoType
static var name: Notification.Name { get }
var userInfo: UserInfoType { set get }
init(userInfo: UserInfoType)
}
extension NotificationType {
static var name: Notification.Name {
return Notification.Name("Stowed.Notification.\(self)")
}
/// Post a notification with the poster
///
/// - Parameter object: The object posting the notification.
/// - Parameter notificationCenter: The notification center, if the value is nil, the default notification center will be used.
func post(by object: Any?, via notificationCenter: NotificationCenter? = nil) {
let center = notificationCenter ?? NotificationCenter.default
let userInfoDict = ["userInfo": userInfo]
center.post(name: Self.name, object: object, userInfo: userInfoDict)
}
}
extension NotificationType where UserInfoType == Void {
/// for whose user info type is void, make a covenient initializer
init() {
self.init(userInfo: ())
}
}
extension NotificationType where UserInfoType: Any {
/// Get user info with strong type
static func parse(notification: Notification)-> UserInfoType {
return notification.userInfo!["userInfo"] as! UserInfoType
}
}
extension NotificationCenter {
@discardableResult
func addObserver(for type: T.Type,
object: Any?,
queue: OperationQueue?,
using block: @escaping ((Notification, T.UserInfoType)-> Void)) -> NSObjectProtocol {
return self.addObserver(forName: T.name, object: object, queue: queue) { (notification) in
let userInfo = T.parse(notification: notification)
block(notification, userInfo)
}
}
}
struct UserCreated: NotificationType {
struct UserInfo {
var userId: String
var userName: String
}
var userInfo: UserInfo
}
struct SomeOperationCompleted: NotificationType {
var userInfo: Void
}
struct NameOverrided: NotificationType {
static var name: Notification.Name = Notification.Name(rawValue: "Something else")
var userInfo: Void
}
NotificationCenter.default.addObserver(for: UserCreated.self, object: nil, queue: nil) { (_, userInfo) in
print(userInfo.userId)
print(userInfo.userName)
}
NotificationCenter.default.addObserver(for: SomeOperationCompleted.self, object: nil, queue: nil) { (_, _) in
print("Something completed")
}
UserCreated(userInfo: UserCreated.UserInfo(userId: "123", userName: "abc")).post(by: nil)
SomeOperationCompleted().post(by: nil)