However, you should always measure it yourself and decide based on your unique use case.
请考虑以下示例,该示例演示了使用 struct 和 class 包装 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)")
}
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")
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" )
16 回答
根据Swift中非常流行的WWDC 2015谈话定向编程(video,transcript),Swift提供了许多功能,使得结构在许多情况下比类更好 .
如果结构相对较小且可复制,则结构是优选的,因为复制比具有与类相同的实例的多个引用更安全 . 将变量传递给多个类和/或在多线程环境中时,这一点尤为重要 . 如果您始终可以将变量的副本发送到其他位置,则无需担心其他位置会更改您变量的值 .
使用Structs,更不用担心内存泄漏或多线程竞争访问/修改变量的单个实例 . (对于更具技术意识的人来说,例外情况是在闭包中捕获一个struct时,因为它实际上捕获了对实例的引用,除非你明确地将它标记为要复制) .
类也可能变得臃肿,因为类只能从单个超类继承 . 这鼓励我们创建巨大的超级类,其中包含许多不同的能力,这些能力只是松散相关的 . 使用协议,特别是协议扩展,您可以为协议提供实现,允许您消除类实现此类行为的需要 .
该演讲列出了这些场景首选的场景:
它意味着结构应该是默认的,类应该是一个后备 .
另一方面,The Swift Programming Language文档有些矛盾:
这里声称我们应该默认使用类并仅在特定情况下使用结构 . 最终,您需要了解值类型与引用类型的真实含义,然后您可以就何时使用结构或类做出明智的决定 . 另外,请记住,这些概念总是在不断发展,Swift编程语言文档是在面向协议编程讲话之前编写的 .
由于struct实例是在堆栈上分配的,并且类实例是在堆上分配的,因此结构有时可以更快 .
However, you should always measure it yourself and decide based on your unique use case.
请考虑以下示例,该示例演示了使用
struct
和class
包装Int
数据类型的两种策略 . 我使用10个重复值来更好地反映现实世界,你有多个领域 .使用表现来衡量绩效
代码可以在https://github.com/knguyen2708/StructVsClassPerformance找到
UPDATE (27 Mar 2018) :
截至Swift 4.0,Xcode 9.2,在iPhone 6S上运行Release版本,iOS 11.2.6,Swift编译器设置为
-O -whole-module-optimization
:class
版本耗时2.06秒struct
版本耗时4.17e-08秒(快50,000,000倍)(我不再平均多次运行,因为差异非常小,低于5%)
Note :如果没有整个模块优化,差异会大得多 . 如果有人能够指出实际的旗帜,我会很高兴的确实 .
UPDATE (7 May 2016) :
截至Swift 2.2.1,Xcode 7.3,在iPhone 6S,iOS 9.3.1上运行Release版本,平均超过5次运行,Swift Compiler设置为
-O -whole-module-optimization
:class
版本占用了2.159942142sstruct
版本耗时5.83E-08s(快37,000,000倍)Note :正如有人提到的那样,在实际场景中,结构中可能会有超过1个字段,我已经为10个字段而不是1个字符串添加了结构/类的测试 . 令人惊讶的是,结果变化不大 .
ORIGINAL RESULTS (2014年6月1日):
(在1个字段的结构/类上,而不是10)
截至Swift 1.2,Xcode 6.3.2,在iPhone 5S,iOS 8.3上运行Release版本,平均超过5次运行
class
版本占用了9.788332333sstruct
版本花了0.010532942s(快了900倍)OLD RESULTS (来自未知时间)
(在1个字段的结构/类上,而不是10)
在我的MacBook Pro上发布版本:
class
版本耗时1.10082秒struct
版本耗时0.02324秒(快50倍)结构和类之间的相似之处 .
我用简单的例子为此创造了要点 . https://github.com/objc-swift/swift-classes-vs-structures
和差异
1.继承 .
结构不能在swift中继承 . 如果你想
去上课 .
2.通过
Swift结构按值传递,类实例按引用传递 .
语境差异
结构常量和变量
示例(在WWDC 2014中使用)
定义一个名为Point的结构 .
现在,如果我尝试更改x . 它是一个有效的表达 .
但是,如果我将一个点定义为常数 .
在这种情况下,整点是不可变的常数 .
如果我使用了Point类,那么这是一个有效的表达式 . 因为在类中,不可变常量是对类本身的引用而不是其实例变量(除非那些变量定义为常量)
以下是其他一些需要考虑的原因:
要在课程中获得此功能,您必须添加初始化程序,并且 maintain 是初始化程序...
Array
等基本集合类型是结构体 . 您在自己的代码中使用它们的次数越多,您就越习惯于通过值而不是引用 . 例如:一些优点:
由于不可共享而自动进行线程安全
由于没有isa和refcount,
使用更少的内存(实际上通常是堆栈)
方法总是静态调度,因此可以内联(尽管@final可以为类执行此操作)
更容易推理(不需要像NSArray,NSString等那样典型的"defensively copy")与线程安全相同的原因
假设我们知道 Struct 是值类型而 Class 是引用类型 .
如果您不知道值类型和引用类型是什么,那么请参阅传递引用与传递值之间的区别是什么?
基于mikeash's post:
此外,当您必须覆盖函数的每个实例(即它们没有任何共享功能)时,请不要使用类 .
所以不要有一个类的几个子类 . 使用符合协议的几个结构 .
结构比Class快得多 . 此外,如果您需要继承,那么您必须使用Class . 最重要的一点是Class是引用类型而Structure是值类型 . 例如,
现在让我们创建两者的实例 .
现在让我们将这些实例传递给两个修改id,description,destination等的函数 .
也,
所以,
现在如果我们打印出flightA的id和描述,我们就会得到
在这里,我们可以看到FlightA的id和描述被更改,因为传递给modify方法的参数实际上指向了flightA对象的内存地址(引用类型) .
现在,如果我们打印出我们得到的FLightB实例的id和描述,
在这里我们可以看到FlightB实例没有改变,因为在modifyFlight2方法中,Flight2的实际实例是传递而不是引用(值类型) .
从值类型与引用类型的角度回答问题,从this Apple blog post看起来很简单:
正如那篇文章中提到的,没有可写属性的类将与结构相同,并且(我将添加)一个警告:结构最适合线程安全模型 - 现代应用程序体系结构中越来越迫切的要求 .
对于继承并通过引用传递的类,结构体没有继承并且通过值传递 .
在Swift上有很棒的WWDC Session ,这个具体的问题在其中一个中得到了非常详细的回答 . 请确保您观看这些内容,因为它可以让您比语言指南或iBook快得多 .
我不会说结构提供的功能较少 .
当然,除了变异功能之外,自我是不可改变的,但这就是它 .
只要你坚持每个类应该是抽象的或最终的好主意,继承就可以正常工作 .
将抽象类实现为协议,将最终类实现为结构 .
关于结构的好处是你可以使你的字段变得可变,而不创建共享的可变状态,因为写入时的副本负责:)
这就是为什么以下示例中的属性/字段都是可变的,我不会在Java或C#或swift类中执行这些操作 .
示例继承结构,在名为“example”的函数的底部有一些脏的和直接的用法:
Creational Pattern:
在swift中,Struct是一个自动克隆的 value types . 因此,我们获得了免费实现原型模式所需的行为 .
而 classes 是引用类型,在分配期间不会自动克隆 . 要实现原型模式,类必须采用
NSCopying
协议 .Shallow copy 仅复制指向那些对象的引用,而 deep copy 复制对象的引用 .
为每个 reference type 实现 deep copy 已成为一项繁琐的工作 . 如果类包含进一步的引用类型,我们必须为每个引用属性实现原型模式 . 然后我们必须通过实现
NSCopying
协议来实际复制整个对象图 .通过使用 structs and enums ,我们使代码更简单,因为我们不必实现复制逻辑 .
许多Cocoa API需要NSObject子类,这会强制您使用类 . 但除此之外,您可以使用Apple的Swift博客中的以下案例来决定是使用struct / enum值类型还是类引用类型 .
https://developer.apple.com/swift/blog/?id=10
Structs
是value type
和Classes
是reference type
值类型比引用类型快
值类型实例在多线程环境中是安全的,因为多线程可以在不必担心竞争条件或死锁的情况下改变实例
值类型没有与引用类型不同的引用;因此没有内存泄漏 .
在以下情况下使用
value
类型:在以下情况下使用
reference
类型:更多信息也可以在Apple文档中找到
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Additional Information
Swift值类型保留在堆栈中 . 在一个进程中,每个线程都有自己的堆栈空间,因此没有其他线程可以直接访问您的值类型 . 因此没有竞争条件,锁,死锁或任何相关的线程同步复杂性 .
值类型不需要动态内存分配或引用计数,这两者都是昂贵的操作 . 同时静态调度值类型的方法 . 这些在性能方面创造了支持 Value 类型的巨大优势 .
作为提醒,这里是Swift列表
Value types:
结构
枚举
元组
原语(Int,Double,Bool等)
集合(数组,字符串,字典,集)
Reference types:
class
任何来自NSObject的东西
功能
关闭
在这些答案中没有引起注意的一点是,持有类与结构的变量可以是
let
,同时仍然允许更改对象的属性,而不能使用结构执行此操作 .如果您不希望变量指向另一个对象,但仍需要修改该对象,即在您希望一个接一个地更新许多实例变量的情况下,这非常有用 . 如果它是一个结构,你必须允许使用
var
将变量重置为另一个对象才能执行此操作,因为Swift中的常量值类型正确地允许零变异,而引用类型(类)不会以这种方式运行 .由于struct是值类型,你可以非常容易地创建存储到stack.Struct中的内存可以很容易地访问,并且在工作范围之后,它可以通过堆栈顶部的pop来轻松地从堆栈内存中释放出来 . 另一方面,类是存储在堆中的引用类型,并且在一个类对象中所做的更改将影响到其他对象,因为它们是紧密耦合的和引用类型 . 结构的所有成员都是公共的,而类的所有成员都是私有的 .
struct的缺点是它不能被继承 .
结构和类是用户违反的数据类型
默认情况下,结构是公共的,而类是私有的
Class实现封装的主体
在堆内存上创建类的对象
类用于可重用性,而结构用于将数据分组到同一结构中
结构数据成员不能直接初始化,但可以由结构外部分配
类数据成员可以由参数less构造函数直接初始化,并由参数化构造函数指定