OCMock 原理

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则很机巧,实现步骤如下:

  1. OCMStub是个宏,首先编译时会做展开, ()内的内容会被展开成Block调用.(忽略了一些细节):

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

[OCMMacroState beginStubMacro]通过一个全局实例持有一个新的OCMExpectationRecorder实例.

[OCMMacroState endStubMacro]则是全局实例释放了这个OCMExpectationRecorder实例, 并返回这个OCMExpectationRecorder实例.

  1. 好了所谓的黑魔法开始了:

由于userDefaultsMock实际上是NSProxy子类的实例,所有的方法调用都会都会先调用该实例的

-(id)forwardingTargetForSelector:(SEL)的方法,这个时候就可以将该方法调用转给OCMExpectationRecorder.

  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…

    原文作者:canopus4u
    原文地址: https://segmentfault.com/a/1190000008159813
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