面试题答案
一键面试Swift内存管理机制
Swift 使用自动引用计数(ARC)来管理内存。ARC 会在运行时自动跟踪和管理类实例的引用。当一个类实例的引用计数降为 0 时,ARC 会自动释放该实例占用的内存。
- 强引用:默认情况下,Swift 中的属性和变量都是强引用。如果一个实例 A 持有对另一个实例 B 的强引用,只要 A 存在,B 就不会被释放。例如:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var person1: Person?
person1 = Person(name: "John")
// 此时 Person 实例被 person1 强引用,不会被释放
person1 = nil
// 此时 person1 对 Person 实例的强引用被移除,引用计数降为 0,实例被释放
- 弱引用:使用
weak
关键字声明。弱引用不会增加实例的引用计数,并且当被引用的实例被释放时,弱引用会自动设置为nil
。常用于解决循环引用问题,例如在视图控制器和其视图之间:
class ViewController {
var view: MyView?
deinit {
print("ViewController is being deinitialized")
}
}
class MyView: UIView {
weak var viewController: ViewController?
deinit {
print("MyView is being deinitialized")
}
}
- 无主引用:使用
unowned
关键字声明。无主引用也不会增加实例的引用计数,但与弱引用不同,无主引用在被引用的实例被释放后不会自动设置为nil
,因此使用时需确保被引用的实例始终存在。常用于一个实例的生命周期总是长于另一个实例的情况,例如在父 - 子关系中:
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
调试内存管理不当导致的性能问题
- 使用 Instruments:
- 步骤:打开 Instruments,选择 “Leaks” 模板。运行应用程序,Instruments 会检测内存泄漏并指出泄漏发生的位置。例如,如果在一个视图控制器中创建了大量临时对象但没有及时释放,可以通过 Leaks 工具定位到具体代码行。
- 代码示例:假设在一个视图控制器的
viewDidLoad
方法中有如下代码:
class MemoryLeakViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0..<1000 {
let largeData = Data(count: 1024 * 1024) // 创建大量数据
// 这里没有对 largeData 进行释放操作,可能导致内存泄漏
}
}
}
运行 Instruments 的 Leaks 工具后,可以定位到创建 largeData
的代码行。
2. 启用僵尸对象:
- 步骤:在 Xcode 中,选择 “Edit Scheme”,在 “Run” -> “Diagnostics” 中勾选 “Enable Zombie Objects”。启用后,当对象被释放但仍被访问时,系统会创建一个僵尸对象,并抛出异常,提示访问已释放的对象,从而帮助定位问题代码。
- 代码示例:假设有如下代码:
class MyClass {
func doSomething() {
print("Doing something")
}
}
var myObject: MyClass? = MyClass()
myObject?.doSomething()
myObject = nil
myObject?.doSomething() // 访问已释放的对象
启用僵尸对象后,运行应用程序,会抛出异常提示对已释放对象的访问。
常见性能问题场景及优化策略
- 循环引用:
- 场景:两个实例相互持有强引用,导致它们的引用计数永远不会降为 0,从而造成内存泄漏。例如:
class A {
var b: B?
deinit {
print("A is deinitialized")
}
}
class B {
var a: A?
deinit {
print("B is deinitialized")
}
}
var aInstance: A? = A()
var bInstance: B? = B()
aInstance?.b = bInstance
bInstance?.a = aInstance
aInstance = nil
bInstance = nil
// A 和 B 的实例不会被释放,因为它们相互持有强引用
- **优化策略**:使用弱引用或无主引用来打破循环引用。例如,将 `B` 中的 `a` 属性改为弱引用:
class A {
var b: B?
deinit {
print("A is deinitialized")
}
}
class B {
weak var a: A?
deinit {
print("B is deinitialized")
}
}
var aInstance: A? = A()
var bInstance: B? = B()
aInstance?.b = bInstance
bInstance?.a = aInstance
aInstance = nil
bInstance = nil
// A 和 B 的实例会被正常释放
- 大量临时对象创建:
- 场景:在循环或频繁调用的方法中创建大量临时对象,导致内存占用过高,性能下降。例如:
func processData() {
for _ in 0..<10000 {
let tempData = Data(count: 1024) // 创建大量临时数据
// 对 tempData 进行处理
}
}
- **优化策略**:尽量复用对象,避免频繁创建和销毁。例如,可以使用对象池来管理对象:
class DataPool {
private var pool: [Data] = []
func getData() -> Data {
if let data = pool.popLast() {
return data
}
return Data(count: 1024)
}
func returnData(_ data: Data) {
pool.append(data)
}
}
let dataPool = DataPool()
func processData() {
for _ in 0..<10000 {
let tempData = dataPool.getData()
// 对 tempData 进行处理
dataPool.returnData(tempData)
}
}
这样可以减少对象的创建和销毁次数,提高性能。