ChatGPT解决这个技术问题 Extra ChatGPT

为什么选择结构而不是类?

使用 Swift,来自 Java 背景,为什么要选择 Struct 而不是 Class?似乎它们是相同的东西,结构提供的功能较少。那为什么选择它?

结构在代码中传递时总是被复制,并且不使用引用计数。来源:developer.apple.com/library/prerelease/ios/documentation/swift/…
我会说结构更适合保存数据,而不是逻辑。用 Java 术语来说,将结构想象为“值对象”。
我很惊讶在整个对话中没有直接提到写时复制,也就是惰性复制。由于这种设计,对结构复制性能的任何担忧大多都没有实际意义。
选择一个结构而不是一个类不是见仁见智的问题。选择其中一个有特定的原因。
我强烈建议您查看 Why Array is not threadSafe。这是相关的,因为 Arrays &结构都是值类型。这里的所有答案都提到,结构/数组/值类型永远不会有线程安全问题,但在一个极端情况下你会遇到。

I
Iulian Onofrei

根据非常流行的 WWDC 2015 演讲 Protocol Oriented Programming in Swift (video, transcript),Swift 提供了许多特性,使结构在许多情况下优于类。

如果结构相对较小且可复制,则结构更可取,因为复制比使用类对同一实例进行多个引用更安全。当将变量传递给许多类和/或在多线程环境中时,这一点尤其重要。如果您始终可以将变量的副本发送到其他地方,那么您永远不必担心其他地方会更改您下面的变量的值。

使用 Structs,不必担心内存泄漏或多个线程竞相访问/修改变量的单个实例。 (对于更有技术头脑的人,例外情况是在闭包内捕获结构时,因为它实际上是在捕获对实例的引用,除非您明确将其标记为要复制)。

类也可能变得臃肿,因为一个类只能从一个超类继承。这鼓励我们创建巨大的超类,其中包含许多只是松散相关的不同能力。使用协议,尤其是可以提供协议实现的协议扩展,可以让您消除对实现此类行为的类的需求。

该演讲列出了首选类的这些场景:

复制或比较实例没有意义(例如,Window) 实例生命周期与外部效果相关联(例如,TemporaryFile) 实例只是“接收器”--只写管道到外部状态(例如CGContext)

这意味着结构应该是默认的,类应该是后备的。

另一方面,The Swift Programming Language 文档有些矛盾:

结构实例总是按值传递,类实例总是按引用传递。这意味着它们适用于不同类型的任务。当您考虑项目所需的数据结构和功能时,请决定是否应将每个数据结构定义为类或结构。作为一般准则,当满足以下一个或多个条件时,请考虑创建结构: 结构的主要目的是封装一些相对简单的数据值。当您分配或传递该结构的实例时,期望封装的值将被复制而不是引用是合理的。结构存储的任何属性本身都是值类型,也应该被复制而不是引用。该结构不需要从另一个现有类型继承属性或行为。结构的良好候选示例包括: 几何形状的大小,可能封装了宽度属性和高度属性,两者都是 Double 类型。一种引用系列中范围的方法,可能封装了 Int 类型的 start 属性和 length 属性。 3D 坐标系中的一个点,可能封装了 x、y 和 z 属性,每个属性都是 Double 类型。在所有其他情况下,定义一个类,并创建该类的实例以通过引用进行管理和传递。实际上,这意味着大多数自定义数据结构应该是类,而不是结构。

这里声称我们应该默认使用类并仅在特定情况下使用结构。最终,您需要了解值类型与引用类型在现实世界中的含义,然后您可以就何时使用结构或类做出明智的决定。另外,请记住,这些概念一直在发展,并且 Swift 编程语言文档是在面向协议编程演讲之前编写的。


@ElgsQianChen 这篇文章的重点是应该默认选择 struct ,并且只在必要时使用 class 。结构更安全且无错误,尤其是在多线程环境中。是的,您始终可以使用类代替结构,但结构更可取。
@drewag 这似乎与它所说的完全相反。它是说你应该默认使用一个类,而不是一个结构In practice, this means that most custom data constructs should be classes, not structures.你能向我解释一下,读完之后你会如何理解大多数数据集应该是结构而不是类?当某些东西应该是结构时,他们给出了一组特定的规则,并且几乎说“类更好的所有其他场景”。
最后一行应该说,“我的个人建议与文档相反:”......然后这是一个很好的答案!
Swift 2.2 的书仍然说在大多数情况下使用类。
Struct over Class 绝对是降低复杂度。但是当结构成为默认选择时,对内存使用的影响是什么。当东西被复制到各处而不是引用时,它应该会增加应用程序的内存使用量。不应该吗?
K
Khanh Nguyen

