玩Swift,来自Java背景,为什么要选择Struct而不是Class?看起来它们是一样的东西,只不过Struct提供的功能更少。那为什么选择它呢?
当前回答
从值类型和引用类型的角度来回答这个问题,从苹果博客的这篇文章来看,它看起来非常简单:
使用值类型[例如struct, enum]: 用==比较实例数据是有意义的 你希望副本有独立的状态 这些数据将在代码中跨多个线程使用 在以下情况下使用引用类型[例如class]: 比较实例标识和===是有意义的 您希望创建共享的、可变的状态
正如在那篇文章中提到的,没有可写属性的类将与结构体的行为相同,但有一点需要注意:结构体最适合线程安全模型——这是现代应用程序架构中日益迫切的需求。
其他回答
Structure and class are user defied data types By default, structure is a public whereas class is private Class implements the principal of encapsulation Objects of a class are created on the heap memory Class is used for re usability whereas structure is used for grouping the data in the same structure Structure data members cannot be initialized directly but they can be assigned by the outside the structure Class data members can be initialized directly by the parameter less constructor and assigned by the parameterized constructor
根据2015年非常流行的WWDC演讲,Swift中面向协议的编程(视频,文本),Swift提供了许多特性,使得结构在很多情况下比类更好。
如果结构相对较小且可复制,则更可取,因为复制比类中对同一个实例有多个引用要安全得多。当将一个变量传递给多个类和/或多线程环境时,这一点尤其重要。如果你总是可以将变量的副本发送到其他地方,你就不必担心其他地方会改变你下面变量的值。
使用Structs,不太需要担心内存泄漏或多个线程竞相访问/修改变量的单个实例。(对于更有技术头脑的人来说,例外情况是在闭包内捕获结构时,因为它实际上是在捕获实例的引用,除非您显式地将其标记为要复制)。
类也可能变得臃肿,因为一个类只能继承一个超类。这鼓励我们创建巨大的超类,其中包含许多不同的能力,而这些能力之间只有松散的关联。使用协议,特别是使用可以为协议提供实现的协议扩展,允许您消除实现此类行为所需的类。
演讲列出了优先使用类的这些场景:
复制或比较实例没有意义(例如,Window) 实例生命周期与外部效果(例如,TemporaryFile)相关联。 实例只是“接收器”——只写外部状态的管道(例如cgcontext)
这意味着结构应该是默认的,而类应该是备用的。
另一方面,Swift编程语言文档有些矛盾:
Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure. As a general guideline, consider creating a structure when one or more of these conditions apply: The structure’s primary purpose is to encapsulate a few relatively simple data values. It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure. Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced. The structure does not need to inherit properties or behavior from another existing type. Examples of good candidates for structures include: The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double. A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int. A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double. In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.
在这里,它声称我们应该只在特定的情况下默认使用类和使用结构。最后,您需要了解值类型与引用类型的实际含义,然后才能就何时使用结构或类做出明智的决定。此外,请记住,这些概念一直在发展,Swift编程语言文档是在面向协议编程演讲之前编写的。
这个答案最初是关于结构和类之间性能的差异。不幸的是,关于我使用的测量方法有太多的争议。我把它留在下面,但请不要过多地去理解它。我认为经过这么多年,在Swift社区中,struct(以及enum)由于其简单和安全而一直是首选。
如果性能对你的应用很重要,那就自己衡量。我仍然认为大多数时候结构性能更优越,但最好的答案就像有人在评论中说的那样:这要看情况。
===旧答案===
由于结构实例是在堆栈上分配的,而类实例是在堆上分配的,因此结构有时会快得多。
但是,您应该始终自己衡量它,并根据您独特的用例进行决定。
考虑下面的例子,它演示了使用结构和类包装Int数据类型的两种策略。我使用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 build, Swift编译器设置为-O -全模块优化:
类版本用时2.06秒 Struct版本耗时4.17e-08秒(快了50,000,000倍)
(我不再平均多次运行,因为方差非常小,低于5%)
注意:在没有整个模块优化的情况下,差异会小很多。如果有人能指出这面旗子到底是干什么的,我会很高兴。
更新(2016年5月7日):
Swift 2.2.1, Xcode 7.3,在iPhone 6S, iOS 9.3.1上运行Release build,平均运行5次,Swift编译器设置为-O -whole-module-optimization:
类版本花费了2.159942142s struct版本耗时5.83 e -08秒(快37,000,000倍)
注意:正如有人提到的,在现实场景中,一个结构中可能会有多个字段,我已经为结构/类添加了10个字段而不是1个字段的测试。令人惊讶的是,结果变化不大。
原始结果(2014年6月1日):
(在struct/class上运行,只有1个字段,而不是10个)
在Swift 1.2、Xcode 6.3.2、iPhone 5S和iOS 8.3上运行Release版本时,平均运行超过5次
类版本花费了9.788332333s Struct版本花费0.010532942秒(快900倍)
旧结果(未知时间)
(在struct/class上运行,只有1个字段,而不是10个)
在我的MacBook Pro上发布:
类版本花费了1.10082秒 struct版本花了0.02324秒(快了50倍)
许多Cocoa api需要NSObject子类,这迫使你使用class。但除此之外,你可以使用以下苹果Swift博客中的案例来决定是使用struct / enum值类型还是类引用类型。
https://developer.apple.com/swift/blog/?id=10
假设我们知道Struct是值类型,Class是引用类型。
如果你不知道值类型和引用类型是什么,那么看看按引用传递和按值传递之间的区别是什么?
根据mikeash的帖子:
... Let's look at some extreme, obvious examples first. Integers are obviously copyable. They should be value types. Network sockets can't be sensibly copied. They should be reference types. Points, as in x, y pairs, are copyable. They should be value types. A controller that represents a disk can't be sensibly copied. That should be a reference type. Some types can be copied but it may not be something you want to happen all the time. This suggests that they should be reference types. For example, a button on the screen can conceptually be copied. The copy will not be quite identical to the original. A click on the copy will not activate the original. The copy will not occupy the same location on the screen. If you pass the button around or put it into a new variable you'll probably want to refer to the original button, and you'd only want to make a copy when it's explicitly requested. That means that your button type should be a reference type. View and window controllers are a similar example. They might be copyable, conceivably, but it's almost never what you'd want to do. They should be reference types. What about model types? You might have a User type representing a user on your system, or a Crime type representing an action taken by a User. These are pretty copyable, so they should probably be value types. However, you probably want updates to a User's Crime made in one place in your program to be visible to other parts of the program. This suggests that your Users should be managed by some sort of user controller which would be a reference type. e.g struct User {} class UserController { var users: [User] func add(user: User) { ... } func remove(userNamed: String) { ... } func ... } Collections are an interesting case. These include things like arrays and dictionaries, as well as strings. Are they copyable? Obviously. Is copying something you want to happen easily and often? That's less clear. Most languages say "no" to this and make their collections reference types. This is true in Objective-C and Java and Python and JavaScript and almost every other language I can think of. (One major exception is C++ with STL collection types, but C++ is the raving lunatic of the language world which does everything strangely.) Swift said "yes," which means that types like Array and Dictionary and String are structs rather than classes. They get copied on assignment, and on passing them as parameters. This is an entirely sensible choice as long as the copy is cheap, which Swift tries very hard to accomplish. ...
我个人不会这样命名我的类。我通常将我的命名为UserManager而不是UserController,但想法是一样的
另外,当你必须重写一个函数的每个实例(即它们没有任何共享功能)时,不要使用类。
所以不是一个类的几个子类。使用几个符合协议的结构体。
使用结构体的另一种合理情况是,当你想对新旧模型进行delta/diff运算时。对于引用类型,你不能开箱即用。对于值类型,突变是不共享的。