为什么选择“结构优先”?

本文翻译自:Why Choose Struct Over Class?

Playing around with Swift, coming from a Java background, why would you want to choose a Struct instead of a Class? 在Java背景下玩Swift,为什么要选择Struct而不是Class? Seems like they are the same thing, with a Struct offering less functionality. 似乎它们是同一回事,但Struct提供的功能较少。 Why choose it then? 为什么选择它呢?


#1楼

参考:https://stackoom.com/question/1dg3b/为什么选择-结构优先


#2楼

With classes you get inheritance and are passed by reference, structs do not have inheritance and are passed by value. 对于类,您可以继承并通过引用传递,而结构不具有继承,而是通过值传递。

There are great WWDC sessions on Swift, this specific question is answered in close detail in one of them. 在Swift上有很棒的WWDC会议,其中一个具体问题得到了详细解答。 Make sure you watch those, as it will get you up to speed much more quickly then the Language guide or the iBook. 确保您观看这些内容,因为它可以使您比语言指南或iBook更快地掌握速度。


#3楼

According to the very popular WWDC 2015 talk Protocol Oriented Programming in Swift ( video , transcript ), Swift provides a number of features that make structs better than classes in many circumstances. 根据非常流行的WWDC 2015演讲《 Swift中的面向协议的编程》( 视频成绩单 ),Swift提供了许多功能,这些功能在许多情况下都比类更好。

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple references to the same instance as happens with classes. 如果结构相对较小且可复制,则结构是可取的,因为与在类中多次引用同一实例相比,复制要安全得多。 This is especially important when passing around a variable to many classes and/or in a multithreaded environment. 当将变量传递给许多类和/或在多线程环境中时,这一点尤其重要。 If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable underneath you. 如果您始终可以将变量的副本发送到其他位置,则不必担心其他位置会更改您下面的变量的值。

With Structs, there is much less need to worry about memory leaks or multiple threads racing to access/modify a single instance of a variable. 使用Structs,无需担心内存泄漏或多个线程争夺访问/修改变量的单个实例的麻烦。 (For the more technically minded, the exception to that is when capturing a struct inside a closure because then it is actually capturing a reference to the instance unless you explicitly mark it to be copied). (从更专业的角度来说,例外是在闭包内部捕获结构时,因为除非您明确标记要复制的实例,否则它实际上是在捕获对实例的引用)。

Classes can also become bloated because a class can only inherit from a single superclass. 类也可能变得肿,因为一个类只能从单个超类继承。 That encourages us to create huge superclasses that encompass many different abilities that are only loosely related. 这鼓励我们创建巨大的超类,其中包含仅松散相关的许多不同能力。 Using protocols, especially with protocol extensions where you can provide implementations to protocols, allows you to eliminate the need for classes to achieve this sort of behavior. 使用协议,尤其是协议扩展,可以在其中提供协议的实现,可以消除类对实现此类行为的需要。

The talk lays out these scenarios where classes are preferred: 演讲列出了首选班级的这些情况:

  • Copying or comparing instances doesn't make sense (eg, Window) 复制或比较实例没有意义(例如Window)
  • Instance lifetime is tied to external effects (eg, TemporaryFile) 实例的生存期与外部影响(例如TemporaryFile)有关
  • Instances are just "sinks"--write-only conduits to external state (egCGContext) 实例只是“接收器”-到外部状态(例如CGContext)的只写管道

It implies that structs should be the default and classes should be a fallback. 这意味着结构应该是默认值,而类应该是后备。

On the other hand, The Swift Programming Language documentation is somewhat contradictory: 另一方面, Swift编程语言文档有些矛盾:

Structure instances are always passed by value, and class instances are always passed by reference. 结构实例始终按值传递,而类实例始终按引用传递。 This means that they are suited to different kinds of tasks. 这意味着它们适合于各种任务。 As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure. 在考虑项目所需的数据结构和功能时,请确定是将每个数据结构定义为类还是结构。

As a general guideline, consider creating a structure when one or more of these conditions apply: 作为一般准则,请考虑在以下一个或多个条件适用时创建结构:

  • The structure's primary purpose is to encapsulate a few relatively simple data values. 该结构的主要目的是封装一些相对简单的数据值。
  • It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure. 合理的是,当您分配或传递该结构的实例时,将封装的值复制而不是引用。
  • Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced. 结构存储的任何属性本身都是值类型,也应该期望将其复制而不是引用。
  • The structure does not need to inherit properties or behavior from another existing type. 该结构不需要从另一个现有类型继承属性或行为。

Examples of good candidates for structures include: 良好的结构候选人包括:

  • The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double. 尺寸为Double的几何形状的大小,可能封装了width属性和height属性。
  • A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int. 引用系列中范围的一种方法,可能封装了Int类型的start属性和length属性。
  • A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double. 3D坐标系中的一个点,可能封装了x,y和z属性,每个属性都是Double类型。

In all other cases, define a class, and create instances of that class to be managed and passed by reference. 在所有其他情况下,定义一个类,并创建该类的实例以通过引用进行管理和传递。 In practice, this means that most custom data constructs should be classes, not structures. 实际上,这意味着大多数自定义数据构造应该是类,而不是结构。

Here it is claiming that we should default to using classes and use structures only in specific circumstances. 在这里,我们声称我们应该默认仅在特定情况下使用类和结构。 Ultimately, you need to understand the real world implication of value types vs. reference types and then you can make an informed decision about when to use structs or classes. 最终,您需要了解值类型与引用类型的真实含义,然后可以就何时使用结构或类做出明智的决定。 Also, keep in mind that these concepts are always evolving and The Swift Programming Language documentation was written before the Protocol Oriented Programming talk was given. 另外,请记住,这些概念一直在发展,在进行面向协议的编程演讲之前,已经编写了Swift编程语言文档。