这个答案最初是关于结构和类之间的性能差异。不幸的是,围绕我用于测量的方法存在太多争议。我把它留在下面,但请不要读太多。我认为这么多年过去了,在 Swift 社区中已经很清楚 struct(连同 enum)由于其简单性和安全性而始终是首选。

如果性能对您的应用很重要,请自行衡量。我仍然认为大多数时候 struct 性能是优越的,但最好的答案就像评论中有人说的那样:这取决于。

=== 旧答案 ===

由于结构实例是在堆栈上分配的,而类实例是在堆上分配的,因此结构有时会快得多。

但是,您应该始终自己衡量并根据您的独特用例做出决定。

考虑以下示例,该示例演示了使用 structclass 包装 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 build,Swift Compiler 设置为 -O -whole-module-optimization

类版本耗时 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 Compiler 设置为 -O -whole-module-optimization

类版本耗时 2.159942142s

struct 版本耗时 5.83E-08s(快 37,000,000 倍)

注意:正如有人提到的那样,在现实世界的场景中,一个结构中可能会有多个字段,我已经为结构/类添加了 10 个字段而不是 1 个字段的测试。令人惊讶的是,结果变化不大。

原始结果(2014 年 6 月 1 日):

(在结构/类上运行 1 个字段,而不是 10 个)

从 Swift 1.2、Xcode 6.3.2 开始,在 iPhone 5S、iOS 8.3 上运行 Release build,平均运行超过 5 次

类版本耗时 9.788332333s

struct 版本耗时 0.010532942s(快 900 倍)

旧结果(来自未知时间)

(在结构/类上运行 1 个字段,而不是 10 个)

在我的 MacBook Pro 上发布版本:

课程版本耗时 1.10082 秒

struct 版本耗时 0.02324 秒(快 50 倍)


没错,但似乎复制一堆结构比复制对单个对象的引用要慢。换句话说,复制单个指针比复制任意大的内存块要快。
-1 这个测试不是一个很好的例子,因为结构上只有一个 var。请注意,如果您添加多个值和一个或两个对象,则结构版本与类版本相当。添加的变量越多,结构版本的速度就越慢。
@joshrl 明白了你的意思,但一个例子是否“好”取决于具体情况。这段代码是从我自己的应用程序中提取的,因此它是一个有效的用例,并且使用结构确实极大地提高了我的应用程序的性能。这可能不是一个常见的用例(嗯,常见的用例是,对于大多数应用程序来说,没有人关心数据传递的速度有多快,因为瓶颈发生在其他地方,例如网络连接,无论如何,优化不是当您拥有具有 GB 或 RAM 的 GHz 设备时至关重要)。
据我了解,快速复制被优化为在写入时发生。这意味着除非新副本即将被更改,否则不会创建物理内存副本。
2016 年的结果看起来有缺陷。 iPhone 6S 中的 A9 运行在 1.8 GHz,这意味着一个时钟周期大约需要 5.6E-10 秒。您的基准测试耗时 5.83E-08 秒,换句话说:大约 100 个时钟周期。这意味着您的 CPU 每个周期执行 100,000 次添加操作。我的猜测是,要么编译器删除了整个块,因为它认为结果从未使用过,要么它认为你不使用任何中间结果,只是用编译时计算的静态结果替换了最终结果。无论哪种方式,您都可能只是测量了测量开销本身。
C
Community

结构和类之间的相似之处。

我用简单的例子为此创建了要点。 https://github.com/objc-swift/swift-classes-vs-structures

和差异

1. 继承。

结构不能迅速继承。如果你想

class Vehicle{
}

class Car : Vehicle{
}

去上课。

2.路过

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 代替,这是一个有效的表达式。因为在一个类中,不可变常量是对类本身的引用,而不是它的实例变量(除非那些变量定义为常量)


您可以在 Swift gist.github.com/AliSoftware/9e4946c8b6038572d678 中继承结构
上面的要点是关于我们如何实现结构的继承风格。你会看到类似的语法。 A:B。它是名为 A 的 struct 实现了名为 B 的协议。Apple 文档明确提到 struct 不支持纯继承,它不支持。
伙计,你的最后一段很棒。我一直都知道你可以改变常数……但我不时看到你不能改变的地方,所以我很困惑。这种区别使它可见
m
mfaani

