OCMock 原理
OCMock中OCMStub
这个宏很强大,可以对一个mock class指定方法指定返回值:
// create a mock for the user defaults
id userDefaultsMock = OCMClassMock([NSUserDefaults class]);
// set it up to return the specified value no matter how the method is invoked
OCMStub([userDefaultsMock stringForKey:[OCMArg any]]).andReturn(@"http://testurl");
是不是很神奇?我们来解析下技术细节. OCMClassMock
不用解释太多了, 通过一个NSProxy来转发message, OCMock中代码也很straightforward,不多言.
而OCMStub
则很机巧,实现步骤如下:
OCMStub是个宏,首先编译时会做展开,
()
内的内容会被展开成Block调用.(忽略了一些细节):
({
[OCMMacroState beginStubMacro];
[userDefaultsMock stringForKey:[OCMArg any]];
[OCMMacroState endStubMacro];
}).andReturn(@"http://testurl");
OCMExpectationRecorder
[OCMMacroState beginStubMacro]
通过一个全局实例持有一个新的OCMExpectationRecorder
实例.
[OCMMacroState endStubMacro]
则是全局实例释放了这个OCMExpectationRecorder
实例, 并返回这个OCMExpectationRecorder
实例.
好了所谓的黑魔法开始了:
由于userDefaultsMock实际上是NSProxy
子类的实例,所有的方法调用都会都会先调用该实例的
-(id)forwardingTargetForSelector:(SEL)
的方法,这个时候就可以将该方法调用转给OCMExpectationRecorder
.
这个时候交给Recorder了
OCMExpectationRecorder
也是一个NSProxy子类实例, 当然这时候不用继续转发给其他实例了, 而是调用基类的(void)forwardInvocation:(NSInvocation *)invocation
(在这里我们忽略了一些继承之后的细节,只看核心部分,代码我做了一些改动):
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:nil];
[invocationMatcher setInvocation:invocation];
[mockObject addStub:invocationMatcher];
}
首先这个invocation
实际上是不会真实invoke的, 所以target
被设置成了nil.
invocationMatcher
保存了invocation
,也即是说,保存了selector
和arguments
mockObject
在这里就是userDefaultsMock
,保存了若干invocationMatcher
andReturn
,稍微有点绕,客官慢慢看
仍然是个宏,我们看看展开之后是什么, 以上述代码为例:
({
[OCMMacroState beginStubMacro];
[userDefaultsMock stringForKey:[OCMArg any]];
[OCMMacroState endStubMacro];
})._andReturn(({
__typeof__(aValue) _val = (@"http://testurl");
NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))];
if (OCMIsObjectType(@encode(__typeof(_val)))) {
objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN);
}
_nsval;
}));
看着略显复杂,简化一下:
(...)._andReturn(_nsval);
_andReturn
的定义是这样的:
- (OCMStubRecorder *(^)(NSValue *))_andReturn;
首先它是block,所以._andReturn
返回了一个block, 而._andReturn()
则是对这个block进行调用.而这个block参数是接受一个NSValue *
.
宏展开那段看似那么多, 而且又是一个block,其实看它最后, _nsval;
返回一个值,其实这段对理解流程并不重要,我们忽略.再看_andReturn
的代码:
- (OCMStubRecorder *(^)(NSValue *))_andReturn{
id (^theBlock)(id) = ^ (NSValue *aValue){
if(OCMIsObjectType([aValue objCType])){
NSValue *objValue = nil;
[aValue getValue:&objValue];
return [self andReturn:objValue];
}else{
return [self andReturnValue:aValue];
}
};
return [[theBlock copy] autorelease];
}
基本也不太重要,核心是调用了andReturn
跳到-(id)andReturn:(id);
- (id)andReturn:(id)anObject{
[[self stub] addInvocationAction:[[[OCMReturnValueProvider alloc] initWithValue:anObject] autorelease]];
return self;
}
简言之:
[self stub]
返回了invocationMatcher
, 这个matcher中保存了返回值.
模拟调用方法的时候:
NSString* value = [userDefaultsMock stringForKey:@"key"];
首先2中提到的全局实例已经不持有OCMExpectationRecorder
实例了,这时候-(id) forwardingTargetForSelector:(SEL)
不会转发给其他target了. 所以来到了userDefaultsMock的(void)forwardInvocation:(NSInvocation *)invocation
,它能干什么呢, 其实这个时候就简单了,记得4里面的[mockObject addStub:invocationMatcher]
吗? 在stubs
(invocationMatcher
)里通过invocation
匹配,如果匹配就从OCMReturnValueProvider
拿到指定的返回值, 塞给invocation
完事.