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
完事.