假设我们知道 Struct 是一个值类型,而 Class 是一个引用类型。

如果您不知道什么是值类型和引用类型,请参阅 What's the difference between passing by reference vs. passing by value?

基于 mikeash's post

... 让我们先看一些极端、明显的例子。整数显然是可复制的。它们应该是值类型。网络套接字不能被明智地复制。它们应该是引用类型。 x, y 对中的点是可复制的。它们应该是值类型。代表磁盘的控制器不能被合理地复制。那应该是引用类型。有些类型可以被复制,但它可能不是你想要一直发生的事情。这表明它们应该是引用类型。例如,在概念上可以复制屏幕上的按钮。副本不会与原件完全相同。单击副本不会激活原件。副本不会在屏幕上占据相同的位置。如果您传递按钮或将其放入新变量中,您可能希望引用原始按钮,并且您只想在明确请求时制作副本。这意味着您的按钮类型应该是引用类型。视图和窗口控制器是一个类似的例子。可以想象,它们可能是可复制的,但这几乎不是您想要做的。它们应该是引用类型。模型类型呢?你可能有一个 User 类型代表你系统上的一个用户,或者一个 Crime 类型代表一个用户采取的行动。这些是非常可复制的,因此它们可能应该是值类型。但是,您可能希望在程序的某个位置对用户犯罪的更新对程序的其他部分可见。这表明您的用户应该由某种作为引用类型的用户控制器管理。例如 struct User {} class UserController { var users: [User] func add(user: User) { ... } func remove(userNamed: String) { ... } func ... } 集合是一个有趣的例子。这些包括诸如数组和字典之类的东西,以及字符串。它们是可复制的吗?明显地。复制您想要的东西是否容易且经常发生?这不太清楚。大多数语言对此说“不”,并使其集合引用类型。在 Objective-C、Java、Python 和 JavaScript 以及我能想到的几乎所有其他语言中都是如此。 (一个主要的例外是具有 STL 集合类型的 C++,但 C++ 是语言世界的疯狂疯子,它所做的一切都很奇怪。)斯威夫特说“是的”,这意味着像 Array 和 Dictionary 和 String 这样的类型是结构而不是类。它们在分配时被复制,并在作为参数传递时被复制。只要副本便宜,这是一个完全明智的选择,Swift 非常努力地做到这一点。 ...

我个人不会这样命名我的课程。我通常命名我的 UserManager 而不是 UserController 但想法是一样的

此外,当您必须覆盖函数的每个实例时不要使用类,即它们没有任何共享功能。

因此,与其拥有一个类的多个子类。使用多个符合协议的结构。

结构的另一个合理案例是当您想要对旧模型和新模型进行增量/差异时。使用引用类型,您不能开箱即用。对于值类型,突变不共享。


正是我正在寻找的那种解释。写得真好:)
非常有用的控制器示例
@AskP 我给 mike 自己发了电子邮件并得到了那段额外的代码 :)
多年的简单字符串经验。谢谢。
C
Community

以下是需要考虑的其他一些原因:

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")

要在一个类中得到这个,你必须添加初始化程序,并维护初始化程序......

