從 Notification.Name 看 Swift 如何優雅的解決 String 硬編碼

前面

初學 Swift 中相關 NSNotification 的代碼時, 發現了以前熟悉的 name 參數的類型由 Objective-C 中的 NSString 變成了 Notification.Name 類型. 並非我指望的 String 類型...這是怎麼回事呢?swift

Swift 中如何使用 Notification

那麼, 在 Swift 中如何使用 Notification 呢, 以 post 爲例.app

NotificationCenter.default.post(name: Notification.Name.UIApplicationDidFinishLaunching, object: nil)
複製代碼

其中, Notification.Name 是能夠省略的, 就變爲了post

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 的別名)編碼

那咱們若是想自定義一個通知怎麼辦呢, 直接能夠仿照系統的方式, 咱們本身爲其增長一個 extensionspa

extension Notification.Name {
	static let LoginStatusChanged = Notification.Name("LoginStatusChanged")
}
複製代碼

其中 Notification.Name("LoginStatusChanged") 是其初始化方法, 可查看文檔說明, 使用時, 可直接code

NotificationCenter.default.post(name: .LoginStatusChanged, object: nil)
複製代碼

由於這個通知 LoginStatusChanged 是定義在 Notification.Name 中的了, 因此也不必在名稱後面增長 Notification 等字樣來表示這是一個通知了. 因此 Swift 中不少定義的名稱都是很是簡潔的.orm

對比 Objective-C 中的使用

對比以前在 Objective-C 中的使用cdn

[[NSNotificationCenter defaultCenter] postNotificationName:"xxxxxxxxxx" object:nilblog

這樣是很是容易出錯的, 查這樣的錯誤常常也是很是費時費力的, 也讓人看來是很是不優雅的, 因此咱們常常會進行宏定義或者是常量來防止字符串硬編碼的問題.

但這實際上也是會帶來一些使人頭疼的問題的:

  1. 爲了代表定義的字符串常量是一個通知名, 還要爲其增長冗長的前綴或者是後綴
  2. 在開發中還常常會在代碼補全中, 看到根本不和場合的一些常量名
  3. 一般爲了使用方便和易於維護, 還會在將全部的通知定義在一個 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 事件名在不想要出現的時候被代碼補全功能強行彈出來.

Reference

相關文章
相關標籤/搜索