编程iOS 开发者

在 Swift 中,栈分配和堆分配是如何工作的?对象在什么情况下放置在栈上或堆上,这对性能和对象的生命周期有什么影响?

用 Hintsage AI 助手通过面试

答案

在 Swift 中,值类型(如 struct、enum 和 tuple)通常放置在栈上,而引用类型(如 class)则放置在堆上。

栈分配 快速、自动,通常用于存储短生命周期的局部变量。 堆分配 需要更多的开销,通常用于生命周期不确定或大小未知的对象。

示例:

struct Point { var x: Int; var y: Int } let p1 = Point(x: 2, y: 3) // 在栈上 class Node { var value: Int; init(value: Int) { self.value = value } } let n1 = Node(value: 5) // 在堆上

细节:

  • Swift 的编译器/优化器可以在特定情况下将 struct 存储在堆中,例如当它们嵌入在类中或放入集合中时。
  • 在传递时,值类型会被复制,这通常意味着它们会在栈上被复制,但 Swift 对集合实现了写时复制(copy-on-write)。
  • 堆分配需要对象生命周期的管理(ARC)。栈对象在超出作用域后会自动销毁。

性能: 栈分配更快,因为不需要处理堆内存和对象生命周期管理。

刁钻的问题

所有的结构体(struct)是否总是只能放置在栈上?

答案:
不是!虽然结构体是值类型,且通常放在栈上,但编译器可以将它们存储在堆中,如果它们位于类对象、数组、字典中,或者用作闭包中的捕获值。例如:

class Box { var point: Point init(point: Point) { self.point = point } } let box = Box(point: Point(x: 1, y: 2))

在这里,Point 的实例将存储在堆中,因为它属于类 Box

由于对主题细节不了解而导致的实际错误示例


故事

一个开发人员试图优化结构体的性能,认为大结构体总是会放在栈上,并没有考虑到集合(数组,字典)使用堆分配,这导致项目中内存消耗意外增加。


故事

项目中未考虑到闭包捕获结构体的值,该值被存储在堆中,影响了对象的生命周期。这增加了变量的生命周期并导致了内存泄漏,因为开发者期望它们在超出作用域时自动释放。


故事

在不理解写时复制的情况下使用重结构数组,导致由于在线程之间传递集合而产生意外昂贵的复制操作,降低了性能并导致 UI 延迟。