通过Notification.Name看Swift是如何优雅的解决String硬编码
前面
初学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)中的的一个静态常量(staticlet),类型是NSNotification.Name
extensionNSNotification.Name{ @available(iOS4.0,*) publicstaticletUIApplicationDidEnterBackground:NSNotification.Name @available(iOS4.0,*) publicstaticletUIApplicationWillEnterForeground:NSNotification.Name publicstaticletUIApplicationDidFinishLaunching:NSNotification.Name ... }
复制代码所以我们才可以省略前面的Notification.Name直接使用.UIApplicationDidFinishLaunching(Notification.Name是NSNotification.Name的别名)
那我们如果想自定义一个通知怎么办呢,直接可以仿照系统的方式,我们自己为其增加一个extension
extensionNotification.Name{ staticletLoginStatusChanged=Notification.Name("LoginStatusChanged") }
其中Notification.Name("LoginStatusChanged")是其初始化方法,可查看文档说明,使用时,可直接
NotificationCenter.default.post(name:.LoginStatusChanged,object:nil)
因为这个通知LoginStatusChanged是定义在Notification.Name中的了,所以也没必要在名称后面增加Notification等字样来表示这是一个通知了.所以Swift中很多定义的名称都是非常简洁的.
对比Objective-C中的使用
对比之前在Objective-C中的使用
[[NSNotificationCenterdefaultCenter]postNotificationName:"xxxxxxxxxx"object:nil
这样是非常容易出错的,查这样的错误经常也是非常费时费力的,也让人看来是非常不优雅的,所以我们经常会进行宏定义或者是常量来防止字符串硬编码的问题.
但这实际上也是会带来一些令人头疼的问题的:
- 为了表明定义的字符串常量是一个通知名,还要为其增加冗长的前缀或者是后缀
- 在开发中还经常会在代码补全中,看到根本不和场合的一些常量名
- 通常为了使用方便和易于维护,还会在将所有的通知定义在一个xxDefine.h的头文件中,并在pch文件中引用,此时如果增删或者修改了任意通知.将会引起工程的全量重新编译.也很是头疼.
...
所以,Swift这种使用方式可谓是十分优雅.
举一反三
在开发中,其实类似于Notification这种需要传递字符串的场景还有很多,我们都可以使用这类使用方法进行优化.
场景
假设有这样一个场景,定义一个类EventReporter用来处理埋点请求.
classEventReporter{ staticletshared=EventReporter() funcreportEvent(_eventId:String,withParamsparams:[String:Any]?){ //埋点上报逻辑 } }
相信这样的场景是很多人都见过的,其中eventId是我们埋点的事件的ID,那么该如何使用类似Notification.Name的方式来优化这类场景呢?
原理
从文档中看出Notification.Name实际上是遵从了一个协议RawRepresentable
Overview
WithaRawRepresentabletype,youcanswitchbackandforthbetweenacustomtypeandanassociatedRawValuetypewithoutlosingthevalueoftheoriginalRawRepresentabletype.UsingtherawvalueofaconformingtypestreamlinesinteroperationwithObjective-CandlegacyAPIsandsimplifiesconformancetootherprotocols,suchasEquatable,Comparable,andHashable.
TheRawRepresentableprotocolisseenmainlyintwocategoriesoftypes:enumerationswithrawvaluetypesandoptionsets.
简单的说就是,使用RawRepresentable类型,可以在自定义类型和其关联的RawValue类型之间来回切换,可简化与Objective-C和传统API的交互,两类:具有原始值类型和选项集的枚举(OptionSet,其实Swift中的选项集枚举就是集成自RawRepresentable这个Protocol实现的),说白了.就是用一个类型封装一下我们想要使用的类型比如说String,来方便交互.
实现
使用起来很简单,定义一个结构体来管理所有的埋点事件
structEventID:RawRepresentable{ }
根据编译器提示,补全协议代码
structEventID:RawRepresentable{ typealiasRawValue=String varrawValue:String init?(rawValue:String){ } }
从这就更容易看出其原理,实际上内部的rawValue属性就是我们需要使用的String类型的事件名,初始化方法传入该String对其赋值即可,返回EventID类型的结构体
这里发现初始化方法返回的是一个Optional类型,这样使用起来还需要解包,不太方便,可以看到Notification.Name的初始化方法返回并不是Optional,因为定义都是非常确定的事件名(通知名),而且init方法中也不会产生异常,所以此处没什么必要使用Optional,去掉?即可
structEventID:RawRepresentable{ typealiasRawValue=String varrawValue:String init(rawValue:String){ self.rawValue=rawValue } }
那么,我们的上报类的代码可以修改如下,这里还可以给params一个默认值,这样如果没有参数时,可以只传递eventId一个参数即可.
classEventReporter{ staticletshared=EventReporter() funcreportEvent(_eventId:EventID,withParamsparams:[String:Any]?=nil){ letevent=eventId.rawValue //埋点逻辑 } }
最后,定义一个埋点事件看看吧~,推荐写到extension中易于维护.
extensionEventID{ staticletLoginPageExposure=EventID(rawValue:"login_page_exposure") }
那么使用的时候,
EventReporter.shared.reportEvent(.LoginPageExposure)
当我们打出.的时候,代码补全就已经将LoginPageExposure提示给我们了.
总结
使用这种方式优化代码,不仅可以让代码意图容易理解,使用也更加简单不会出错.而且也不会使得LoginPageExposure事件名在不想要出现的时候被代码补全功能强行弹出来.
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。
Reference
- NSNotification.Name
- RawRepresentable