Completion Handler无返回值排查:Operation(NSOperation)单元测试问题
嘿,这种情况我之前在做NSOperation相关的单元测试时也踩过坑,结合你的描述(调试时已经走到了strongSelf.onCompletion?(Result.success(dataResponse))但completion没反应),大概率是这几个原因导致的:
单元测试没等待异步操作完成
这是最常见的坑!NSOperation是异步执行的,而XCTest默认会在测试方法的主线程代码跑完后立刻终止测试进程,根本不等后台的Operation执行完completion。哪怕你调试时能看到走到了那行代码,测试进程可能已经被kill了,completion里的逻辑自然没机会跑完。
解决办法是用XCTestExpectation来强制等待异步操作完成,示例代码如下:func testYourOperation() { // 创建一个期望,用来标记异步操作完成 let completionExpectation = expectation(description: "Operation completion called") let yourOperation = CustomOperation() yourOperation.onCompletion = { result in // 这里写你的断言逻辑 XCTAssertTrue(result.isSuccess) // 标记期望已完成 completionExpectation.fulfill() } let queue = OperationQueue() queue.addOperation(yourOperation) // 等待期望完成,超时时间设为5秒足够大部分场景 waitForExpectations(timeout: 5) { error in if let error = error { XCTFail("Operation timed out: \(error.localizedDescription)") } } }自定义异步NSOperation的状态管理错误
如果你的Operation是异步的(比如内部封装了网络请求、后台任务),但没正确重写isAsynchronous、isExecuting、isFinished这些属性,也没通过KVO更新状态,那么OperationQueue会误以为你的Operation已经执行完毕,提前释放相关资源。哪怕你的异步任务还在跑,后续调用completion时可能因为上下文被释放而失效。
要确保异步Operation正确实现状态管理:比如用私有变量存储_isExecuting和_isFinished,重写对应的属性并触发KVO通知,在start()方法里正确切换状态,直到异步任务完成后再标记为finished。Completion Handler的生命周期问题
虽然你调试时走到了调用completion的那行,但如果completion handler本身已经被提前释放了(比如测试方法里的completion是局部变量,而Operation的持有关系有问题),那调用它自然不会有反应。不过这种情况比较少见,因为你已经走到了调用那行,大概率是前面两个问题,但也可以检查下completion的持有是否正确,比如有没有不小心把completion设成weak导致提前释放。线程上下文问题
如果你的completion是在后台线程调用的,而completion里的逻辑依赖主线程(比如某些UI相关操作,不过单元测试里一般不会有,但如果有主线程断言的话),可能会因为线程问题导致代码没执行。可以在completion里加个简单的print语句,看看控制台有没有输出,确认completion是否真的被调用了。
内容的提问来源于stack exchange,提问作者SwiftyJD

