玩Swift,来自Java背景,为什么要选择Struct而不是Class?看起来它们是一样的东西,只不过Struct提供的功能更少。那为什么选择它呢?


当前回答

结构比类快得多。同样,如果你需要继承,那么你必须使用Class。最重要的一点是类是引用类型,而结构是值类型。例如,

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

so,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

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

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

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

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

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

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

其他回答

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

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

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

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

struct的好处是你可以在不创建共享可变状态的情况下使你的字段可变,因为写时复制会照顾到这一点:)

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

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

创建型模式:

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

而类是引用类型,在赋值过程中不会自动克隆。为了实现原型模式,类必须采用NSCopying协议。


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


为每种引用类型实现深度复制已成为一项乏味的任务。如果类包含进一步的引用类型,我们必须为每个引用属性实现原型模式。然后我们需要通过实现NSCopying协议复制整个对象图。

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

class Address{
   var street:String
   ...
} 

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

这个答案最初是关于结构和类之间性能的差异。不幸的是,关于我使用的测量方法有太多的争议。我把它留在下面,但请不要过多地去理解它。我认为经过这么多年,在Swift社区中,struct(以及enum)由于其简单和安全而一直是首选。

如果性能对你的应用很重要,那就自己衡量。我仍然认为大多数时候结构性能更优越,但最好的答案就像有人在评论中说的那样:这要看情况。

===旧答案===

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

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

考虑下面的例子,它演示了使用结构和类包装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 -全模块优化:

类版本用时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:

类版本花费了2.159942142s struct版本耗时5.83 e -08秒(快37,000,000倍)

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


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

(在struct/class上运行,只有1个字段,而不是10个)

在Swift 1.2、Xcode 6.3.2、iPhone 5S和iOS 8.3上运行Release版本时,平均运行超过5次

类版本花费了9.788332333s Struct版本花费0.010532942秒(快900倍)


旧结果(未知时间)

(在struct/class上运行,只有1个字段,而不是10个)

在我的MacBook Pro上发布:

类版本花费了1.10082秒 struct版本花了0.02324秒(快了50倍)

从值类型和引用类型的角度来回答这个问题,从苹果博客的这篇文章来看,它看起来非常简单:

使用值类型[例如struct, enum]: 用==比较实例数据是有意义的 你希望副本有独立的状态 这些数据将在代码中跨多个线程使用 在以下情况下使用引用类型[例如class]: 比较实例标识和===是有意义的 您希望创建共享的、可变的状态

正如在那篇文章中提到的,没有可写属性的类将与结构体的行为相同,但有一点需要注意:结构体最适合线程安全模型——这是现代应用程序架构中日益迫切的需求。

结构是值类型,类是引用类型

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

在以下情况使用值类型:

你希望副本有独立的状态,数据才会被使用 跨多线程的代码

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

您希望创建共享的、可变的状态。

更多的信息也可以在苹果文档中找到

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


额外的信息

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

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

作为提醒,这里有一个Swift列表

值类型:

结构体 枚举 元组 基本类型(Int, Double, Bool等) 集合(数组,字符串,字典,集合)

引用类型:

类 任何来自NSObject的东西 函数 关闭