面试题答案
一键面试Swift的自动引用计数(ARC)机制
- 工作原理 ARC是Swift语言中自动内存管理的核心机制。当一个类的实例被创建时,ARC会为其分配内存,并将其引用计数设置为1。每当有一个新的强引用指向这个实例时,引用计数就会加1;而当指向该实例的强引用被释放(比如变量超出作用域),引用计数就会减1。当引用计数变为0时,ARC会自动释放该实例所占用的内存。例如:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var person: Person? = Person(name: "John")
// Person实例引用计数为1
person = nil
// 引用计数减为0,实例被释放
- 局限性
- 循环引用:如果两个或多个实例相互持有强引用,就会形成循环引用,导致引用计数永远不会变为0,实例无法被释放。例如,两个视图控制器互相持有对方的强引用:
class ViewControllerA: UIViewController {
var viewControllerB: ViewControllerB?
}
class ViewControllerB: UIViewController {
var viewControllerA: ViewControllerA?
}
- **闭包引起的内存泄漏**:当闭包捕获了一个实例,并且这个闭包的生命周期比该实例长时,就可能导致内存泄漏。例如:
class SomeClass {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deinitialized") }
func doWork() {
let closure = { [weak self] in
guard let self = self else { return }
print(self.name)
}
// 使用closure
}
}
如果闭包没有使用[weak self]
或[unowned self]
来捕获self
,就可能导致SomeClass
实例无法被释放。
多线程iOS应用场景下内存泄漏检测与修复思路和步骤
- 检测视图控制器之间的循环引用
- 使用 Instruments 的 Leaks 工具:运行应用,通过Instruments中的Leaks工具来检测是否存在内存泄漏。Leaks工具会标记出可能存在泄漏的内存块,并尝试找到导致泄漏的对象引用路径。
- 分析引用关系:如果发现视图控制器相关的泄漏,在调试器中暂停应用,通过查看对象的引用关系来确定是否存在循环引用。可以使用
po
(print object)命令在LLDB调试器中查看对象的属性和引用。 - 修复循环引用:将其中一个强引用改为弱引用(
weak
)或无主引用(unowned
)。如果其中一个视图控制器的生命周期应该短于另一个,通常使用weak
引用;如果两个视图控制器生命周期相同,且可以确定不会出现nil
的情况,可以使用unowned
引用。例如:
class ViewControllerA: UIViewController {
weak var viewControllerB: ViewControllerB?
}
class ViewControllerB: UIViewController {
var viewControllerA: ViewControllerA?
}
- 检测闭包引起的内存泄漏
- 代码审查:仔细检查闭包的使用,特别是那些捕获了实例的闭包。确保使用
[weak self]
或[unowned self]
来捕获self
,避免强引用循环。 - 添加调试日志:在闭包内添加日志语句,观察闭包的执行情况以及捕获的
self
是否正确释放。例如:
- 代码审查:仔细检查闭包的使用,特别是那些捕获了实例的闭包。确保使用
class SomeClass {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deinitialized") }
func doWork() {
let closure = { [weak self] in
guard let self = self else { return }
print("\(self.name) in closure")
}
// 使用closure
}
}
- 处理Core Foundation对象的手动内存管理
- 使用桥接转换:在Swift中,尽量使用Swift对象来代替Core Foundation对象,因为Swift对象可以通过ARC进行自动管理。如果必须使用Core Foundation对象,使用桥接转换(如
as?
或as!
)将其转换为对应的Swift对象,这样就可以利用ARC。例如,将CFString
转换为String
:
- 使用桥接转换:在Swift中,尽量使用Swift对象来代替Core Foundation对象,因为Swift对象可以通过ARC进行自动管理。如果必须使用Core Foundation对象,使用桥接转换(如
let cfString: CFString = "Hello" as CFString
let swiftString = cfString as String
- **手动管理内存**:对于不能桥接转换的Core Foundation对象,必须按照Core Foundation的内存管理规则手动管理内存。使用`CFRetain`增加引用计数,使用`CFRelease`减少引用计数。例如:
let cfObject: CFTypeRef? = someCFObjectCreationFunction()
if let cfObject = cfObject {
CFRetain(cfObject)
// 使用cfObject
CFRelease(cfObject)
}
- 多线程相关的内存泄漏
- 使用线程安全的数据结构:在多线程环境中,使用线程安全的数据结构来避免数据竞争导致的内存泄漏。例如,使用
DispatchQueue
来同步访问共享资源。 - 内存访问同步:确保在不同线程中对共享内存的访问是同步的。可以使用
DispatchSemaphore
或NSLock
来实现同步。例如:
- 使用线程安全的数据结构:在多线程环境中,使用线程安全的数据结构来避免数据竞争导致的内存泄漏。例如,使用
let semaphore = DispatchSemaphore(value: 1)
semaphore.wait()
// 访问共享资源
semaphore.signal()
通过以上系统的检测和修复步骤,可以有效地处理复杂多线程iOS应用场景中的内存泄漏问题。