【问题标题】:Why Choose Struct Over Class?为什么选择结构而不是类?
【发布时间】:2014-08-05 15:14:43
【问题描述】:

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

【问题讨论】:

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

标签: swift class struct design-principles


【解决方案1】:

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

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

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

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

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

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

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

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

结构实例总是按值传递,而类 实例总是通过引用传递。这意味着他们是 适合不同类型的任务。当你考虑数据 项目所需的结构和功能,决定 每个数据结构应该定义为一个类还是一个 结构。

作为一般准则,考虑创建一个结构,当一个或多个 以下条件适用:

  • 该结构的主要目的是封装一些相对简单的数据值。
  • 当您分配或传递一个 该结构的实例。
  • 结构存储的任何属性本身都是值类型,也应该被复制而不是引用。
  • 结构不需要从其他现有类型继承属性或行为。

结构的良好候选示例包括:

  • 几何形状的大小,可能封装了一个宽度属性和一个高度属性,两者都是 Double 类型。
  • 一种在系列中引用范围的方法,可能封装了一个起始属性和一个长度属性,两者都是 Int 类型。
  • 3D 坐标系中的一个点,可能封装了 x、y 和 z 属性,每个属性都是 Double 类型。

在所有其他情况下,定义一个类并创建该类的实例 通过引用进行管理和传递。在实践中,这意味着 大多数自定义数据构造应该是类,而不是结构。

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

【讨论】:

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

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

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

考虑以下示例,该示例演示了使用structclass 包装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 -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 build,平均运行 5 次以上,Swift 编译器设置为 -O -whole-module-optimization

  • class 版本耗时 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 次

  • class 版本耗时 9.788332333s
  • struct 版本耗时 0.010532942 秒(快 900 倍)

旧结果(来自未知时间)

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

在我的 MacBook Pro 上发布版本:

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

【讨论】:

  • 没错,但是复制一堆结构似乎比复制对单个对象的引用要慢。换句话说,复制单个指针比复制任意大的内存块要快。
  • -1 这个测试不是一个很好的例子,因为结构上只有一个 var。请注意,如果您添加多个值和一个或两个对象,则结构版本与类版本相当。添加的变量越多,结构版本的速度就越慢。
  • @joshrl 明白了你的意思,但一个例子是否“好”取决于具体情况。这段代码是从我自己的应用程序中提取的,因此它是一个有效的用例,并且使用结构确实极大地提高了我的应用程序的性能。这可能不是一个常见的用例(嗯,常见的用例是,对于大多数应用程序来说,没有人关心数据传递的速度有多快,因为瓶颈发生在其他地方,例如网络连接,无论如何,优化不是当您拥有具有 GB 或 RAM 的 GHz 设备时至关重要)。
  • 据我了解,在 Swift 中复制已优化为在 WRITE 时发生。这意味着除非要更改新副本,否则不会创建物理内存副本。
  • 这个答案展示了一个极其微不足道的例子,以至于不切实际,因此在许多情况下都是不正确的。更好的答案是“视情况而定”。
【解决方案3】:

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

我用简单的例子为此创建了要点。 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 不支持纯继承,它不支持。
  • 你的最后一段很棒。我一直都知道你可以改变常数……但我不时看到你不能改变的地方,所以我很困惑。这种区别使它可见
【解决方案4】:

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

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

基于mikeash's post

... 让我们先看一些极端的、明显的例子。整数是 显然是可复制的。它们应该是值类型。网络套接字不能 被明智地复制。它们应该是引用类型。点,如 x, y 对,是可复制的。它们应该是值类型。一个控制器 表示磁盘无法合理复制。那应该是参考 输入。

某些类型可以复制,但可能不是您想要的 一直在发生。这表明它们应该作为参考 类型。例如,在概念上可以复制屏幕上的按钮。 副本不会与原件完全相同。点击 复制不会激活原件。副本不会占用相同的 屏幕上的位置。如果您传递按钮或将其放入 您可能希望引用原始按钮的新变量,以及 您只想在明确要求时制作副本。那 表示您的按钮类型应该是引用类型。

视图和窗口控制器是一个类似的例子。他们可能是 可以复制,可以想象,但它几乎永远不是你想要做的。 它们应该是引用类型。