像 Array 这样的基本集合类型是结构。您在自己的代码中使用它们的次数越多,您就越习惯于通过值而不是引用来传递。例如: func removeLast(var array:[String]) { array.removeLast() println(array) // [one, two] } var someArray = ["one", "two", "three"] removeLast(someArray ) println(someArray) // [一、二、三] 显然,不变性与可变性是一个巨大的话题,但很多聪明人认为不变性——在这种情况下是结构——更可取。可变对象与不可变对象


确实,您会获得自动初始化程序。当所有属性都是可选的时,您还会得到一个空的初始化程序。但是,如果您在框架中有一个结构,如果您希望它在 internal 范围之外可用,则需要自己实际编写初始化程序。
@Abizern 证实了 -- stackoverflow.com/a/26224873/8047 -- 和令人讨厌的人。
@Abizern Swift 中的一切都有很好的理由,但是每当某件事在一个地方而不是另一个地方是正确的,开发人员就必须了解更多的东西。我想这就是我应该说的地方,“以如此具有挑战性的语言工作真是令人兴奋!”
我还可以补充一点,不是结构的不变性使它们有用(尽管这是一件非常好的事情)。您可以改变结构,但您必须将方法标记为 mutating,以便明确哪些函数会更改其状态。但它们作为值类型的性质才是重要的。如果您使用 let 声明一个结构,则不能在其上调用任何变异函数。 通过值类型更好地编程 的 WWDC 15 视频是一个很好的资源。
感谢@Abizern,在阅读您的评论之前,我从来没有真正理解过这一点。对于对象,let 与 var 差别不大,但对于结构来说,差别很大。感谢您指出了这一点。
C
Catfish_Man

一些优点:

由于不可共享而自动线程安全

由于没有 isa 和 refcount 使用更少的内存(实际上通常是堆栈分配的)

方法总是静态分派的,因此可以内联(尽管@final 可以对类执行此操作)

出于与线程安全相同的原因,更容易推理(无需像 NSArray、NSString 等那样典型地“防御性复制”)


不确定它是否超出了此答案的范围,但是您能否解释(或链接,我猜)“方法总是静态调度”这一点?
当然。我也可以附加一个警告。动态分派的目的是在您事先不知道要使用哪个实现时选择一个实现。在 Swift 中,这可能是由于继承(可能在子类中被覆盖),也可能是由于函数是泛型的(您不知道泛型参数是什么)。结构体不能继承,整体模块优化+泛型特化主要消除了未知的泛型,因此可以直接调用方法,而不必查找要调用的内容。不过,非专业泛型仍然对结构进行动态调度
谢谢,很好的解释。所以我们期待更高的运行速度,或者从 IDE 的角度来看更少的歧义,或者两者兼而有之?
主要是前者。
请注意,如果您通过协议引用结构,则不会静态调度方法。
c
casillas

Structsvalue typeClassesreference type

值类型比引用类型快

值类型实例在多线程环境中是安全的,因为多个线程可以改变实例而不必担心竞争条件或死锁

与引用类型不同,值类型没有引用;因此没有内存泄漏。

在以下情况下使用 value 类型:

您希望副本具有独立状态,数据将在多个线程的代码中使用

在以下情况下使用 reference 类型:

您想要创建共享的、可变的状态。

更多信息也可以在 Apple 文档中找到

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

附加信息

Swift 值类型保存在堆栈中。在一个进程中,每个线程都有自己的堆栈空间,因此没有其他线程能够直接访问您的值类型。因此没有竞争条件、锁、死锁或任何相关的线程同步复杂性。

值类型不需要动态内存分配或引用计数,这两者都是昂贵的操作。同时,值类型的方法是静态分派的。这些在性能方面创造了有利于价值类型的巨大优势。

提醒一下,这里是 Swift 的列表

值类型:

结构

枚举

元组

基元(Int、Double、Bool 等)

集合(数组、字符串、字典、集合)

参考类型:

班级

任何来自 NSObject 的东西

功能

关闭


在多线程环境中不安全 如果作为对象传递的实例同时被多个线程访问,则此函数可能仍会返回 true。因此,您只能从具有适当线程同步的变异方法中调用此函数。这将确保 isKnownUniquelyReferenced(_:) 仅在确实有一个访问器或存在竞争条件时才返回 true,这已经是未定义的行为。
C
Climbatize

结构比类快得多。此外,如果您需要继承,那么您必须使用 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  
}

现在让我们创建两者的实例。

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

现在让我们将这些实例传递给修改 id、描述、目的地等的两个函数。

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)

现在如果我们打印航班 A 的 id 和描述,我们得到

id = 200
description = "second flight of Virgin Airlines"

在这里,我们可以看到 FlightA 的 id 和 description 发生了变化,因为传递给 modify 方法的参数实际上指向了 flightA 对象(引用类型)的内存地址。

现在如果我们打印我们得到的 FLightB 实例的 id 和描述,

id = 100
description = "first ever flight of Virgin Airlines"

在这里我们可以看到 FlightB 实例没有改变,因为在 modifyFlight2 方法中,Flight2 的实际实例是传递而不是引用(值类型)。


你从未创建过 FLightB 的实例
那你为什么说FlightB兄弟? Here we can see that the FlightB instance is not changed
@ManojKarki,很好的答案。只是想指出,当我认为您想声明 FlightA 和 FlightB 时,您两次声明了 flightA。
O
Otávio

从值类型与引用类型的角度回答这个问题,从 this Apple blog post 看起来很简单:

