MST

星途 面试题库

面试题:Swift 数据类型与内存管理的深度关联

Swift 中的数据类型,例如类、结构体、枚举等,它们在内存管理方面有哪些特性和差异?请深入分析引用计数、ARC(自动引用计数)在这些数据类型上的具体工作原理,以及可能会出现的内存管理问题,如循环引用等。同时,针对可能出现的内存管理问题,提供至少两种不同的解决方案,并结合代码示例详细说明。
17.8万 热度难度
编程语言Swift

知识考点

AI 面试

面试题答案

一键面试

1. Swift 数据类型内存管理特性及差异

  • 类(Class)
    • 特性:类是引用类型。多个常量和变量可以引用同一个类实例。
    • 内存管理:其内存分配在堆上,生命周期由 ARC 管理。ARC 会跟踪和管理类实例的引用计数。
  • 结构体(Struct)
    • 特性:结构体是值类型。当传递或赋值时,会进行值拷贝。
    • 内存管理:其内存分配在栈上(对于简单结构体,复杂结构体可能部分内容在堆上),不需要 ARC 管理,当它们离开作用域时自动释放。
  • 枚举(Enum)
    • 特性:枚举也是值类型。与结构体类似,传递或赋值时会进行值拷贝。
    • 内存管理:内存分配通常在栈上,生命周期与所在作用域相关,无需 ARC 管理。

2. 引用计数与 ARC 工作原理

  • ARC 原理:ARC 是 Swift 中的自动内存管理机制,它通过跟踪对象的引用计数来管理内存。每当一个类实例被赋值给一个常量、变量或作为函数参数传递时,该实例的引用计数就会增加;当引用该实例的常量、变量超出其作用域时,引用计数就会减少。当引用计数变为 0 时,ARC 会自动释放该实例所占用的内存。

3. 内存管理问题 - 循环引用

  • 循环引用场景:当两个类实例相互持有对方的强引用时,会导致循环引用。例如:
class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let number: Int
    var tenant: Person?
    init(number: Int) {
        self.number = number
    }
    deinit {
        print("Apartment \(number) is being deinitialized")
    }
}

var john: Person? = Person(name: "John")
var number73: Apartment? = Apartment(number: 73)
john?.apartment = number73
number73?.tenant = john
john = nil
number73 = nil 
// 此时 John 和 Apartment 73 都不会被 deinitialized,因为存在循环引用

4. 解决方案

  • 使用弱引用(Weak Reference)
    • 原理:弱引用不会增加对象的引用计数。适用于关系中一个实例可以在另一个实例之前释放的情况。
    • 代码示例
class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let number: Int
    weak var tenant: Person?
    init(number: Int) {
        self.number = number
    }
    deinit {
        print("Apartment \(number) is being deinitialized")
    }
}

var john: Person? = Person(name: "John")
var number73: Apartment? = Apartment(number: 73)
john?.apartment = number73
number73?.tenant = john
john = nil
number73 = nil 
// 此时 John 和 Apartment 73 都会被 deinitialized,因为使用了弱引用打破了循环引用
  • 使用无主引用(Unowned Reference)
    • 原理:无主引用与弱引用类似,不会增加引用计数,但它假定被引用的对象永远不会变为 nil。适用于两个实例彼此间的生命周期紧密相连,并且在初始化完成后,都不会变为 nil 的情况。
    • 代码示例
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

class CreditCard {
    let number: Int
    unowned let customer: Customer
    init(number: Int, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit {
        print("Credit card \(number) is being deinitialized")
    }
}

var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil 
// 此时 John 和 Credit card 1234_5678_9012_3456 都会被 deinitialized,因为使用了无主引用打破了循环引用