背景
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值是不是name
和age
啊,这需要去发送端看一看😓
/// 发送端
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
类型的name
和Int
类型的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强大的协议进行抽象,再配合泛型来确定函数中的具体类型来达到最终目的。 简洁快捷易维护。