用Swift写一个通知中心

背景

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强大的协议进行抽象,再配合泛型来确定函数中的具体类型来达到最终目的。 简洁快捷易维护。

源码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容