何时在Swift中使用类而不是结构体?在Swift中最好的设计模式是基于协议的编程还是基于对象的编程?
何时在Swift中使用类而不是结构体?在Swift中最好的设计模式是基于协议的编程还是基于对象的编程?
玩弄Swift时,如果您来自Java背景,为什么要选择结构体而不是类呢?看起来它们是一样的,但是结构体提供的功能比类要少。那为什么要选择它呢?
这篇回答起源于struct和class性能之间的差异。不幸的是,关于我用于测量的方法有太多争议。我将其保留在下面,但请不要对此过分解读。我认为经过这么多年,Swift社区已经很清楚struct(以及enum)由于其简单和安全性总是优先的。
如果性能对你的应用很重要,那么请自己做出测量。我仍然认为大多数情况下struct性能更优,但最好的答案正如评论中有人说的:这取决于具体情况。
=== 旧答案 ===
由于struct实例在堆栈上分配,而class实例在堆上分配,因此struct有时可以快得多。
然而,你应该根据你的独特用例自己测量并做出决策。
考虑以下示例,它演示了使用struct和class包装Int数据类型的2种策略。我使用重复了10次的值,以更好地反映实际情况,其中你有多个字段。
class Int10Class { let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int init(_ val: Int) { self.value1 = val self.value2 = val self.value3 = val self.value4 = val self.value5 = val self.value6 = val self.value7 = val self.value8 = val self.value9 = val self.value10 = val } } struct Int10Struct { let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int init(_ val: Int) { self.value1 = val self.value2 = val self.value3 = val self.value4 = val self.value5 = val self.value6 = val self.value7 = val self.value8 = val self.value9 = val self.value10 = val } } func + (x: Int10Class, y: Int10Class) -> Int10Class { return IntClass(x.value + y.value) } func + (x: Int10Struct, y: Int10Struct) -> Int10Struct { return IntStruct(x.value + y.value) }
使用以下代码测量性能
// Measure Int10Class measure("class (10 fields)") { var x = Int10Class(0) for _ in 1...10000000 { x = x + Int10Class(1) } } // Measure Int10Struct measure("struct (10 fields)") { var y = Int10Struct(0) for _ in 1...10000000 { y = y + Int10Struct(1) } } func measure(name: String, @noescape block: () -> ()) { let t0 = CACurrentMediaTime() block() let dt = CACurrentMediaTime() - t0 print("\(name) -> \(dt)") }
代码可以在https://github.com/knguyen2708/StructVsClassPerformance找到
更新(2018年3月27日):
截至Swift 4.0,Xcode 9.2,在iPhone 6S,iOS 11.2.6上运行Release构建,Swift编译器设置为-O整个模块优化
:
class
版本花费了2.06秒struct
版本花费了4.17e-08秒(快了5000万倍)
(我不再对多次运行取平均值,因为方差非常小,小于5%)
注意:如果不进行整个模块优化,则差异要小得多。如果有人能够指出这个标志实际上是做什么的那我会很高兴。
更新(2016年5月7日):
截至Swift 2.2.1,Xcode 7.3,在iPhone 6S,iOS 9.3.1上运行Release构建,平均运行了5次,Swift编译器设置为-O整个模块优化
:
class
版本花费了2.159942142秒struct
版本花费了5.83E-08秒(快了3700万倍)
注意:如有人提到,在实际情况下,在struct中可能会有超过1个字段,我已添加了测试来测试具有10个字段而不是1个字段的struct和class。令人惊讶的是,结果没有太大差异。
原始结果(2014年6月1日):
(运行在具有1个字段的struct/class上,而不是10个)
截至Swift 1.2,Xcode 6.3.2,在iPhone 5S,iOS 8.3上运行Release构建,平均运行了5次
class
版本花费了9.788332333秒struct
版本花费了0.010532942秒(快了900倍)
旧结果(来自未知时间)
(对于只有1个字段的struct/class进行运行测试,而非10个)
在我 MacBook Pro 上进行 Release 构建的结果如下:
class
版本耗时 1.10082 秒struct
版本耗时 0.02324 秒(快了50倍)
根据2015年非常热门的WWDC 2015黄金议题- Swift中的面向协议编程(video, transcript),Swift提供了许多使得结构体在许多情况下比类更好的特性。
如果结构体比较小且能够被拷贝,那它们就更受欢迎了,因为复制要比多次引用同一个实例更加安全。当将变量传递给多个类和/或多线程环境时,这点尤为重要。如果你总是可以将变量的副本发送到其他地方,你就不必担心其他位置在你之下更改变量的值。
使用结构体,就不需要太担心内存泄漏或多个线程竞争访问/修改单个变量实例的问题。(对于技术方面较为敏感的人,唯一的例外是在闭包内捕获结构体时,因为此时它实际上是捕获实例的引用,除非你显式地标记它要被复制)。
类也可能变得臃肿,因为类只能继承自一个超类。这促使我们创建庞大的超类,涵盖许多只有松散相关的不同能力。使用协议尤其是协议扩展,你可以提供协议的实现,从而消除了需要类来实现这种行为的必要性。
该谈话列出了以下情况下首选类:
- 拷贝或比较实例没有意义(例如,窗口)
- 实例生存期与外部影响有关(例如,临时文件)
- 实例只是“接收器”,即只写的外部状态(例如CGContext)
它暗示结构体应该是默认的,而类应该是备选项。
另一方面,The Swift Programming Language文档有些自相矛盾:
结构体实例始终通过值传递,而类实例始终通过引用传递。这意味着它们适用于不同类型的任务。在考虑项目所需的数据结构和功能时,请决定每个数据结构是应定义为类还是结构体。
作为一般指导方针,请考虑在以下一种或多种情况下创建结构体:
- 结构体的主要目的是封装一些相对简单的数据值。
- 当你分配或传递结构体实例时,预计将复制封装的值,而不是引用它们。
- 结构体存储的任何属性本身都是值类型,这些值类型也应该被复制,而不是引用。
- 结构体不需要从另一个现有类型继承属性或行为。
适用于结构体的示例:
- 一个几何形状的大小,可能包括一个宽度属性和一个高度属性,均为Double类型。
- 引用系列内范围的方式,可能包括一个起始属性和一个长度属性,均为Int类型。
- 3D坐标系中的一个点,可能包括x、y和z属性,均为Double类型。
在所有其他情况下,定义一个类,并创建该类的实例以进行管理和传递引用。实际上,这意味着大多数自定义数据结构应该是类,而不是结构体。
在这里,它声称我们应该默认使用类,并仅在特定情况下使用结构体。最终,您需要了解值类型与引用类型的现实世界影响,然后才能做出有根据的决定何时使用结构体或类。此外,请记住,这些概念始终在不断发展,Swift编程语言文档是在 Protocol Oriented Programming 演讲之前编写的。