Swift 實現(xiàn) iOS 類型安全的 Notification

iOS中Notification (或者NSNotification)是一對多通信的常用手段。但是開發(fā)者們長期以來對Notification的詬病不斷,一不小心,整個app就會變得notification滿天飛,加上它本身是基于String和Dictionary作為信息載體的,使得它難以被管理。

Swift中的Notification雖然增加了Notification.Name,使其有了一定類型安全性,但對它的信息載荷userInfo依然使用了任意類型的Dictionary,使其在解析上很難有類型安全的保證。本文將介紹一種方法,使其在通知名稱和信息載荷上都保證類型安全,但又不過多更改Notification的使用方法。

首先我們創(chuàng)建一個protocol來約束Notification.NameuserInfo的關(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容