blu*_*l2k 465 struct class design-principles swift
使用Swift,来自Java背景,为什么要选择Struct而不是Class?看起来它们是相同的,使用Struct提供更少的功能.为什么选择呢?
dre*_*wag 531
根据Swift中非常流行的WWDC 2015谈话定向编程(视频,脚本),Swift提供了许多功能,使得结构在许多情况下比类更好.
如果结构相对较小且可复制,则结构是优选的,因为复制比具有与类相同的实例的多个引用更安全.将变量传递给多个类和/或在多线程环境中时,这一点尤为重要.如果您始终可以将变量的副本发送到其他位置,则无需担心其他位置会更改您变量的值.
使用Structs,更不用担心内存泄漏或多线程竞争访问/修改变量的单个实例.(对于更具技术意识的人来说,例外情况是在闭包中捕获一个struct时,因为它实际上捕获了对实例的引用,除非你明确标记它被复制).
类也可能变得臃肿,因为类只能从单个超类继承.这鼓励我们创建巨大的超级类,其中包含许多不同的能力,这些能力只是松散相关的.使用协议,特别是协议扩展,您可以为协议提供实现,允许您消除类实现此类行为的需要.
该演讲列出了首选类的场景:
- 复制或比较实例没有意义(例如,Window)
- 实例生命周期与外部效应相关(例如,TemporaryFile)
- 实例只是"下沉" - 外部状态的只写管道(例如CGContext)
它意味着结构应该是默认的,类应该是一个后备.
另一方面,Swift编程语言文档有些矛盾:
结构实例始终按值传递,类实例始终通过引用传递.这意味着它们适用于不同类型的任务.在考虑项目所需的数据结构和功能时,请确定是将每个数据结构定义为类还是结构.
作为一般准则,考虑在满足以下一个或多个条件时创建结构:
- 该结构的主要目的是封装一些相对简单的数据值.
- 当您分配或传递该结构的实例时,期望复制封装的值而不是引用是合理的.
- 结构存储的任何属性本身都是值类型,也可以复制而不是引用它们.
- 该结构不需要从另一个现有类型继承属性或行为.
结构的良好候选者的例子包括:
- 几何形状的大小,可能封装了width属性和height属性,两者都是Double类型.
- 一种引用系列中的范围的方法,可能包含类型为Int的start属性和length属性.
- 三维坐标系中的一个点,可能包含x,y和z属性,每个属性为Double.
在所有其他情况下,定义一个类,并创建要通过引用管理和传递的类的实例.实际上,这意味着大多数自定义数据结构应该是类,而不是结构.
这里声称我们应该默认只在特定情况下使用类和使用结构.最终,您需要了解值类型与引用类型的真实含义,然后您可以就何时使用结构或类做出明智的决定.另外,请记住,这些概念总是在不断发展,Swift编程语言文档是在面向协议编程讲话之前编写的.
Kha*_*yen 158
由于struct实例是在堆栈上分配的,并且类实例是在堆上分配的,因此结构有时可以更快.
但是,您应该自己测量它并根据您的独特用例来决定.
请考虑以下示例,该示例演示了Int使用struct和包装数据类型的两种策略class.我使用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)
}
Run Code Online (Sandbox Code Playgroud)
使用表现来衡量绩效
// 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)")
}
Run Code Online (Sandbox Code Playgroud)
代码可以在https://github.com/knguyen2708/StructVsClassPerformance找到
更新(2018年3月27日):
截至Swift 4.0,Xcode 9.2,在iPhone 6S上运行Release版本,iOS 11.2.6,Swift Compiler设置为-O -whole-module-optimization:
class 版本用了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版本,平均超过5次运行,Swift Compiler设置为-O -whole-module-optimization:
class 版本花了2.159942142sstruct 版本耗时5.83E-08s(快37,000,000倍)注意:正如有人提到的那样,在实际场景中,结构中可能会有超过1个字段,我已经为10个字段而不是1个字符串添加了结构/类的测试.令人惊讶的是,结果变化不大.
原始结果(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倍)旧结果(来自未知时间)
(在1个字段的结构/类上,而不是10)
在我的MacBook Pro上发布版本:
class版本耗时1.10082秒struct版本耗时0.02324秒(快50倍)Mad*_*Nik 60
我用简单的例子为此创造了要点. https://github.com/objc-swift/swift-classes-vs-structures
结构不能在swift中继承.如果你想
class Vehicle{
}
class Car : Vehicle{
}
Run Code Online (Sandbox Code Playgroud)
去上课.
Swift结构按值传递,类实例按引用传递.
结构常量和变量
示例(在WWDC 2014上使用)
struct Point{
var x = 0.0;
var y = 0.0;
}
Run Code Online (Sandbox Code Playgroud)
定义一个名为Point的结构.
var point = Point(x:0.0,y:2.0)
Run Code Online (Sandbox Code Playgroud)
现在,如果我尝试更改x.它是一个有效的表达.
point.x = 5
Run Code Online (Sandbox Code Playgroud)
但是如果我将一个点定义为常数.
let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.
Run Code Online (Sandbox Code Playgroud)
在这种情况下,整点是不可变的常数.
如果我使用了Point类,那么这是一个有效的表达式.因为在类中,不可变常量是对类本身的引用而不是它的实例变量(除非那些变量定义为常量)
Dan*_*ark 28
以下是其他一些需要考虑的原因:
structs获得一个自动初始化程序,您根本不需要在代码中维护它.
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")
Run Code Online (Sandbox Code Playgroud)要在课程中获得此功能,您必须添加初始化程序,并维护初始化程序...
基本集合类型,如Array结构.您在自己的代码中使用它们的次数越多,您就越习惯于通过值而不是引用.例如:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
Run Code Online (Sandbox Code Playgroud)显然,不变性与可变性是一个很大的话题,但很多聪明的人认为不可变性 - 在这种情况下的结构 - 是更可取的.可变对象与不可变对象
Hon*_*ney 23
假设我们知道Struct是一个值类型而Class是一个引用类型.
如果你不知道什么是值类型和引用类型,然后看看有什么引用传递与路过值之间的差异?
基于mikeash的帖子:
......让我们先看看一些极端明显的例子.整数显然是可复制的.它们应该是价值类型.网络套接字无法合理复制.它们应该是引用类型.点,如x,y对,是可复制的.它们应该是价值类型.代表磁盘的控制器无法合理复制.那应该是一个参考类型.
某些类型可以被复制,但它可能不是您想要一直发生的事情.这表明它们应该是引用类型.例如,屏幕上的按钮可以在概念上被复制.副本与原件不完全相同.单击副本不会激活原件.副本不会占据屏幕上的相同位置.如果您传递按钮或将其放入新变量中,您可能需要引用原始按钮,并且您只想在明确请求时制作副本.这意味着您的按钮类型应该是引用类型.
视图和窗口控制器是一个类似的例子.可以想象它们可以复制,但它几乎不是你想要做的.它们应该是引用类型.
模型类型怎么样?您可能具有表示系统上用户的User类型,或表示用户采取的操作的Crime类型.这些都是可复制的,所以它们应该是值类型.不过,你可能要更新到用户的犯罪在一个地方在你的程序做成的程序的其他部分可见. 这表明您的用户应该由某种用户控制器管理,该用户控制器将是一种引用类型.例如
Run Code Online (Sandbox Code Playgroud)struct User {} class UserController { var users: [User] func add(user: User) { ... } func remove(userNamed: String) { ... } func ... }收藏是一个有趣的案例.这些包括数组和字典,以及字符串.它们是可复制的吗?明显.是否经常复制你想要发生的事情?那不太清楚.
大多数语言对此说"不"并使其集合引用类型.这在Objective-C和Java,Python和JavaScript以及我能想到的几乎所有其他语言中都是如此.(一个主要的例外是带有STL集合类型的C++,但C++是语言世界的狂热疯子,它可以完成任何奇怪的工作.)
Swift说"是的",这意味着像Array,Dictionary和String这样的类型是结构而不是类.它们在赋值时被复制,并在作为参数传递时被复制.这是一个完全合理的选择,只要副本很便宜,Swift很难完成....
此外,当您必须覆盖函数的每个实例(即它们没有任何共享功能)时,请不要使用类.
所以不要有一个类的几个子类.使用符合协议的几个结构.
Cat*_*Man 18
一些优点:
小智 12
结构比Class快得多.此外,如果您需要继承,那么您必须使用Class.最重要的一点是Class是引用类型而Structure是值类型.例如,
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
}
Run Code Online (Sandbox Code Playgroud)
现在让我们创建两者的实例.
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )
Run Code Online (Sandbox Code Playgroud)
现在让我们将这些实例传递给两个修改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"
}
Run Code Online (Sandbox Code Playgroud)
也,
func modifyFlight2(flight2: Flight2) -> Void {
var passedFlight = flight2
passedFlight.id = 200
passedFlight.description = "second flight from virgin airlines"
}
Run Code Online (Sandbox Code Playgroud)
所以,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
Run Code Online (Sandbox Code Playgroud)
现在如果我们打印出flightA的id和描述,我们就会得到
id = 200
description = "second flight of Virgin Airlines"
Run Code Online (Sandbox Code Playgroud)
在这里,我们可以看到FlightA的id和描述已更改,因为传递给modify方法的参数实际上指向flightA对象的内存地址(引用类型).
现在如果我们打印出我们得到的FLightB实例的id和描述,
id = 100
description = "first ever flight of Virgin Airlines"
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到FlightB实例没有改变,因为在modifyFlight2方法中,Flight2的实际实例是传递而不是引用(值类型).
Structs是value type和Classes是reference type
在以下情况下使用value类型:
在以下情况下使用reference类型:
进一步的信息也可以在Apple文档中找到
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
附加信息
Swift值类型保留在堆栈中。在一个进程中,每个线程都有其自己的堆栈空间,因此,其他线程将无法直接访问您的值类型。因此,没有竞争条件,锁,死锁或任何相关的线程同步复杂性。
值类型不需要动态内存分配或引用计数,这两者都是昂贵的操作。同时,值类型的方法是静态分派的。这些在性能方面为值类型提供了巨大的优势。
提醒一下,这里是Swift列表
值类型:
参考类型:
从值类型与引用类型的角度回答问题,在此Apple博客文章中,它看起来非常简单:
在以下情况下,请使用值类型[例如struct,enum]:
- 将实例数据与==进行比较很有意义
- 您希望副本具有独立状态
- 数据将在多个线程的代码中使用
在以下情况下,使用引用类型[例如类]:
- 将实例身份与===进行比较比较有意义
- 您要创建共享的可变状态
如该文章所述,没有可写属性的类的行为与结构相同,但有一个警告:(我要补充一点):结构最适合线程安全模型 -现代应用程序体系结构中日益迫切的需求。
结构与类
Struct更优选。但Struct默认情况下并不能解决所有问题。通常您会听到“value type在堆栈上分配”,但事实并非总是如此。仅局部变量分配在堆栈上
//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)
}
Run Code Online (Sandbox Code Playgroud)
*您不应该使用/依赖retainCount,因为它没有说有用的信息
检查堆栈或堆
在编译期间Swift Intermediate Language(SIL)可以优化您的代码
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
Run Code Online (Sandbox Code Playgroud)
在那里你可以找到alloc_stack(堆栈上的分配)和alloc_box(堆上的分配)
[优化级别(SWIFT_OPTIMIZATION_LEVEL)]
| 归档时间: |
|
| 查看次数: |
143265 次 |
| 最近记录: |