面试题答案
一键面试Swift自动引用计数(ARC)机制工作原理
- 引用计数基本概念:ARC 是Swift 中的自动内存管理机制,它通过跟踪和管理对象的引用计数来自动释放不再被使用的对象。当一个对象被创建时,它的引用计数被设置为1。每次有新的强引用指向该对象时,其引用计数加1;当一个指向对象的强引用被释放时,引用计数减1。当对象的引用计数变为0时,ARC 会自动释放该对象所占用的内存。
- 引用类型:
- 强引用(Strong Reference):默认情况下,Swift 中的对象引用是强引用。只要有一个强引用指向对象,对象就会保持存活。例如:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
// 此时Person对象的引用计数为1,因为john强引用了它
john = nil
// 引用计数减为0,对象被释放,会打印 "John is being deinitialized"
- **弱引用(Weak Reference)**:弱引用不会增加对象的引用计数,并且当被引用的对象被释放时,弱引用会自动被设置为 `nil`。通常用于解决循环引用问题,例如:
class Apartment {
let number: Int
init(number: Int) {
self.number = number
}
weak var tenant: Person?
deinit {
print("Apartment #\(number) is being deinitialized")
}
}
class Person {
let name: String
init(name: String) {
self.name = name
}
var apartment: Apartment?
deinit {
print("\(name) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
var number73: Apartment? = Apartment(number: 73)
john?.apartment = number73
number73?.tenant = john
// 此时两个对象相互持有强引用,形成循环引用
john = nil
// 由于循环引用,Person对象不会被释放,不会打印 "John is being deinitialized"
number73 = nil
// 同样,Apartment对象也不会被释放,不会打印 "Apartment #73 is being deinitialized"
// 将Apartment的tenant改为弱引用,打破循环引用
class Apartment {
let number: Int
init(number: Int) {
self.number = number
}
weak var tenant: Person?
deinit {
print("Apartment #\(number) is being deinitialized")
}
}
// 重新执行上述操作,当john = nil时,Person对象会被释放,然后Apartment对象也会被释放
- **无主引用(Unowned Reference)**:和弱引用类似,无主引用也不会增加对象的引用计数。不同的是,无主引用在被引用的对象被释放后不会被设置为 `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("Card #\(number) is being deinitialized")
}
}
var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
// 这里Customer对象的生命周期长于CreditCard对象,使用无主引用
john = nil
// 此时Customer对象会被释放,然后CreditCard对象也会被释放,会打印 "John is being deinitialized" 和 "Card #1234567890123456 is being deinitialized"
复杂对象关系图中的内存管理问题
- 循环引用:在复杂的对象关系图中,循环引用是最常见的内存管理问题。当两个或多个对象相互持有强引用时,它们的引用计数永远不会变为0,导致这些对象无法被释放,从而造成内存泄漏。例如上述Apartment和Person相互持有强引用的例子。
- 强引用环:除了简单的两个对象之间的循环引用,还可能存在多个对象形成的强引用环。例如A持有B的强引用,B持有C的强引用,C又持有A的强引用,这样也会导致这些对象无法被释放。
检测和解决循环引用问题
- 检测:
- 静态分析工具:Xcode自带的静态分析工具可以帮助检测代码中潜在的循环引用问题。在Xcode中,选择Product -> Analyze即可运行静态分析,它会在代码中标记出可能存在循环引用的地方。
- ** Instruments工具**:使用Instruments中的Leaks工具可以在运行时检测内存泄漏。通过运行应用程序并观察Leaks工具的报告,可以确定是否存在对象没有被正确释放,从而间接推断可能存在循环引用。
- 解决:
- 使用弱引用或无主引用:如上述例子所示,通过将相互引用中的一个引用改为弱引用或无主引用,可以打破循环引用。通常,当两个对象的生命周期相互独立,应该使用弱引用;当一个对象的生命周期必然长于另一个对象时,使用无主引用。
- 使用闭包时注意引用捕获:在使用闭包时,如果闭包中捕获了对象,也可能导致循环引用。例如:
class ViewController: UIViewController {
var someClosure: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
someClosure = { [weak self] in
guard let self = self else { return }
// 闭包中使用self
}
}
}
在闭包中使用 [weak self]
或 [unowned self]
来捕获 self
,以避免循环引用。[weak self]
会在 self
被释放时将其设置为 nil
,[unowned self]
则假设 self
始终存在,使用时要小心。
ARC基础上的优化策略提高内存使用效率
- 延迟加载:对于一些不急需使用的对象,可以采用延迟加载的方式。在Swift中,可以使用
lazy
关键字来实现延迟加载。例如:
class DataLoader {
lazy var data: [Int] = {
// 这里进行数据加载操作,可能是从文件、网络等读取数据
var result: [Int] = []
for i in 0..<1000 {
result.append(i)
}
return result
}()
}
let loader = DataLoader()
// 此时data还没有被加载
let firstValue = loader.data[0]
// 此时才会触发data的加载
这样可以避免在对象初始化时就加载大量数据,从而提高内存使用效率。 2. 对象复用:对于一些频繁创建和销毁的对象,可以考虑对象复用。例如在UITableView的Cell重用机制中,通过重用已经创建的Cell,避免了频繁创建新的Cell对象,从而减少内存开销。 3. 合理使用值类型:值类型(如结构体、枚举)在栈上分配内存,相比于引用类型(类)在堆上分配内存,值类型的内存管理开销更小。在合适的场景下,优先使用值类型可以提高内存使用效率。例如,一个简单的表示坐标的结构体:
struct Coordinate {
var x: Int
var y: Int
}
let point = Coordinate(x: 10, y: 20)
// 结构体在栈上分配内存,内存管理简单高效
- 及时释放资源:对于一些占用大量资源的对象,在不再使用时要及时将其设置为
nil
,以便ARC 能够尽快释放它们所占用的内存。例如,在一个视图控制器中,如果不再需要某个大型图片对象,应将其对应的属性设置为nil
。
class ImageViewController: UIViewController {
var largeImage: UIImage?
// 使用完图片后
func someFunction() {
largeImage = nil
}
}