OCMock 原理

OCMock 原理

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

  1. OCMStub是個宏,首先編譯時會作展開, ()內的內容會被展開成Block調用.(忽略了一些細節):ide

({ 
  [OCMMacroState beginStubMacro]; 
  [userDefaultsMock stringForKey:[OCMArg any]]; 
  [OCMMacroState endStubMacro]; 
}).andReturn(@"http://testurl");
  1. OCMExpectationRecorderurl

[OCMMacroState beginStubMacro]經過一個全局實例持有一個新的OCMExpectationRecorder實例.code

[OCMMacroState endStubMacro]則是全局實例釋放了這個OCMExpectationRecorder實例, 並返回這個OCMExpectationRecorder實例.繼承

  1. 好了所謂的黑魔法開始了:ci

因爲userDefaultsMock其實是NSProxy子類的實例,全部的方法調用都會都會先調用該實例的get

-(id)forwardingTargetForSelector:(SEL)的方法,這個時候就能夠將該方法調用轉給OCMExpectationRecorder.string

  1. 這個時候交給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,也便是說,保存了selectorarguments

mockObject在這裏就是userDefaultsMock,保存了若干invocationMatcher

  1. 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中保存了返回值.

  1. 模擬調用方法的時候:

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

參考

https://github.com/erikdoe/oc...

相關文章
相關標籤/搜索