在使用Swift时玩耍,来自Java背景,为什么要选择结构体而不是类?似乎它们是相同的东西,只是结构体提供的功能较少。那为什么要选择它呢?
在使用Swift时玩耍,来自Java背景,为什么要选择结构体而不是类?似乎它们是相同的东西,只是结构体提供的功能较少。那为什么要选择它呢?
类也可能变得臃肿,因为一个类只能继承自一个超类。这鼓励我们创建包含许多不太相关的能力的庞大超类。使用协议,特别是通过协议扩展提供实现的方式,可以消除类需要实现此类行为的需求。
演讲概述了以下情况下优先选择类:
- 复制或比较实例没有意义(例如
Window
)- 实例生命周期与外部影响相关(例如
TemporaryFile
)- 实例只是“接口”——只写的对外状态通道(例如
CGContext
)
这意味着结构体应该是默认选择,而类应作为备选。
另一方面,The Swift Programming Language文档有些自相矛盾:
结构实例总是按值传递,而类实例总是按引用传递。这意味着它们适用于不同类型的任务。在考虑项目所需的数据结构和功能时,请决定每个数据结构应该定义为类还是结构。原本这篇回答是关于struct和class在性能方面的区别。不幸的是,我使用的测量方法引起了太多争议。我将它保留在下面,但请不要过多解读。我认为,在这么多年之后,Swift社区已经清楚地认识到,由于其简单性和安全性,struct(以及enum)始终是首选。
如果性能对你的应用程序很重要,请自行进行测量。我仍然认为大多数情况下,struct性能优于class,但最好的答案就像评论中有人所说的那样:这取决于情况。
=== 旧回答 ===
由于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找到。-O -whole-module-optimization
:
- class
版本花费了2.06秒
- struct
版本花费了4.17e-08秒(快了5000万倍)-O -whole-module-optimization
:
- class
版本需要2.159942142秒
- struct
版本需5.83E-08秒(快3700万倍)class
版本需要9.788332333秒
- struct
版本需0.010532942秒(快了900倍)class
版本需要1.10082秒
- struct
版本需要0.02324秒(快50倍)我创建了一个简单示例的代码片段。 https://github.com/objc-swift/swift-classes-vs-structures
在 Swift 中,结构体无法继承。如果你想要继承,那么你需要使用类。
class Vehicle{
}
class Car : Vehicle{
}
参加一堂课程。
Swift中,结构体采用传值方式,而类实例采用传引用方式。
结构体常量和变量
示例(用于WWDC 2014)
struct Point{
var x = 0.0;
var y = 0.0;
}
定义了一个名为 Point 的结构体。
var point = Point(x:0.0,y:2.0)
现在如果我尝试改变x,这是一个有效的表达式。
point.x = 5
但是如果我将一个点定义为常量。
let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.
在这种情况下,整个点是不可变常量。
如果我使用了一个Point类,那么这就是一个有效的表达式。因为在类中,不可变常量是对类本身的引用,而不是它的实例变量(除非这些变量被定义为常量)。
如果您不知道什么是值类型和引用类型,请参见按引用传递与按值传递的区别是什么?。
基于 mikeash的帖子:
我个人不会使用这样的类名。我通常使用UserManager而不是UserController,但是想法是相同的。... 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. ...
另一个使用结构体的合理情况是当您想要对旧模型和新模型进行差异/比较时。使用引用类型无法直接完成此操作。使用值类型,变异不会被共享。
以下是考虑的其他原因:
结构体会自动初始化,您完全不需要在代码中维护它。
struct MorphProperty {
var type : MorphPropertyValueType
var key : String
var value : AnyObject
enum MorphPropertyValueType {
case String, Int, Double
}
}
var m = MorphProperty(type: .Int, key: "what", value: "blah")
为了将其放入类中,您需要添加初始化程序并 维护 初始化程序...
基本的集合类型(如Array
)是结构体。您在自己的代码中使用它们越多,就越习惯按值传递而不是引用传递。例如:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
显然,不可变性和可变性是一个很大的话题,但许多聪明的人认为在这种情况下不可变性——即结构体——更可取。可变 vs 不可变对象
internal
之外使用它,你需要自己编写初始化器。 - Abizernmutating
,以便明确表示哪些函数会更改它们的状态。但是,它们作为 值类型 的本质非常重要。如果你使用 let
声明一个结构体,你将无法对其调用任何可变函数。WWDC 15 上关于通过值类型进行更好编程的视频是一个绝佳资源。 - Abizern一些优势:
结构体
是值类型
,而类
是引用类型
当:
值
类型当:
引用
类型。进一步的信息也可以在苹果文档中找到
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
附加信息
Swift值类型存储在堆栈中。在进程中,每个线程都有自己的堆栈空间,因此其他线程将无法直接访问您的值类型。因此,没有竞争条件、锁定、死锁或任何相关的线程同步复杂性。
值类型不需要动态内存分配或引用计数,这两者都是昂贵的操作。同时,值类型上的方法是静态分派的。这些优势在性能方面给值类型创造了巨大的优势。
以下是 Swift 的值类型列表:
值类型:
引用类型:
结构体比类更快,如果需要继承,则必须使用类。最重要的一点是,类是引用类型,而结构体是值类型。例如:
class Flight {
var id:Int?
var description:String?
var destination:String?
var airlines:String?
init(){
id = 100
description = "first ever flight of Virgin Airlines"
destination = "london"
airlines = "Virgin Airlines"
}
}
struct Flight2 {
var id:Int
var description:String
var destination:String
var airlines:String
}
现在让我们创建两个实例。
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )
现在让我们将这些实例传递给两个函数,这些函数会修改id、description、destination等内容。
func modifyFlight(flight:Flight) -> Void {
flight.id = 200
flight.description = "second flight of Virgin Airlines"
flight.destination = "new york"
flight.airlines = "Virgin Airlines"
}
另外,
func modifyFlight2(flight2: Flight2) -> Void {
var passedFlight = flight2
passedFlight.id = 200
passedFlight.description = "second flight from virgin airlines"
}
所以,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
现在,如果我们打印flightA的id和描述,我们会得到:
id = 200
description = "second flight of Virgin Airlines"
在这里,我们可以看到FlightA的id和description已经被更改,因为传递给修改方法的参数实际上是指向flightA对象(引用类型)的内存地址。
现在,如果我们打印FLightB实例的id和description,我们会得到:
id = 100
description = "first ever flight of Virgin Airlines"
在这里我们可以看到,FlightB实例没有被改变,因为在modifyFlight2方法中传递的是Flight2的实际实例而不是引用(值类型)。
结构体 vs 类
结构体
更加可取。但是结构体
并不能默认解决所有问题。通常你会听到值类型
被分配在栈上,但这并不总是正确的。只有局部变量才会被分配在栈上。
//simple blocks
struct ValueType {}
class ReferenceType {}
struct StructWithRef {
let ref1 = ReferenceType()
}
class ClassWithRef {
let ref1 = ReferenceType()
}
func foo() {
//simple blocks
let valueType1 = ValueType()
let refType1 = ReferenceType()
//RetainCount
//StructWithRef
let structWithRef1 = StructWithRef()
let structWithRef1Copy = structWithRef1
print("original:", CFGetRetainCount(structWithRef1 as CFTypeRef)) //1
print("ref1:", CFGetRetainCount(structWithRef1.ref1)) //2 (originally 3)
//ClassWithRef
let classWithRef1 = ClassWithRef()
let classWithRef1Copy = classWithRef1
print("original:", CFGetRetainCount(classWithRef1)) //2 (originally 3)
print("ref1:", CFGetRetainCount(classWithRef1.ref1)) //1 (originally 2)
}
swiftc -emit-silgen -<optimization> <file_name>.swift
//e.g.
swiftc -emit-silgen -Onone file.swift
//emit-silgen -> emit-sil(is used in any case)
//-emit-silgen Emit raw SIL file(s)
//-emit-sil Emit canonical SIL file(s)
//optimization: O, Osize, Onone. It is the same as Swift Compiler - Code Generation -> Optimization Level
alloc_stack
(堆栈分配)和alloc_box
(堆分配)。