假装为Class的NSProxy不会在64位运行时中处理respondsToSelector

Jon*_*eid 7 objective-c objective-c-runtime class-method nsproxy

在OCMockito中,使用NSProxy实现测试双精度.实例中的双重实现-respondsToSelector:如下:

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_mockedClass instancesRespondToSelector:aSelector];
}
Run Code Online (Sandbox Code Playgroud)

但是对于一个类的实现-respondsToSelector:这样的双重实现:

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_mockedClass respondsToSelector:aSelector];
}
Run Code Online (Sandbox Code Playgroud)

这一切都适用于32位运行时.例如,如果_mockedClass[NSString class],则代理正确回答它响应选择器+pathWithComponents:

但在64位运行时,它崩溃了:

Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT

Application Specific Information:
objc[1868]: GC: forcing GC OFF because OBJC_DISABLE_GC is set

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 0x00007fff95cbffc6 cache_getImp + 6
1   libobjc.A.dylib                 0x00007fff95ccd1dc lookUpImpOrForward + 50
2   libobjc.A.dylib                 0x00007fff95ccd198 lookUpImpOrNil + 20
3   libobjc.A.dylib                 0x00007fff95cc218a class_respondsToSelector + 37
4   com.apple.CoreFoundation        0x00007fff91c131ad ___forwarding___ + 429
5   com.apple.CoreFoundation        0x00007fff91c12f78 _CF_forwarding_prep_0 + 120
6   org.mockito.OCMockitoTests      0x000000010451a55b -[StubClassTest testStubbedMethod_ShouldReturnGivenObject] + 107 (StubClassTest.m:48)
Run Code Online (Sandbox Code Playgroud)

请注意,它正在呼叫class_respondsToSelector(…).我怀疑我被运行时的优化所困扰.我该怎么做才能解决这个问题?

Sas*_*ats 7

这是一个有点长的答案,所以忍受我.我运行了一个简单的代码来验证行为:

Class mock = mockClass([NSProcessInfo class]);
[mock processInfo];
[verify(mock) processInfo];
Run Code Online (Sandbox Code Playgroud)

确实它会因为错误的指针异常而崩溃.用.替换第一行

id mock = mockClass([NSProcessInfo class]);
Run Code Online (Sandbox Code Playgroud)

按预期工作.我认为在ARC之后查看代码可能是值得的.那些片段有点长,所以这里有一些要点:Class基于测试,id基于测试

正如您所看到的,当您声明类型变量时,Class会有额外的变量release.我的猜测是,因为类在整个运行时间内注册(除非使用运行时api删除),所以可以将Class变量设置为__unsafe_unretained.

总而言之,您有两种可能的解决方案:

@implementation StubClassTest
{
    __strong Class mockClass;
}
Run Code Online (Sandbox Code Playgroud)

要么

@implementation StubClassTest
{
    id mockClass;
}
Run Code Online (Sandbox Code Playgroud)

似乎为我解决了这个问题.

更新

作为一种特殊情况,如果对象的基类型是Class(可能是协议限定的),则将类型调整为具有__unsafe_unretained限定.

来自http://clang.llvm.org/docs/AutomaticReferenceCounting.html#objects