我有一些执行保留循环的模拟代码(或者至少在我看来它应该)。这是:
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()
方法中,我们在服务完成中强烈捕获 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
框架不了解什么?
谢谢您的帮助!
这里没有保留周期。该闭包确实强烈地捕获了
self
,并且 self
也保留了对 MyServiceImpl
的强烈引用。然而,当闭包传递给MyServiceImpl
时,MyServiceImpl
不会保留闭包的强引用。它只是将其传递给 DispatchQueue
,后者会在运行完成后立即丢弃闭包。该图看起来像这样:
DispatchQueue.main ---> closure ---> MyObject ----> MyServiceImpl
要获得保留周期,
MyService
可以保留对闭包的引用:
class MyServiceImpl: MyService {
var closure: (() -> Void)?
func perform(completion: @escaping () -> Void) {
closure = completion // note this line
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: completion)
}
}
现在有一个保留周期,您的测试失败了。您还可以在 Xcode 的内存图调试器中看到这一点。
没有保留周期。
DispatchQueue.main.asyncAfter
执行后将释放闭包,然后引用计数器达到0并且闭包被释放,并且引用计数也减少到myObject