如何用OCMock存根带响应Block的类方法?解决EXC_BAD_ACCESS崩溃
我之前也踩过这个坑!你遇到的EXC_BAD_ACCESS崩溃,主要是两个问题导致的:一是手动解析NSInvocation时容易踩参数索引的坑,二是局部变量successResult的内存管理没处理好,导致block回调执行时它已经被释放了。下面给你两种正确的测试方式:
方式一:用OCMock内置方法(推荐,简洁又安全)
OCMock专门提供了OCMArg invokeBlockWithArgs方法,帮你直接触发方法中的block参数,完全不用手动处理NSInvocation,既避免了索引错误,又解决了内存管理问题:
// 创建测试用的成功结果 HTTPResult *successResult = [[HTTPResult alloc] init]; successResult.success = YES; successResult.content = @{@"key":@"value"}; // 生成HTTPUtility的类Mock对象 id httpMock = OCMClassMock([HTTPUtility class]); // Stub目标类方法,直接用invokeBlockWithArgs触发response回调 OCMStub(ClassMethod([httpMock requestWithHTTPMethod:HTTPRequestMethodGet URLString:@"testURL" parameters:[OCMArg any] response:[OCMArg invokeBlockWithArgs:successResult, nil]])).andReturn(nil); // 这里调用你的被测代码(它会触发HTTPUtility的requestWithHTTPMethod:方法) // 测试结束后清理Mock(可选,但建议养成好习惯) [httpMock stopMocking];
方式二:手动处理NSInvocation(适合特殊场景)
如果因为业务需求必须手动解析NSInvocation,那要注意两个关键细节:
- 确保参数索引正确:类方法的
NSInvocation参数索引从2开始(索引0是类对象本身,索引1是方法SEL),你的目标block是第4个方法参数,对应的索引是5(这个你之前的代码是对的)。 - 正确持有block和结果对象:在ARC下,从
NSInvocation获取的block默认是__unsafe_unretained的,要赋值给__strong变量避免提前释放;同时要确保successResult在block执行时仍然存活:
HTTPResult *successResult = [[HTTPResult alloc] init]; successResult.success = YES; successResult.content = @{@"key":@"value"}; id httpMock = OCMClassMock([HTTPUtility class]); OCMStub(ClassMethod([httpMock requestWithHTTPMethod:HTTPRequestMethodGet URLString:@"testURL" parameters:[OCMArg any] response:[OCMArg any]])).andDo(^(NSInvocation *invocation) { // 用__strong修饰block变量,确保它被正确持有 void(^__strong responseBlock)(HTTPResult *) = nil; [invocation getArgument:&responseBlock atIndex:5]; // 确保successResult在回调执行时仍然存活 HTTPResult *__strong retainedResult = successResult; responseBlock(retainedResult); }); // 调用被测代码... [httpMock stopMocking];
为什么你的原代码会崩溃?
- 内存管理问题:
successResult是局部变量,如果被测代码是异步触发block,测试方法执行完后ARC会释放它,此时调用response(successResult)就会访问野指针。 - block持有问题:从
NSInvocation获取的block没有被__strong变量持有,可能被提前释放,导致调用时崩溃。
内容的提问来源于stack exchange,提问作者chenfeng
相关产品推荐
相关产品推荐

