妖狐 发表于 2023-10-17 16:29:18

Swift 为什么推荐使用结构体而不是类?原因是什么?或者说结构体相比类优势在哪里?

Swift开发语言,为什么推荐使用结构体而不是类?原因是什么?或者说结构体相比类优势在哪里?

abcadr 发表于 2023-10-17 16:29:23

实在无法理解为什么某些Java程序员持续这么多年一直在知乎上强答Swift问题……还错误百出……
Swift推荐使用结构体的原因很简单,结构体是值语义,类是引用语义。值可以随便拷贝,拷贝之间相互独立,修改其中一个不影响另外一个,类则没有这性质。下面是ChatGPT生成的示例代码:
struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 5, y: 10)
var point2 = point1 // 创建point1的副本

point2.x = 3 // 修改point2的x值

print(point1.x) // 输出:5,point1的x值不受影响
print(point2.x) // 输出:3,point2的x值已被修改

class Person {
    var name: String
   
    init(name: String) {
      self.name = name
    }
}

var person1 = Person(name: "Alice")
var person2 = person1 // 创建person1的引用

person2.name = "Bob" // 修改person2的name属性

print(person1.name) // 输出:Bob,person1的name值也被修改
print(person2.name) // 输出:Bob,person2的name值剩下一些值得注意的点:

[*]Swift的对象都在堆上,没有C++的placement new那回事。
[*]Swift的对象通过引用计数实现,你可以通过isKnownUniquelyReferenced来检查这个对象是不是只有一个引用。
[*]配合isKnownUniquelyReferenced,你可以把对象通过结构体包起来,并在此基础上实现写时复制语义(copy-on-write)。
[*]这个东西有两个常见的用处:

[*]实现容器。Swift标准库里所有的容器都是结构体包起来的,满足值语义。
[*]将非常大的结构体塞到堆上,降低栈的使用。

[*]Swift早年需要实现type eraser,比如说AnyCollection,内部就是一个base class,然后derived class是个generics。这个没办法。现在有了exisentials,你可以直接写any Collection<Int>,不需要了。
举例一下type eraser + isKnownUniquelyReferenced:
private class AnyCollectionBase {
    var count: Int {
      fatalError()
    }

    func copy() -> AnyCollectionBase {
      fatalError()
    }
}

private final class AnyCollectionConcrete<T: Collection> {
    var storage: T

    override var count: Int {
      storage.count
    }

    override func copy() -> AnyCollectionBase {
      AnyCollectionCrete(storage: storage)
    }
}
struct AnyCollection<X> {
    private var box: AnyCollectionBase

    init<T: Collection>(_ t: T) where T.Element == X {
      box = AnyCollectionCrete(storage: t)
    }

    var count: Int {
      box.count
    }

    private mutating func copyIfNeeded() {
      if !isKnownUniquelyReferenced(&box) {
             box = box.copy()
      }
    }

    mutating func append(_ x: X) {
      copyIfNeeded()
      box.append(x)
    }
}还有一个性能细节:结构体应该要比对象快。一方面,因为结构体都在栈上,缓存友好。另一方面,由于Swift目前糟糕的编译器优化,你只要用了对象,就会在每个scope里多出一对retain/release,这个引用计数是原子的,即使在ARM平台也很慢,X86还要慢一个数量级。这个问题最终会被解决,但估计还要等几年——ownership应该要在Swift 6之前实现。
然后就是Swift的并发支持。Swift不只是简单的加入了async/await这个语法糖,它的类型系统可以对可能会造成data race的代码报错。简单来说,要想避免 data race,就两个方法:

[*]让数据immutable
[*]通过各种方法确保同时只有一段代码访问(serialization):

[*]锁
[*]队列(比如说Swift的actor)

如果你的类型非常好的遵守了值语义,那么处理并发代码非常简单。你只需要确保你的并发环境的代码capture的所有的变量都是immutable的就行了。但如果有class就会很tricky,你需要手工声明它是Sendable的。这一部分检查目前比较松,等Swift 6要强制打开,又要红一大片。
所以Swift的推荐就是,除非你明确需要一个identity,需要共享数据,需要有生命周期,否则一律考虑struct。不然的话,优先考虑actor。最后的最后,你在做非常低级的优化,或者在实现库内部,搞一些类型黑魔法,才考虑用class。

十四 发表于 2023-10-17 16:29:43

这个官方不是有答案么?

http://picx.zhimg.com/v2-a64ea67abc72bc530ec84a6afa880bd6_r.jpg?source=1940ef5c

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

The additional capabilities that classes support come at the cost of increased complexity. As a general guideline, prefer structures because they’re easier to reason about, and use classes when they’re appropriate or necessary. 粗体部分翻译一下:类的附加功能是以增加复杂性为代价,这就是交易
就是类比起结构来说,特性更多
但是,你可能用不到这么多东西,对于一些场景,普通的结构体就够你用了
我想这有一个重要的原因,就是swift没有gc
所以一些重量级的东西,它就不敢轻易搞,无形中就增加了编程时候的心智负担
java的zgc出来之后,其实new对象这个操作基本上就彻底放开了
随便new,别给我省内存,省了我也不会感谢你
但是前提是,要有zgc,没有zgc的时代,那你就不能这样随便new
因为new太多会造成cms时候的full gc次数增加,从而导致gc暂停时间变长
有了zgc,反正一次也就2ms不到,多数时候在1ms以内解决,那只要内存够,随便new
看swift就不一样了,因为swift没有gc,只有arc,而且需要手动标记weak reference之类的
一些重量级的结构,比如类,就最好不要轻易用了,因为swift没有zgc这种强力的保证
所以你还是自己手动处理一下吧
写swift比写java要复杂一点
不过话说回来,java以后也会增加类似结构的value class,我看java已经完成了初步的版本,估计过几年就可以下发生产了,可能22开始preview吧
因为value class对比普通class,性能更好,而且需要用户标记的地方,比较少
几乎是无痛的,就在一般的class前面加上个value就好了
所以这种交易,看上去还挺划算的,而且这个操作还可以由ide代劳,你现在用idea的话
应该有一种感觉,它会经常提示你用final,一个意思,以后能标记成value或者primitive的地方,idea也会主动提示你标记成value或者primitive的
所以,为什么不?
总结一下:普通的class,对比swift里面的结构以及java的value/primitive class而言,功能更多,但是相比之下,也更复杂,如果没有zgc这种强力的保证的话,你还是手动标记成struct或者value/primitive class比较好,给机器造成的负担会轻一点
但是如果你想粗暴简单写代码的话,在有zgc的前提下,全部用class,也是可以的,看你自己的需求,swift没有gc,更没有zgc,所以心智负担就交到了开发者这边
类似的,dart就有gc,所以dart里面全都是class,flutter里面甚至很多组件是stateful widget,这是非常重量级的组件,stateful和stateless的区别让我想起了ejb
所以有gc,gc强劲的话,很多重量级的东西,就可以随便搞了,复杂就复杂,反正折磨的是gc,不是人,但是如果没有,那人就相应要注意了,写的时候,负担就要高一点
页: [1]
查看完整版本: Swift 为什么推荐使用结构体而不是类?原因是什么?或者说结构体相比类优势在哪里?