#4楼

Some advantages: 一些优点:

  • automatically threadsafe due to not being shareable 由于不可共享而自动线程安全
  • uses less memory due to no isa and refcount (and in fact is stack allocated generally) 由于没有isa和refcount,因此使用较少的内存(实际上通常是堆栈分配的)
  • methods are always statically dispatched, so can be inlined (though @final can do this for classes) 方法总是静态分派的,因此可以内联(尽管@final可以对类执行此操作)
  • easier to reason about (no need to "defensively copy" as is typical with NSArray, NSString, etc...) for the same reason as thread safety 出于与线程安全相同的原因,更易于推理(无需“防御性复制”,如NSArray,NSString等常见)。

#5楼

Since struct instances are allocated on stack, and class instances are allocated on heap, structs can sometimes be drastically faster. 由于struct实例是在堆栈上分配的,而class实例是在堆上分配的,因此结构有时有时会更快。

However, you should always measure it yourself and decide based on your unique use case. 但是,您应该始终自己进行测量,并根据您的独特用例进行决策。

Consider the following example, which demonstrates 2 strategies of wrapping Int data type using struct and class . 考虑以下示例,该示例演示了两种使用structclass包装Int数据类型的策略。 I am using 10 repeated values are to better reflect real world, where you have multiple fields. 我使用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)
}

Performance is measured using 性能使用

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

Code can be found at https://github.com/knguyen2708/StructVsClassPerformance 可以在https://github.com/knguyen2708/StructVsClassPerformance中找到代码

UPDATE (27 Mar 2018) : 更新(2018年3月27日)

As of Swift 4.0, Xcode 9.2, running Release build on iPhone 6S, iOS 11.2.6, Swift Compiler setting is -O -whole-module-optimization : 从Swift 4.0(Xcode 9.2)开始,在iPhone 6S,iOS 11.2.6上运行Release build,Swift Compiler设置为-O -whole-module-optimization

  • class version took 2.06 seconds class时间为2.06秒
  • struct version took 4.17e-08 seconds (50,000,000 times faster) struct版本花了4.17e-08秒(快了50,000,000倍)

(I no longer average multiple runs, as variances are very small, under 5%) (我不再对多次运行进行平均,因为方差很小,不到5%)

Note : the difference is a lot less dramatic without whole module optimization. 注意 :如果不对整个模块进行优化,则差异不大。 I'd be glad if someone can point out what the flag actually does. 如果有人能指出该旗标的实际作用,我将感到非常高兴。


UPDATE (7 May 2016) : 更新(2016年5月7日)

As of Swift 2.2.1, Xcode 7.3, running Release build on iPhone 6S, iOS 9.3.1, averaged over 5 runs, Swift Compiler setting is -O -whole-module-optimization : 从Swift 2.2.1,Xcode 7.3开始,在iPhone 6S,iOS 9.3.1上运行Release版本,平均运行5次以上,Swift编译器设置为-O -whole-module-optimization

  • class version took 2.159942142s class版本为2.159942142s
  • struct version took 5.83E-08s (37,000,000 times faster) struct版本花了5.83E-08s(快37,000,000倍)

Note : as someone mentioned that in real-world scenarios, there will be likely more than 1 field in a struct, I have added tests for structs/classes with 10 fields instead of 1. Surprisingly, results don't vary much. 注意 :正如有人提到的那样,在实际情况下,结构中可能会有1个以上的字段,我为具有10个字段而不是1个字段的结构/类添加了测试。令人惊讶的是,结果变化不大。


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

(Ran on struct/class with 1 field, not 10) (对结构/类使用1个字段,而不是10个)

As of Swift 1.2, Xcode 6.3.2, running Release build on iPhone 5S, iOS 8.3, averaged over 5 runs 从Swift 1.2,Xcode 6.3.2开始,在iPhone 5S,iOS 8.3上运行Release版本,平均运行5次以上

  • class version took 9.788332333s class版本花了9.788332333s
  • struct version took 0.010532942s (900 times faster) struct版本花费了0.010532942s(快了900倍)

OLD RESULTS (from unknown time) 旧结果 (来自未知时间)

(Ran on struct/class with 1 field, not 10) (对结构/类使用1个字段,而不是10个)

With release build on my MacBook Pro: 在我的MacBook Pro上发布版本:

  • The class version took 1.10082 sec class时间为1.10082秒
  • The struct version took 0.02324 sec (50 times faster) struct版本花费0.02324秒(快50倍)

#6楼

Here are some other reasons to consider: 还有其他一些要考虑的原因:

  1. structs get an automatic initializer that you don't have to maintain in code at all. 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") 

To get this in a class, you would have to add the initializer, and maintain the intializer... 为了在一个类中做到这一点,您必须添加初始化程序,并维护初始化程序。

  1. Basic collection types like Array are structs. Array这样的基本集合类型是结构。 The more you use them in your own code, the more you will get used to passing by value as opposed to reference. 您在自己的代码中使用它们的次数越多,您就会习惯于按值传递而不是引用。 For instance: 例如:

     func removeLast(var array:[String]) { array.removeLast() println(array) // [one, two] } var someArray = ["one", "two", "three"] removeLast(someArray) println(someArray) // [one, two, three] 
  2. Apparently immutability vs. mutability is a huge topic, but a lot of smart folks think immutability -- structs in this case -- is preferable. 显然,不变性与可变性是一个巨大的话题,但是许多聪明人认为,不变性(在这种情况下为结构)是更可取的。 Mutable vs immutable objects 可变对象与不可变对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值