模型类型呢?您可能有一个代表用户的用户类型 在您的系统上,或犯罪类型代表一个人采取的行动 用户。这些是非常可复制的,所以它们应该是有价值的 类型。但是,您可能希望更新用户犯罪 程序中的一个位置对程序的其他部分可见。 这表明您的用户应该由某种用户管理 控制器,这将是一个引用类型。例如

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 这样的类型 字符串是结构而不是类。他们在作业中被抄袭, 并将它们作为参数传递。这是一个完全明智的选择 只要副本便宜,斯威夫特非常努力 完成。 ...

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

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

所以不是一个类的多个子类。使用多个符合协议的结构。


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

【讨论】:

  • 正是我正在寻找的那种解释。写得真好:)
  • 非常有用的控制器示例
  • @AskP 我给 mike 自己发了电子邮件,得到了那段额外的代码 :)
【解决方案5】:

还有其他一些需要考虑的原因:

  1. 结构有一个自动初始化器,您根本不必在代码中维护它。

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

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

  1. Array 等基本集合类型是结构。您在自己的代码中使用它们的次数越多,您就越习惯于通过值而不是引用来传递。例如:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
    
  2. 显然,不变性与可变性是一个巨大的话题,但很多聪明人认为不变性——在这种情况下是结构——更可取。 Mutable vs immutable objects

【讨论】:

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

一些优点:

  • 由于不可共享而自动线程安全
  • 由于没有 isa 和 refcount 使用更少的内存(实际上通常是堆栈分配的)
  • 方法总是静态分派的,因此可以内联(尽管@final 可以对类执行此操作)
  • 出于与线程安全相同的原因,更容易推理(无需像 NSArray、NSString 等那样典型地“防御性复制”)

【讨论】:

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

Structsvalue typeClassesreference type

  • 值类型比引用类型快
  • 值类型实例在多线程环境中是安全的,因为 多个线程可以改变实例而不必担心 关于竞争条件或死锁
  • 与引用类型不同,值类型没有引用;因此那里 没有内存泄漏。

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

  • 您希望副本具有独立状态,数据将用于 跨多个线程的代码

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

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

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

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


其他信息

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

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

提醒一下,这里有一个 Swift 列表

值类型:

  • 结构
  • 枚举
  • 元组
  • 基元(Int、Double、Bool 等)
  • 集合(数组、字符串、字典、集合)

参考类型:

  • 来自 NSObject 的任何内容
  • 功能
  • 关闭

【讨论】:

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

结构比类快得多。此外,如果您需要继承,则必须使用 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。
【解决方案9】:

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

使用值类型 [e.g. struct, enum] 当:

  • 用 == 比较实例数据是有意义的
  • 您希望副本具有独立状态
  • 数据将用于跨多个线程的代码

使用引用类型 [例如类]何时:

  • 将实例标识与 === 进行比较是有意义的
  • 您想要创建共享的可变状态

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

【讨论】:

    【解决方案10】:

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

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

    【讨论】:

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

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

    当然,self 是不可变的,除非在变异函数中,但仅此而已。

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

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

    关于结构的好处是你可以让你的字段可变而不创建共享可变状态,因为写入时复制会处理这个问题:)

    这就是为什么下面示例中的属性/字段都是可变的,我不会在 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)")
    }
    

    【讨论】:

      【解决方案12】:

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

      创作模式:

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

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


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


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

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

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

      【讨论】:

        【解决方案13】:

        结构与类

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

        【讨论】:

          【解决方案14】:

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

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

          【讨论】:

            【解决方案15】:

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

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

            【讨论】:

              【解决方案16】:

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

              struct的缺点是不能被继承。

              【讨论】:

                【解决方案17】:
                • 结构和类是用户定义的数据类型

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

                • 类实现封装的原理

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

                • 类用于重用,而结构用于分组 相同结构的数据

                • 结构数据成员不能直接初始化,但可以 由结构外分配

                • 类数据成员可以通过参数less直接初始化 构造函数并由参数化构造函数赋值

                【讨论】:

                • 有史以来最糟糕的答案!
                • 复制粘贴答案
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-10-24
                • 1970-01-01
                • 1970-01-01
                • 2011-12-06
                相关资源
                最近更新 更多