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


当前回答

结构vs类

[堆栈vs堆] [值vs参考类型]

结构更可取。但是缺省情况下,Struct并不能解决所有问题。通常你会听说值类型是在堆栈上分配的,但这并不总是正确的。只有局部变量被分配到堆栈上

//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中间语言)可以优化你的代码

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

(优化级别(SWIFT_OPTIMIZATION_LEVEL)]

其他回答

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

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

在以下情况使用值类型:

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

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

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

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

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


额外的信息

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

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

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

值类型:

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

引用类型:

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

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

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

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

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

当然,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)")
}

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

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

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

有很多关于Swift的WWDC会议,其中一个会议详细回答了这个问题。确保你看了这些,因为它会让你更快地跟上语言指南或iBook。