原文:Swift: NSNotificationCenter protocol
NSNotificationCenter在OSX第一個版本釋出時就存在了,已經有超過17年的歷史,很多開發者已經對它很熟悉。它是設計模式中觀察者模式的一種實現。
NSNotificationCenter存在的問題
通知沒有統一的命名格式
對於通知的命名沒有強制的要求,一個專案裡可能有多種不同的命名規則。比如:
1 2 3 4 5 6 |
class Barista { let notification = "coffeeMadeNotification" } class Trainee { let coffeeMadeNotificationName = "Coffee Made" } |
通知名稱可能衝突
因為對於通知名稱沒有限制,可能在不同的物件定義了同樣的通知名,這樣會導致難以意料的bug。
1 2 3 4 5 6 |
class Barista { let notification = "coffeeMadeNotification" } class Trainee { let coffeeMadeNotificationName = "coffeeMadeNotification" } |
通知的名稱是字串
字串真的太危險了,很容易出現打錯的情況。
1 2 |
NSNotificationCenter.defaultCenter() .postNotificationName("coffeeMadNotfication") |
利用protocol的解決方案
我們通過設計一個protocol來解決上面提到的問題。
1 |
protocol Notifier { } |
通過列舉統一通知名稱
給這個協議增加一個關聯型別:
1 2 3 |
protocol Notifier { associatedType Notification: RawRepresentable } |
所有要傳送通知的物件或者結構體都要實現Notifier這個協議,然後提供一個實現了RawRepresentable的型別。其實就是一個字串列舉。
1 2 3 4 5 6 |
class Barista : Notifier { enum Notification : String { case makingCoffee case coffeeMade } } |
這樣就可以有一個統一的方式獲取通知名稱:
1 2 3 4 |
let coffeeMade = Barista.Notification.coffeeMade.rawValue NSNotificationCenter.defaultCenter() .postNotificationName(coffeeMade) |
避免通知名稱衝突
我們可以為通知新增一個唯一的名稱空間(namespace)來避免衝突。這裡想到的解決方案是使用實現這個協議的object名稱,因為每個object的名稱在一個專案裡是唯一的。
1 2 3 4 5 6 |
let baristaNotification = "\(Barista).\(Barista.Notification.coffeeMade.rawValue)" let traineeNotification = "\(Trainee).\(Trainee.Notification.coffeeMade.rawValue)" // baristaNotification: Barista.coffeeMade // traineeNotification: Trainee.coffeeMade |
但是每個通知都要手動新增就太蛋疼了。我們給這個協議加一個擴充方法來生成唯一的通知名稱。因為這個方法只需要內部知道,所以標記為private。
1 2 3 4 5 6 7 |
public extension Notifier where Notification.RawValue == String { private static func nameFor(notification: Notification) -> String { return "\(self).\(notification.rawValue)" } } |
Notifier的擴充套件方法
新增觀察者
最後一個通知的引數型別就是前面定義的那個列舉型別,這樣就不用輸入通知名稱的字串。
1 2 3 4 5 6 |
static func addObserver(observer: AnyObject, selector: Selector, notification: Notification) { let name = nameFor(notification) NSNotificationCenter.defaultCenter() .addObserver(observer, selector: selector, name: name, object: nil) } |
這樣在使用的時候,在實現協議的object上直接方便的新增觀察者:
1 |
Barista.addObserver(customer, selector: #selector(Customer.drink(_:)), notification: .coffeeMade) |
傳送通知
呼叫的時候應該是這樣的:
1 |
Barista.postNotification(.coffeeMade) |
這裡利用了swfit的預設引數,object和userinfo設定一個預設的空值。實現如下:
1 2 3 4 5 6 |
static func postNotification(notification: Notification, object: AnyObject? = nil, userInfo: [String : AnyObject]? = nil) { let name = nameFor(notification) NSNotificationCenter.defaultCenter() .postNotificationName(name, object: object, userInfo: userInfo) } |
移除觀察
這個實現就不貼了。和前面兩個方法類似。呼叫的時候是這樣的:
1 |
Barista.removeObserver(customer, notification: .coffeeMade) |
總結
通過靈活利用swfit的語言特性:協議關聯型別,協議可以新增預設的方法實現以及方法的預設引數,利用自定義的Notifier協議封裝了NSNotificationCenter的呼叫方式,解決了傳統NSNotificationCenter呼叫的可能產生的三個潛在風險。
歡迎關注我的微博:@沒故事的卓同學
原始碼: https://github.com/andyyhope/Blog_NSNotificationCenterProtocol