在以下情况下使用值类型[例如结构、枚举]:比较实例数据与 == 有意义您希望副本具有独立状态数据将在跨多个线程的代码中使用在以下情况下使用引用类型[例如类]:比较实例身份with === 有意义你想创建共享的、可变的状态

正如那篇文章中提到的,没有可写属性的类与结构的行为相同,但(我将添加)一个警告:结构最适合线程安全模型——现代应用程序架构中越来越迫切的要求。


y
yoAlex5

结构与类

[Stack vs Heap]
[Value vs Reference type]

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)
     
}

*您应该使用/依赖 retainCount,因为它没有说有用的信息

在编译 SIL(Swift Intermediate Language) 可以优化你的代码

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(堆上的分配)


J
Joride

对于类,您可以获得继承并通过引用传递,结构没有继承并通过值传递。

有很多关于 Swift 的 WWDC 会议,其中一个对这个特定问题进行了详细的回答。确保您观看这些内容,因为它可以让您比语言指南或 iBook 更快地上手。


你能提供一些你提到的链接吗?因为在 WWDC 上有很多可供选择,我想看一个谈论这个特定主题的人
对我来说,这是一个好的开始:github.com/raywenderlich/…
他可能正在谈论这个精彩的会议:Swift 中的面向协议的编程。 (链接:videotranscript
y
yeoman

我不会说结构提供的功能更少。

当然,self 是不可变的,除了在一个 mutating 函数中,但仅此而已。

只要您坚持每个类都应该是抽象的或最终的,继承就可以正常工作。

将抽象类实现为协议,将最终类实现为结构。

结构的好处是您可以使字段可变而无需创建共享可变状态,因为写入时复制会处理该问题:)

这就是为什么以下示例中的属性/字段都是可变的,我不会在 Java 或 C# 或 swift 类中这样做。

示例继承结构,在名为“example”的函数的底部有一些肮脏和直接的用法:

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}

B
Balasubramanian

在 Swift 中,引入了一种新的编程模式,称为面向协议的编程。

创作模式:

在 swift 中,Struct 是一种自动克隆的值类型。因此,我们免费获得了实现原型模式所需的行为。

classes 是引用类型,在分配期间不会自动克隆。要实现原型模式,类必须采用 NSCopying 协议。

浅拷贝只复制指向这些对象的引用,而深拷贝复制对象的引用。

为每个引用类型实现深拷贝已成为一项乏味的任务。如果类包含更多引用类型,我们必须为每个引用属性实现原型模式。然后我们必须通过实现 NSCopying 协议来实际复制整个对象图。

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

通过使用结构和枚举,我们使我们的代码更简单,因为我们不必实现复制逻辑。


a
akshay

许多 Cocoa API 需要 NSObject 子类,这迫使您使用类。但除此之外,您可以使用 Apple Swift 博客中的以下案例来决定是使用结构/枚举值类型还是类引用类型。

https://developer.apple.com/swift/blog/?id=10


j
johnbakers

在这些答案中没有引起注意的一点是,包含类与结构的变量可以是 let,同时仍允许更改对象的属性,而您不能对结构执行此操作。

如果您不希望变量指向另一个对象,但仍需要修改该对象,即在您希望一个接一个地更新许多实例变量的情况下,这很有用。如果它是一个结构,则必须允许使用 var 将变量完全重置为另一个对象才能做到这一点,因为 Swift 中的常量值类型正确地允许零突变,而引用类型(类)不表现这边走。


T
Tapash Mollick

由于结构是值类型,您可以非常轻松地创建存储到堆栈中的内存。结构可以很容易地访问,并且在工作范围之后,它很容易通过堆栈顶部的弹出从堆栈内存中释放。另一方面,类是存储在堆中的引用类型,一个类对象中所做的更改将影响另一个对象,因为它们是紧密耦合的引用类型。结构的所有成员都是公共的,而类的所有成员都是私有的.

struct 的缺点是不能被继承。


M
Matheus Cuba

结构和类是用户定义的数据类型

默认情况下,结构是公共的,而类是私有的

类实现封装的原则

类的对象是在堆内存上创建的

类用于可重用性,而结构用于将数据分组到同一结构中

结构体数据成员不能直接初始化,但可以由结构体外部赋值

类数据成员可以直接由无参构造函数初始化,由有参构造函数赋值


有史以来最糟糕的答案!
复制粘贴答案