OCMock中OCMStub
这个宏很强大,能够对一个mock class指定方法指定返回值:git
// 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,很少言.github
而OCMStub
则很机巧,实现步骤以下:objective-c
OCMStub是个宏,首先编译时会作展开, ()
内的内容会被展开成Block调用.(忽略了一些细节):ide
({ [OCMMacroState beginStubMacro]; [userDefaultsMock stringForKey:[OCMArg any]]; [OCMMacroState endStubMacro]; }).andReturn(@"http://testurl");
OCMExpectationRecorderurl
[OCMMacroState beginStubMacro]
经过一个全局实例持有一个新的OCMExpectationRecorder
实例.code
[OCMMacroState endStubMacro]
则是全局实例释放了这个OCMExpectationRecorder
实例, 并返回这个OCMExpectationRecorder
实例.继承
好了所谓的黑魔法开始了:ci
因为userDefaultsMock其实是NSProxy
子类的实例,全部的方法调用都会都会先调用该实例的get
-(id)forwardingTargetForSelector:(SEL)
的方法,这个时候就能够将该方法调用转给OCMExpectationRecorder
.string
这个时候交给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
完事.