从 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

相关文章
相关标签/搜索