【问题标题】:How to properly use class extensions in Swift?如何在 Swift 中正确使用类扩展?
【发布时间】:2017-03-22 23:24:06
【问题描述】:

在 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 社区如何看待扩展。你怎么看?有银弹吗?

【问题讨论】:

  • 他们这样做只是为了 1.Hide 函数而不将所有函数文件标记为私有,2.Neater 看起来,就像每个扩展都可以做一些特定的工作。我真的没有反对这一点,在我的实践中它比 obj-c 中的 #pragma mark 更好,如果需要,他们还可以轻松地将其拆分为多个文件
  • 我们不能也说class A: B, C, D { // Initializers, stored properties etc. // Protocol implementation B... }

标签: ios swift swift-extensions system-design


【解决方案1】:

当然,这只是我的看法,所以我会轻松写下。

我目前在我的项目中使用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 类中编辑您的 uidname 值,但您不能在外面,因为您只会处理 User 协议类型。

【讨论】:

  • 这正是我的代码现在的结构方式,所以我很高兴看到至少我并不孤单:)
【解决方案2】:

我用了类似的方法,一句话就能形容:

将类型的职责分类到扩展中

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

  • 从客户端看到的类型的主界面。
  • 协议一致性(即委托协议,通常是私有的)。
  • 序列化(例如与NSCoding 相关的所有内容)。
  • 存在于后台线程中的类型的一部分,例如网络回调。

有时,当单个方面的复杂性增加时,我什至将一个类型的实现拆分到多个文件中。

这里有一些细节描述了我如何对实现相关的代码进行排序:

  • 重点是功能性成员资格。
  • 保持公共和私有实现紧密但分开。
  • 不要在varfunc 之间进行拆分。
  • 将功能实现的所有方面放在一起:嵌套类型、初始化程序、协议一致性等。

优势

将类型的各个方面分开的主要原因是为了使其更易于阅读和理解。

在阅读外国(或我自己的旧)代码时,了解大局通常是深入研究中最困难的部分。让开发人员了解某种方法的上下文很有帮助。

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

当前限制

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

  • 存储的属性
  • 覆盖 func/var
  • 可覆盖的函数/变量
  • 必需的(指定的)初始化程序

这些限制(至少是前三个)来自于需要提前知道对象的数据布局(以及纯 Swift 的见证表)。扩展可能会在运行时延迟加载(通过框架、插件、dlopen 等),并且在创建实例后更改类型的布局会破坏它们的 ABI。

给 Swift 团队的一个小建议 :)

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

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

【讨论】:

【解决方案3】:

我讨厌它。它增加了额外的复杂性并混淆了扩展程序的使用,使得人们不清楚人们使用扩展程序的目的。

如果您使用扩展来实现协议一致性,好的,我可以看到,但为什么不直接评论您的代码呢?这如何更好?我没看到。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-23
    • 2022-01-21
    • 1970-01-01
    • 2018-01-19
    • 2019-02-22
    相关资源
    最近更新 更多