如何在Swift中正确使用类扩展?
在Swift中,我一直使用扩展来扩展封闭types,并提供方便的,无逻辑的function,比如animation,math扩展等。但是,由于扩展是遍布整个代码库的硬依赖,所以在实现作为延伸的东西。
不过,最近我已经看到,苹果build议在更大程度上使用扩展,例如将协议作为单独的扩展来实现。
也就是说,如果你有一个实现协议B的类A,那么你最终得到这个devise:
class A { // Initializers, stored properties etc. } extension A: B { // Protocol implementation }
当你进入这个兔子洞,我开始看到更多基于扩展的代码,例如:
fileprivate extension A { // Private, calculated properties } fileprivate extension A { // Private functions }
我的一部分就像你在分开的扩展中实现协议时所获得的构build块。 它使得这个类的不同部分真正的不同。 但是,只要inheritance这个类,就不得不改变这个devise,因为扩展函数不能被覆盖。
我认为第二种方法是…有趣的。 有一件好事就是你不必注释每个私人财产和function作为私人,因为你可以指定的扩展。
然而,这种devise也分裂了存储和非存储的属性,公共和私人function,使得类的“逻辑”更难以遵循(我写小类)。 那和子类问题一起,让我在扩展仙境的门廊上停住了一点。
很想听听Swift社区如何看待扩展。 你怎么看? 有一个silverbullet?
当然,这只是我的看法,所以把我写的容易。
我目前在我的项目中使用extension-approach
有几个原因:
- 代码很干净 :我的类永远不会超过150行,通过扩展进行分隔使我的代码更具可读性,并按职责分开
这通常是一个类的样子:
final class A { // Here the public and private stored properties } extension A { // Here the public methods and public non-stored properties } fileprivate extension A { // here my private methods }
扩展可以不止一个,当然,这取决于你的课程。 这对于组织代码并从Xcode顶部栏读取它非常有用
-
它提醒我Swift是一种面向协议的编程语言,而不是OOP语言。 协议和协议扩展无法做到。 我更喜欢使用协议将安全层添加到我的类/结构中。 例如,我通常以这种方式写我的模型:
protocol User { var uid: String { get } var name: String { get } } final class UserModel: User { var uid: String var name: String init(uid: String, name: String) { self.uid = uid self.name = name } }
通过这种方式,您仍然可以在UserModel
类中编辑您的uid
和name
值,但不能在外面,因为您只能处理User
协议types。
我使用了类似的方法,可以用一句话来描述:
将types的职责分类为扩展
这些是我正在把个别扩展的方面的例子:
- types的主界面,从客户端看。
- 协议一致性(即委托协议,通常是私有的)。
- 序列化(例如与
NSCoding
相关的所有内容)。 - 生活在后台线程中的部分types,如networkingcallback。
有时候,当一个方面的复杂性上升时,我甚至会将一个types的实现分成多个文件。
这里有一些细节描述我如何sorting实现相关的代码:
- 重点是function成员资格。
- 保持公共和私人的实现closures,但分开。
- 不要在
var
和func
之间分割。 - 保持function实现的所有方面:嵌套types,初始化器,协议一致性等
优点
分离一个types的主要原因是为了更容易阅读和理解。
在阅读外国(或我自己的旧)代码时,理解大局常常是潜水中最困难的部分。给开发人员一个方法上下文的概念帮助很大。
还有另一个好处:访问控制使得不会无意中调用某些东西。 只能从后台线程调用的方法可以在“后台”扩展中声明为private
。 现在根本不能从其他地方调用。
目前的限制
Swift 3对这种风格施加了一定的限制。 有几件事情只能在主types的实现中生存:
- 存储的属性
- 覆盖func / var
- 可用的function/ var
- 需要(指定)初始化程序
这些限制(至less前三个)来自需要事先知道对象的数据布局(纯Swift的见证表)。 扩展可能会在运行时延迟加载(通过框架,插件,dlopen,…),创build实例后更改types的布局会使ABI瘫痪。
Swift团队的一个温和的build议:)
所有来自一个模块的代码都保证可以同时使用。 如果Swift编译器允许在单个模块中 “组合”types,则可以避免阻止完全分离function方面的限制。 对于组合types,我的意思是编译器将从模块中的所有文件中收集所有定义types布局的声明。 与其他方面的语言一样,它会自动查找内部文件依赖关系。
这将允许真正写出“面向方面”的扩展。 不需要在主声明中声明存储的属性或覆盖,就可以实现更好的访问控制和关注点分离。