iOS中Notification (或者NSNotification)是一對多通信的常用手段。但是開發(fā)者們長期以來對Notification的詬病不斷,一不小心,整個app就會變得notification滿天飛,加上它本身是基于String和Dictionary作為信息載體的,使得它難以被管理。
Swift中的Notification雖然增加了Notification.Name,使其有了一定類型安全性,但對它的信息載荷userInfo依然使用了任意類型的Dictionary,使其在解析上很難有類型安全的保證。本文將介紹一種方法,使其在通知名稱和信息載荷上都保證類型安全,但又不過多更改Notification的使用方法。
首先我們創(chuàng)建一個protocol來約束Notification.Name和userInfo的關(guān)系。
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方法是用來規(guī)范notification發(fā)送的結(jié)構(gòu)。從代碼中我們能看出來,發(fā)送時強類型的信息載荷對象被裝到一個字典里,并強制使用"userInfo"作為key,這樣在解析的時候我們可以直接從userInfo里拿出強類型的object了。
有了這兩段代碼,我們便可以通過下面的方法定義和發(fā)送Notification了:
有載荷的Notificaiton:
struct UserCreated: NotificationType {
struct UserInfo {
var userId: String
var userName: String
}
var userInfo: UserInfo
}
這里,我們創(chuàng)建了UserCreated,它實現(xiàn)了NotificationType。我們在struct中定義了另一個struct UserInfo來作為其載荷類型(這樣做使得UserInfo有了namespace,避免沖突),并且在下面顯式指定userInfo的類型為UserInfo。這時,Swift的類型推斷能夠正確識別associatedtype為UserCreated.UserInfo。這樣這個Notification就被構(gòu)造好了。
我們要發(fā)送UserCreated很簡單,只需要
UserCreated(userInfo: UserCreated.UserInfo(userId: "123", userName: "abc")).post(by: nil)
接下來我們要解決接收的問題了。我們要充分利用之前在實現(xiàn)protocol時的約定,使得解析userInfo的內(nèi)容簡化。我們通過擴展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<T: NotificationType>(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在發(fā)送和接收過程中都保持了類型安全。
另外,我們可以增加一個擴展,使得無載荷的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定制,可以在實現(xiàn)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<T: NotificationType>(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)