如何测试对象是否已释放?为何循环引用测试未检测到内存泄漏?
问题:为什么预期的循环引用未导致内存泄漏?
我编写了一段本应存在循环引用的Swift代码:
protocol MyService { func perform(completion: @escaping () -> Void) } class MyServiceImpl: MyService { func perform(completion: @escaping () -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: completion) } } class MyObject { let service: MyService var didComplete = false init(service: MyService) { self.service = service } func doSomething(completion: @escaping () -> Void) { service.perform { self.didComplete = true completion() } } }
注意在MyObject.doSomething()方法中,我在service的completion闭包里强引用了self。我原本认为这会导致循环引用——因为MyObject持有service的强引用,而service也持有MyObject的引用,但实际编写测试后发现测试通过了,这和预期不符。
测试代码如下:
final class DemoTests: XCTestCase { func test_demo() { let service = MyServiceImpl() let myObject = MyObject(service: service) addTeardownBlock { [weak myObject, weak service] in XCTAssertNil(myObject) XCTAssertNil(service) } let exp = expectation(description: "wait for complete") myObject.doSomething { exp.fulfill() } wait(for: [exp], timeout: 1) XCTAssertTrue(myObject.didComplete) } }
我在循环引用或XCTest框架的理解上存在什么误区?
解答
1. 当前代码不存在循环引用
你对循环引用的判断有误,当前逻辑里并没有形成闭环的强引用链:
MyObject确实持有MyServiceImpl的强引用;- 但
MyServiceImpl并没有持有MyObject的引用——perform方法只是将completion闭包传递给了DispatchQueue,自身并没有长期保存这个闭包。当asyncAfter的延迟任务执行完毕后,GCD会自动释放该闭包,闭包内对self的强引用也会随之解除。
简单来说,MyServiceImpl只是临时转发了闭包,没有长期持有它,因此无法和MyObject形成互相强引用的循环。
2. 测试通过的原因
测试中的addTeardownBlock会在测试方法执行完毕后触发,此时:
- 延迟任务已经执行完成,GCD已释放闭包,
myObject和service的所有强引用都已解除; - 这两个对象没有被其他任何地方持有,会被ARC自动回收,因此
XCTAssertNil断言会通过。
如何制造真正的循环引用
若要让MyServiceImpl和MyObject形成循环引用,只需让MyServiceImpl长期持有completion闭包即可,示例修改如下:
class MyServiceImpl: MyService { var storedCompletion: (() -> Void)? // 长期持有闭包 func perform(completion: @escaping () -> Void) { storedCompletion = completion DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in self?.storedCompletion?() // 如果不手动清空storedCompletion,就会形成循环引用 // self?.storedCompletion = nil } } }
这种情况下,MyObject强引用MyServiceImpl,MyServiceImpl通过storedCompletion强引用MyObject,形成闭环。此时测试中的XCTAssertNil会失败,从而检测到内存泄漏。
内容的提问来源于stack exchange,提问作者Alexandru Motoc
相关产品推荐
相关产品推荐

