如何在Swift中正确使用类扩展?

时间:2016-11-09 07:44:56

标签: ios swift swift-extensions system-design

在Swift中,我历来使用扩展来扩展封闭类型并提供方便的,无逻辑的功能,如动画,数学扩展等。但是,由于扩展是硬依赖性遍布整个代码库,我总是认为三在实施某些内容之前的时间。

最近,我已经看到Apple建议在更大程度上使用扩展,例如将协议实现为单独的扩展。

也就是说,如果您有一个实现协议B的A类,那么您最终会得到这样的设计:

class A {
    // Initializers, stored properties etc.
}

extension A: B {
    // Protocol implementation
}

当你进入那个兔子洞时,我开始看到更多基于扩展的代码,例如:

fileprivate extension A {
    // Private, calculated properties
}

fileprivate extension A {
    // Private functions
}

我的一部分就像你在单独的扩展中实现协议时获得的构建块。它使得该类的独立部分非常独特。但是,只要继承此类,就必须更改此设计,因为无法覆盖扩展函数。

我认为第二种方法是......有趣。有一点很棒的是你不需要注释每个私有属性和私有属性,因为你可以为扩展指定。

然而,这种设计还会分割存储和非存储的属性,公共和私有函数,使类的“逻辑”更难遵循(写小类,我知道)。这与子类化问题一起使我在延伸仙境的门廊上停了一下。

很想听听Swift社区如何看待扩展。你怎么看?有一个银色的灯泡吗?

3 个答案:

答案 0 :(得分:10)

当然,这只是我的意见,所以我可以轻松写下来。

我目前在我的项目中使用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顶部栏中读取它非常有用

extension description

  • 它提醒我 Swift 是一种面向协议的编程语言,而不是OOP语言。您无法对协议和协议扩展做任何事情。我更喜欢使用协议为我的classes / struct添加安全层。例如,我通常以这种方式编写模型:

    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
        }
    }
    

通过这种方式,您仍然可以修改uid课程中的nameUserModel值,但您不能在外面,因为您只会处理User {1}}协议类型。

答案 1 :(得分:5)

我使用了类似的方法,可用一句话来描述:

将类型的职责分类为扩展

这些是我在各个扩展中加入的方面的示例:

  • 从客户端看到的类型主界面。
  • 协议一致性(即代理协议,通常是私有的)。
  • 序列化(例如所有NSCoding相关)。
  • 生活在后台线程上的部分类型,例如网络回调。

有时,当单个方面的复杂性上升时,我甚至会将类型的实现分割为多个文件。

以下是一些细节,描述了我如何对与实现相关的代码进行排序:

  • 重点是功能会员资格。
  • 保持公共和私人实施关闭,但是分开。
  • 请勿在{{1​​}}和var之间进行拆分。
  • 将功能实现的所有方面放在一起:嵌套类型,初始化器,协议一致性等。

优势

分离类型方面的主要原因是为了便于阅读和理解。

在阅读外国(或我自己的旧代码)时,理解大局通常是潜水中最困难的部分。让开发人员了解某种方法的背景有很多帮助。

还有另一个好处:访问控制可以更容易地无意中调用某些内容。只应从后台线程调用的方法可以在“background”扩展名中声明为func。现在它根本无法从其他地方调用。

当前限制

Swift 3对这种风格施加了某些限制。有几件事只能存在于主要类型的实现中:

  • 存储的属性
  • 覆盖func / var
  • overidable func / var
  • 必需(指定)初始化程序

这些限制(至少前三个)来自于必须事先知道对象的数据布局(以及纯Swift的见证表)。扩展可能在运行时期间(通过框架,插件,dlopen,...)延迟加载,并且在创建实例后更改类型的布局会使其​​ABI失效。

对Swift团队的一个温和的建议:)

保证一个模块的所有代码同时可用。如果Swift编译器允许在单个模块中“组合”类型,则可以避免阻止完全分离功能方面的限制。对于组合类型,我的意思是编译器将从模块中的所有文件中收集定义类型布局的所有声明。与语言的其他方面一样,它会自动找到文件内依赖项。

这将允许真正编写“面向方面”的扩展。不必在主声明中声明存储的属性或覆盖将实现更好的访问控制和关注点分离。

答案 2 :(得分:0)

我讨厌它。它增加了额外的复杂性,并且使扩展程序的使用变得混乱,因此不清楚人们期望使用扩展程序的原因。

如果您正在使用扩展来实现协议一致性,那么可以,我可以看到,但是为什么不只注释您的代码呢?这样更好吗我没看到。