面试题答案
一键面试可能出现的内存管理问题及分析
- 强引用循环:当装饰器对象和被装饰对象之间相互持有强引用时,就会形成强引用循环。例如,装饰器持有对被装饰对象的强引用以增强其功能,而被装饰对象可能又持有对装饰器的强引用(比如用于回调等场景),这样两个对象相互引用,导致它们的引用计数永远不会降为0,从而无法被释放,造成内存泄漏。
避免方法
- 使用弱引用或无主引用:如果被装饰对象对装饰器的引用只是为了回调,并且可以接受装饰器在某个时刻被释放,那么可以将被装饰对象对装饰器的引用设置为弱引用(
weak
)。如果确定装饰器的生命周期至少和被装饰对象一样长,也可以使用无主引用(unowned
),但使用unowned
时要小心空指针异常,因为它不允许nil
值。
具体场景及代码示例
- 场景:假设我们有一个
ViewController
(可视为被装饰对象),需要通过一个DataLoaderDecorator
(装饰器)来加载数据并更新视图。ViewController
可能需要在数据加载完成后通知DataLoaderDecorator
做一些后续处理。
protocol DataLoader {
func loadData()
}
class ViewController {
var dataLoader: DataLoader?
// 这里假设会持有装饰器的引用
var decorator: DataLoaderDecorator?
init() {
let decorator = DataLoaderDecorator(dataLoader: self)
self.dataLoader = decorator
self.decorator = decorator
}
}
class DataLoaderDecorator: DataLoader {
let decorated: DataLoader
init(dataLoader: DataLoader) {
self.decorated = dataLoader
}
func loadData() {
// 增强的加载逻辑
decorated.loadData()
// 加载完成后可能需要通知被装饰对象
if let viewController = decorated as? ViewController {
viewController.updateUI()
}
}
}
extension ViewController: DataLoader {
func loadData() {
// 实际的数据加载逻辑
print("Loading data...")
}
func updateUI() {
print("Updating UI...")
}
}
在上述代码中,如果不处理引用关系,ViewController
和DataLoaderDecorator
会形成强引用循环。
解决方法
protocol DataLoader {
func loadData()
}
class ViewController {
var dataLoader: DataLoader?
// 使用弱引用避免强引用循环
weak var decorator: DataLoaderDecorator?
init() {
let decorator = DataLoaderDecorator(dataLoader: self)
self.dataLoader = decorator
self.decorator = decorator
}
}
class DataLoaderDecorator: DataLoader {
let decorated: DataLoader
init(dataLoader: DataLoader) {
self.decorated = dataLoader
}
func loadData() {
// 增强的加载逻辑
decorated.loadData()
// 加载完成后可能需要通知被装饰对象
if let viewController = decorated as? ViewController {
viewController.updateUI()
}
}
}
extension ViewController: DataLoader {
func loadData() {
// 实际的数据加载逻辑
print("Loading data...")
}
func updateUI() {
print("Updating UI...")
}
}
通过将ViewController
中的decorator
属性声明为weak
,打破了强引用循环,确保当其中一个对象不再被其他地方引用时,能够被正确释放。