用Swift写一个通知中心

2018-11-23  本文已影响32人  Zafir_zzf

背景

NotificationCenter - 一个非常古老传统,但是我们还经常遇到的东西。古老体现在它的API和用法上,在Swift这样的高级语言看来,是比较让人抓狂的。

当你想要进行全局的监听与传值,你很大可能会选择用一个通知中心。
比如你需要在首页监听一个从个人中心传来的String和Int值。接下来你便开始写下了这样的代码:

func addObserve() {
    NotificationCenter.default.addObserver(self, selector: #selector(receiveNotification(noti:)), name: NSNotification.Name(rawValue: oneNotificationName), object: nil)
}
    
@objc func receiveNotification(noti: Notification) {
    guard let info = noti.userInfo as? [String: Any] else { return }
    let strValue = info["name"] as? String
    let intValue = info["age"] as? Int
}

看看仅仅是注册的函数长度和可读性吧😰 好不容易写完了.
为位了拿到我想要的String和Int还要去解析一个Dictionary..
Wait..它们的key值是不是nameage啊,这需要去发送端看一看😓

/// 发送端
func postNotification() {
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: oneNotificationName), object: nil, userInfo: ["name": "jack", "age": 5])
}

长长的难以找到重点的函数,并且传一个字典需要指定不安全的字符串key值。
key值也声明为常量在一个文件管理是个注意,不过那又增加了一个通知的维护成本和时间成本。

如果你要想要及时的移除通知,你还需要这样:

deinit {
    NotificationCenter.default.removeObserver(self)
}

SwiftyNotification

我的思路是完全抛弃系统的API,自己去实现一套通知机制,其实就是先将一段代码块存起来,延迟执行,再加上如何识别对应的代码块,就是一个简易的通知中心了。
既然是在原先的基础上改进,解决掉注册和执行时不明确具体类型这个痛点我觉得是有必要的。为每一个通知指定类型,但又不能每一个通知写一个函数丢掉复用性,利用泛型就可以做到这一点。
注册通知,发生通知的函数是要公用的,所以我选择用协议来进行抽象。

/// 每个通知要遵循的协议
public protocol INewNotifioncation {
    associatedtype InfoType
    static var name: String { get }
}

公共的抽出来,需要各个通知所提供的就是传值的类型和用来识别哪一个通知所用的name.

接下来定义一个通知来遵循这个协议.

/// 用来管理所有通知的类
class NewNotifications {
    
    /// 某一个通知,遵循协议后,指定类型和name
    struct MarketChangeNoti: INewNotifioncation {
        typealias InfoType = (name: String, age: Int)
        static var name: String = "marketChangeNoti"
    }
}

写一个通知写这些看起来代码有些多,但是随着通知的增多,这样更方便管理查阅。重点是为了使用起来更简单。

注册通知:

 NewNotifications.MarketChangeNoti.addObserve(.always) { (result) in
    
    print(result.name, result.age) 
}

函数已经简洁了很多,并且我们在函数末尾直接跟上回调,result的类型就是在刚才声明通知时候指定的类型,直接就可以拿到String类型的nameInt类型的age了。

.always代表这个通知一直不会销毁,传入一个dispose变量注册会跟随者dispose的销毁而取消订阅。

发送通知:

 NewNotifications.MarketChangeNoti.post(("zzf", 18))

编译器会根据你调用哪一个通知对应的在post后面的参数指定具体的类型,这样我们忘了这个通知要传什么值,编译器也会提醒我们,也不需要传入一个字典了。而且可读性明显更加好了。

核心代码

首先需要一个数组,里面装着每一个注册通知对应的事件闭包,当发送一个通知的时候,可能有多个注册的地方,所以会触发多个闭包。如果是同一个地点的重复注册,下一次的要覆盖上一次的。

所以我创建了这样几个类。

class NewNotiHandler {
    /// 用来识别哪一个对象注册的
    weak var notiDispose: NewNotiDispose?
    /// 事件闭包
    var handler: NotiHandler
    
    init(dispose: NewNotiDispose,
         handler: @escaping NotiHandler) {
        self.notiDispose = dispose
        self.handler = handler
    }
}
/// 某一个通知,存储着一个名称与对应所有注册它的闭包
class NewNotification {

    var name: String
    var handlers: [NewNotiHandler]
    
    init(name: String, handlers: [NewNotiHandler]) {
        self.name = name
        self.handlers = handlers
    }
}
class NewNotificationCenter {
    /// 所有的通知
    static var newNotis = [NewNotification]()
    
    /// 注册一个通知
    static func addObserve(dispose: NewNotiDispose,
                           name: String,
                           handler: @escaping NotiHandler) {
        
        if let noti = newNotis.first(where: { $0.name == name }) {
            if let exitNoti = noti.handlers.first(where: { $0.notiDispose == dispose }) {
                exitNoti.handler = handler
            } else {
                noti.handlers.append(NewNotiHandler(dispose: dispose, handler: handler))
            }
        }
        
        let newNoti = NewNotification(name: name, handlers: [NewNotiHandler(dispose: dispose, handler: handler)])
        newNotis.append(newNoti)
    }
    
    static func postNotification(name: String,
                                 info: AnyObject) {
        guard let theNoti = newNotis.first(where: { $0.name == name }) else { return }
        theNoti.handlers.forEach { $0.handler(info) }
    }
}

最终通过协议扩展,给诸多声明的通知添加方法。

extension INewNotifioncation {
    static func addObserve(_ dispose: NewNotiDispose, response: @escaping (InfoType) -> ()) {
        NewNotificationCenter.addObserve(dispose: dispose, name: Self.name) { (object) in
            guard let info = object as? InfoType else { return }
            response(info)
        }
    }
    
    static func post(_ info: InfoType) {
        NewNotificationCenter.postNotification(name: Self.name, info: info as AnyObject)
    }
}

这与我上一篇 面相协议的网络请求层 核心思想一样,通过Swift强大的协议进行抽象,再配合泛型来确定函数中的具体类型来达到最终目的。 简洁快捷易维护。

源码

上一篇 下一篇

猜你喜欢

热点阅读