前面
初學 Swift 中相關 NSNotification 的程式碼時, 發現了之前熟悉的 name 引數的型別由 Objective-C 中的 NSString 變成了 Notification.Name 型別. 並不是我期望的 String 型別…這是怎麼回事呢?
Swift 中如何使用 Notification
那麼, 在 Swift 中如何使用 Notification 呢, 以 post 為例.
NotificationCenter.default.post(name: Notification.Name.UIApplicationDidFinishLaunching, object: nil)
複製程式碼
其中, Notification.Name
是可以省略的, 就變為了
NotificationCenter.default.post(name: .UIApplicationDidFinishLaunching, object: nil)
複製程式碼
檢視定義發現了 UIApplicationDidFinishLaunching
實際上是定義在結構體 NSNotification.Name
擴充套件(extension)中的的一個靜態常量 (static let
), 型別是 NSNotification.Name
extension NSNotification.Name {
@available(iOS 4.0, *)
public static let UIApplicationDidEnterBackground: NSNotification.Name
@available(iOS 4.0, *)
public static let UIApplicationWillEnterForeground: NSNotification.Name
public static let UIApplicationDidFinishLaunching: NSNotification.Name
...
}
複製程式碼
所以我們才可以省略前面的 Notification.Name
直接使用 .UIApplicationDidFinishLaunching
(Notification.Name 是 NSNotification.Name 的別名)
那我們如果想自定義一個通知怎麼辦呢, 直接可以仿照系統的方式, 我們自己為其增加一個 extension
extension Notification.Name {
static let LoginStatusChanged = Notification.Name("LoginStatusChanged")
}
複製程式碼
其中 Notification.Name("LoginStatusChanged")
是其初始化方法, 可檢視文件說明, 使用時, 可直接
NotificationCenter.default.post(name: .LoginStatusChanged, object: nil)
複製程式碼
因為這個通知 LoginStatusChanged
是定義在 Notification.Name
中的了, 所以也沒必要在名稱後面增加 Notification 等字樣來表示這是一個通知了. 所以 Swift 中很多定義的名稱都是非常簡潔的.
對比 Objective-C 中的使用
對比之前在 Objective-C 中的使用
[[NSNotificationCenter defaultCenter] postNotificationName:"xxxxxxxxxx" object:nil
這樣是非常容易出錯的, 查這樣的錯誤經常也是非常費時費力的, 也讓人看來是非常不優雅的, 所以我們經常會進行巨集定義或者是常量來防止字串硬編碼的問題.
但這實際上也是會帶來一些令人頭疼的問題的:
- 為了表明定義的字串常量是一個通知名, 還要為其增加冗長的字首或者是字尾
- 在開發中還經常會在程式碼補全中, 看到根本不和場合的一些常量名
- 通常為了使用方便和易於維護, 還會在將所有的通知定義在一個 xxDefine.h 的標頭檔案中, 並在 pch 檔案中引用, 此時如果增刪或者修改了任意通知. 將會引起工程的全量重新編譯. 也很是頭疼.
…
所以, Swift 這種使用方式可謂是十分優雅.
舉一反三
在開發中, 其實類似於 Notification 這種需要傳遞字串的場景還有很多, 我們都可以使用這類使用方法進行優化.
場景
假設有這樣一個場景, 定義一個類 EventReporter
用來處理埋點請求.
class EventReporter {
static let shared = EventReporter()
func reportEvent(_ eventId: String, withParams params: [String:Any]?) {
// 埋點上報邏輯
}
}
複製程式碼
相信這樣的場景是很多人都見過的, 其中 eventId 是我們埋點的事件的ID, 那麼該如何使用類似 Notification.Name
的方式來優化這類場景呢?
原理
從文件中看出 Notification.Name
實際上是遵從了一個協議 RawRepresentable
Overview
With a RawRepresentable type, you can switch back and forth between a custom type and an associated RawValue type without losing the value of the original RawRepresentable type. Using the raw value of a conforming type streamlines interoperation with Objective-C and legacy APIs and simplifies conformance to other protocols, such as Equatable, Comparable, and Hashable.
The RawRepresentable protocol is seen mainly in two categories of types: enumerations with raw value types and option sets.
簡單的說就是, 使用 RawRepresentable
型別, 可以在自定義型別和其關聯的 RawValue
型別之間來回切換, 可簡化與 Objective-C 和傳統 API 的互動, 兩類:具有原始值型別和選項集的列舉(OptionSet, 其實 Swift 中的選項集列舉就是整合自 RawRepresentable
這個 Protocol 實現的), 說白了. 就是用一個型別封裝一下我們想要使用的型別比如說 String, 來方便互動.
實現
使用起來很簡單, 定義一個結構體來管理所有的埋點事件
struct EventID: RawRepresentable {
}
複製程式碼
根據編譯器提示, 補全協議程式碼
struct EventID: RawRepresentable {
typealias RawValue = String
var rawValue: String
init?(rawValue: String) {
}
}
複製程式碼
從這就更容易看出其原理, 實際上內部的 rawValue
屬性就是我們需要使用的 String 型別的事件名, 初始化方法傳入該 String 對其賦值即可, 返回 EventID
型別的結構體
這裡發現初始化方法返回的是一個 Optional
型別, 這樣使用起來還需要解包, 不太方便, 可以看到 Notification.Name
的初始化方法返回並不是 Optional
, 因為定義都是非常確定的事件名(通知名), 而且 init
方法中也不會產生異常, 所以此處沒什麼必要使用 Optional
, 去掉 ?
即可
struct EventID: RawRepresentable {
typealias RawValue = String
var rawValue: String
init(rawValue: String) {
self.rawValue = rawValue
}
}
複製程式碼
那麼, 我們的上報類的程式碼可以修改如下, 這裡還可以給 params
一個預設值, 這樣如果沒有引數時, 可以只傳遞 eventId
一個引數即可.
class EventReporter {
static let shared = EventReporter()
func reportEvent(_ eventId: EventID, withParams params: [String:Any]? = nil) {
let event = eventId.rawValue
// 埋點邏輯
}
}
複製程式碼
最後, 定義一個埋點事件看看吧~, 推薦寫到 extension
中易於維護.
extension EventID {
static let LoginPageExposure = EventID(rawValue: "login_page_exposure")
}
複製程式碼
那麼使用的時候,
EventReporter.shared.reportEvent(.LoginPageExposure)
複製程式碼
當我們打出 .
的時候, 程式碼補全就已經將 LoginPageExposure
提示給我們了.
總結
使用這種方式優化程式碼, 不僅可以讓程式碼意圖容易理解, 使用也更加簡單不會出錯. 而且也不會使得 LoginPageExposure
事件名在不想要出現的時候被程式碼補全功能強行彈出來.