You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试对象是否已释放?为何循环引用测试未检测到内存泄漏?

问题:为什么预期的循环引用未导致内存泄漏?

我编写了一段本应存在循环引用的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:37:05