OHHTTPStubs 源码解析
2016-12-27 13:45
615 查看
NSURLProtocol
NSURLProtocol是苹果为我们提供的 URL
Loading System 的一部分,这是一张从官方文档贴过来的图片:
官方文档对
NSURLProtocol的描述是这样的:
An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You
create subclasses for any custom protocols or URL schemes that your app supports.
在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的
NSURLProtocol对象处理对应的 URL 请求,而我们需要做的就是写一个继承自
NSURLProtocol的类,并通过
- registerClass:方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。
这样,我们需要解决的核心问题就变成了如何使用
NSURLProtocol来处理所有的网络请求,这里使用苹果官方文档中的 CustomHTTPProtocol 进行介绍,你可以点击这里下载源代码。
在这个工程中
CustomHTTPProtocol.m是需要重点关注的文件,
CustomHTTPProtocol就是
NSURLProtocol的子类:
@interface CustomHTTPProtocol : NSURLProtocol ... @end
现在重新回到需要解决的问题,也就是 如何使用 NSURLProtocol 拦截 HTTP 请求?,有这个么几个问题需要去解决:
如何决定哪些请求需要当前协议对象处理?
对当前的请求对象需要进行哪些处理?
NSURLProtocol如何实例化?
如何发出 HTTP 请求并且将响应传递给调用者?
上面的这几个问题其实都可以通过
NSURLProtocol为我们提供的 API 来解决,决定请求是否需要当前协议对象处理的方法是:
+ canInitWithRequest:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request { BOOL shouldAccept; NSURL *url; NSString *scheme; shouldAccept = (request != nil); if (shouldAccept) { url = [request URL]; shouldAccept = (url != nil); } return shouldAccept; }
因为项目中的这个方法是大约有 60 多行,在这里只粘贴了其中的一部分,只为了说明该方法的作用:每一次请求都会有一个
NSURLRequest实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象;而上面的代码只会处理所有
URL不为空的请求。
请求经过
+ canInitWithRequest:方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作,而这都会在
+ canonicalRequestForRequest:中进行,虽然它与
+ canInitWithRequest:方法传入的 request 对象都是一个,但是最好不要在
+ canInitWithRequest:中操作对象,可能会有语义上的问题;所以,我们需要覆写
+ canonicalRequestForRequest:方法提供一个标准的请求对象:
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; }
这里对请求不做任何修改,直接返回,当然你也可以给这个请求加个 header,只要最后返回一个
NSURLRequest对象就可以。
在得到了需要的请求对象之后,就可以初始化一个
NSURLProtocol对象了:
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client { return [super initWithRequest:request cachedResponse:cachedResponse client:client]; }
在这里直接调用
super的指定构造器方法,实例化一个对象,然后就进入了发送网络请求,获取数据并返回的阶段了:
- (void)startLoading { NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil]; NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request]; [task resume]; }
这里使用简化了 CustomHTTPClient 中的项目代码,可以达到几乎相同的效果。
你可以在
- startLoading中使用任何方法来对协议对象持有的
request进行转发,包括
NSURLSession、
NSURLConnection甚至使用
AFNetworking 等网络库,只要你能在回调方法中把数据传回
client,帮助其正确渲染就可以,比如这样:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [[self client] URLProtocol:self didLoadData:data]; }
当然这里省略后的代码只会保证大多数情况下的正确执行,只是给你一个对获取响应数据粗略的认知,如果你需要更加详细的代码,我觉得最好还是查看一下
CustomHTTPProtocol中对
HTTP 响应处理的代码,也就是
NSURLSessionDelegate协议实现的部分。
client你可以理解为当前网络请求的发起者,所有的
client都实现了
NSURLProtocolClient协议,协议的作用就是在
HTTP 请求发出以及接受响应时向其它对象传输数据:
@protocol NSURLProtocolClient <NSObject> ... - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy; - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data; - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol; ... @end
当然这个协议中还有很多其他的方法,比如 HTTPS 验证、重定向以及响应缓存相关的方法,你需要在合适的时候调用这些代理方法,对信息进行传递。
如果你只是继承了
NSURLProtocol并且实现了上述方法,依然不能达到预期的效果,完成对 HTTP 请求的拦截,你还需要在 URL 加载系统中注册当前类:
[NSURLProtocol registerClass:self];
需要注意的是
NSURLProtocol只能拦截
UIURLConnection、
NSURLSession和
UIWebView中的请求,对于
WKWebView中发出的网络请求也无能为力,如果真的要拦截来自
WKWebView中的请求,还是需要实现
WKWebView对应的
WKNavigationDelegate,并在代理方法中获取请求。
无论是
NSURLProtocol、
NSURLConnection还是
NSURLSession都会走底层的
socket,但是
WKWebView可能由于基于 WebKit,并不会执行 C socket 相关的函数对 HTTP 请求进行处理,具体会执行什么代码暂时不是很清楚,如果对此有兴趣的读者,可以联系笔者一起讨论。
如何使用 OHHTTPStubs Mock 网络请求
HTTP Mock 在测试中非常好用,我们可以在不需要后端 API 的情况下,在本地对 HTTP 请求进行拦截,返回想要的 json数据,而 OHHTTPStubs 就为我们提供了这样一种解决方案。
在了解其实现之前,先对 OHHTTPStubs 进行简单的介绍,引入头文件这种事情在这里会直接省略,先来看一下程序的源代码:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) { return [request.URL.absoluteString isEqualToString:@"https://idont.know"]; } withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) { NSString *fixture = OHPathForFile(@"example.json", self.class); return [OHHTTPStubsResponse responseWithFileAtPath:fixture statusCode:200 headers:@{@"Content-Type":@"application/json"}]; }]; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:@"https://idont.know" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"%@", responseObject); } failure:nil];
我们向
https://idont.know这个 URL 发送一个 GET 请求,虽然这个 URL 并不存在,但是这里的代码通过 HTTP stub 成功地模拟了 HTTP 响应:
OHHTTPStubs
的实现
在了解了 OHHTTPStubs 的使用之后,我们会对其实现进行分析,它分成四部分进行:OHHTTPStubsProtocol拦截 HTTP 请求
OHHTTPStubs单例管理
OHHTTPStubsDescriptor实例
OHHTTPStubsResponse伪造 HTTP 响应
一些辅助功能
OHHTTPStubsProtocol
拦截 HTTP 请求
在 OHHTTPStubs 中继承 NSURLProtocol的类就是
OHHTTPStubsProtocol,它在
HTTP 请求发出之前对 request 对象进行过滤以及处理:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return ([OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)response client:(id<NSURLProtocolClient>)client {
OHHTTPStubsProtocol* proto = [super initWithRequest:request cachedResponse:nil client:client];
proto.stub = [OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request];
return proto;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; }
判断请求是否会被当前协议对象进行处理是需要
OHHTTPStubs的实例方法
- firstStubPassingTestForRequest:的执行的,在这里暂时先不对这个方法进行讨论。
接下来就是请求发送的过程
- startLoading方法了,该方法的实现实在是太过于复杂,所以这里分块来分析代码:
- (void)startLoading { NSURLRequest* request = self.request; id<NSURLProtocolClient> client = self.client; OHHTTPStubsResponse* responseStub = self.stub.responseBlock(request); if (OHHTTPStubs.sharedInstance.onStubActivationBlock) { OHHTTPStubs.sharedInstance.onStubActivationBlock(request, self.stub, responseStub); } ... }
从当前对象中取出
request以及
client对象,如果
OHHTTPStubs的单例中包含
onStubActivationBlock,就会执行这里的
block,然后调用
responseBlock获取一个
OHHTTPStubsResponseHTTP
响应对象。
OHHTTPStubs不只提供了
onStubActivationBlock这一个钩子,还有以下
block:
+ onStubActivationBlock:stub 被激活时调用
+ onStubRedirectBlock:发生重定向时
+ afterStubFinishBlock:在 stub 结束时调用
如果响应对象的生成没有遇到任何问题,就会进入处理 Cookie、重定向、发送响应和模拟数据流的过程了。
首先是对 Cookie 的处理
NSHTTPURLResponse* urlResponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:responseStub.statusCode HTTPVersion:@"HTTP/1.1" headerFields:responseStub.httpHeaders]; if (request.HTTPShouldHandleCookies && request.URL) { NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseStub.httpHeaders forURL:request.URL]; if (cookies) { [NSHTTPCookieStorage.sharedHTTPCookieStorage setCookies:cookies forURL:request.URL mainDocumentURL:request.mainDocumentURL]; } }
如果 HTTP 状态码在 300-400 之间,就会处理重定向的问题,调用
onStubRedirectBlock进行需要的回调
NSString* redirectLocation = (responseStub.httpHeaders)[@"Location"]; NSURL* redirectLocationURL = redirectLocation ? [NSURL URLWithString:redirectLocation] : nil; if (((responseStub.statusCode > 300) && (responseStub.statusCode < 400)) && redirectLocationURL) { NSURLRequest* redirectRequest = [NSURLRequest requestWithURL:redirectLocationURL]; [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ if (!self.stopped) { [client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:urlResponse]; if (OHHTTPStubs.sharedInstance.onStubRedirectBlock) { OHHTTPStubs.sharedInstance.onStubRedirectBlock(request, redirectRequest, self.stub, responseStub); } } }]; }
最后这里有一些复杂,我们根据
stub中存储的
responseTime来模拟响应的一个延迟时间,然后使用
- streamDataForClient:withStubResponse:completion:来模拟数据以
NSData的形式分块发送回
client的过程,最后调用
afterStubFinishBlock。
[self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ if (!self.stopped) { [client URLProtocol:self didReceiveResponse:urlResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed]; if(responseStub.inputStream.streamStatus == NSStreamStatusNotOpen) { [responseStub.inputStream open]; } [self streamDataForClient:client withStubResponse:responseStub completion:^(NSError * error) { [responseStub.inputStream close]; NSError *blockError = nil; if (error==nil) { [client URLProtocolDidFinishLoading:self]; } else { [client URLProtocol:self didFailWithError:responseStub.error]; blockError = responseStub.error; } if (OHHTTPStubs.sharedInstance.afterStubFinishBlock) { OHHTTPStubs.sharedInstance.afterStubFinishBlock(request, self.stub, responseStub, blockError); } }]; } }];
当然如果在生成
responseStub的时候发生了错误,也会进行类似的操作,在延迟一定时间(模拟网络延迟)后执行 block 并传入各种参数:
[self executeOnClientRunLoopAfterDelay:responseStub.responseTime block:^{ if (!self.stopped) { [client URLProtocol:self didFailWithError:responseStub.error]; if (OHHTTPStubs.sharedInstance.afterStubFinishBlock) { OHHTTPStubs.sharedInstance.afterStubFinishBlock(request, self.stub, responseStub, responseStub.error); } } }];
模拟数据流
因为在客户端接收数据时,所有的 NSData并不是一次就涌入客户端的,而是分块加载打包解码的,尤其是在我们执行下载操作时,有时几 MB 的文件不可能同时到达服务端,而
- startLoading中调用的
- streamDataForClient:withStubResponse:completion:方法就是为了模拟数据流,分块向服务端发送数据,不过这部分的处理涉及到一个私有的结构体
OHHTTPStubsStreamTimingInfo:
typedef struct { NSTimeInterval slotTime; double chunkSizePerSlot; double cumulativeChunkSize; } OHHTTPStubsStreamTimingInfo;
这个结构体包含了关于发送数据流的信息:
slotTime:两次发送
NSData的间隔时间
chunkSizePerSlot:每块数据流大小
cumulativeChunkSize:已发送的数据流大小
模拟数据流的过程需要两个方法的支持,其中一个方法做一些预加载工作:
- (void)streamDataForClient:(id<NSURLProtocolClient>)client withStubResponse:(OHHTTPStubsResponse*)stubResponse completion:(void(^)(NSError * error))completion { if ((stubResponse.dataSize>0) && stubResponse.inputStream.hasBytesAvailable && (!self.stopped)) { OHHTTPStubsStreamTimingInfo timingInfo = { .slotTime = kSlotTime, .cumulativeChunkSize = 0 }; if(stubResponse.responseTime < 0) { timingInfo.chunkSizePerSlot = (fabs(stubResponse.responseTime) * 1000) * timingInfo.slotTime; } else if (stubResponse.responseTime < kSlotTime) { timingInfo.chunkSizePerSlot = stubResponse.dataSize; timingInfo.slotTime = stubResponse.responseTime; } else { timingInfo.chunkSizePerSlot = ((stubResponse.dataSize/stubResponse.responseTime) * timingInfo.slotTime); } [self streamDataForClient:client fromStream:stubResponse.inputStream timingInfo:timingInfo completion:completion]; } else { if (completion) completion(nil); } }
该方法将生成的
OHHTTPStubsStreamTimingInfo信息传入下一个实例方法
- streamDataForClient:fromStream:timingInfo:completion::
- (void)streamDataForClient:(id<NSURLProtocolClient>)client fromStream:(NSInputStream*)inputStream timingInfo:(OHHTTPStubsStreamTimingInfo)timingInfo completion:(void(^)(NSError * error))completion { if (inputStream.hasBytesAvailable && (!self.stopped)) { double cumulativeChunkSizeAfterRead = timingInfo.cumulativeChunkSize + timingInfo.chunkSizePerSlot; NSUInteger chunkSizeToRead = floor(cumulativeChunkSizeAfterRead) - floor(timingInfo.cumulativeChunkSize); timingInfo.cumulativeChunkSize = cumulativeChunkSizeAfterRead; if (chunkSizeToRead == 0) { [self executeOnClientRunLoopAfterDelay:timingInfo.slotTime block:^{ [self streamDataForClient:client fromStream:inputStream timingInfo:timingInfo completion:completion]; }]; } else { uint8_t* buffer = (uint8_t*)malloc(sizeof(uint8_t)*chunkSizeToRead); NSInteger bytesRead = [inputStream read:buffer maxLength:chunkSizeToRead]; if (bytesRead > 0) { NSData * data = [NSData dataWithBytes:buffer length:bytesRead]; [self executeOnClientRunLoopAfterDelay:((double)bytesRead / (double)chunkSizeToRead) * timingInfo.slotTime block:^{ [client URLProtocol:self didLoadData:data]; [self streamDataForClient:client fromStream:inputStream timingInfo:timingInfo completion:completion]; }]; } else { if (completion) completion(inputStream.streamError); } free(buffer); } } else { if (completion) completion(nil); } }
上述方法会先计算
chunkSizeToRead,也就是接下来要传递给
client的数据长度
从
NSInputStream中读取对应长度的数据
通过
- executeOnClientRunLoopAfterDelay:block:模拟数据传输的延时
使用
- URLProtocol:didLoadData:代理方法将数据传回
client
OHHTTPStubs 通过上面的两个方法很好的模拟了 HTTP 响应由于网络造成的延迟以及数据分块到达客户端的特点。
OHHTTPStubs
以及 OHHTTPStubsDescriptor 对 stub 的管理
OHHTTPStubs遵循单例模式,其主要作用就是提供便利的 API 并持有一个
OHHTTPStubsDescriptor数组,对
stub 进行管理。
OHHTTPStubs提供的类方法
+ stubRequestsPassingTest:withStubResponse:会添加一个
OHHTTPStubsDescriptor的实例到
OHHTTPStubsDescriptor数组中:
+ (id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock withStubResponse:(OHHTTPStubsResponseBlock)responseBlock { OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor stubDescriptorWithTestBlock:testBlock responseBlock:responseBlock]; [OHHTTPStubs.sharedInstance addStub:stub]; return stub; }
该类主要有两种方法,一种方法用于管理持有的 HTTP stub,比如说:
+ (BOOL)removeStub:(id<OHHTTPStubsDescriptor>)stubDesc
+ (void)removeAllStubs
- (void)addStub:(OHHTTPStubsDescriptor*)stubDesc
- (BOOL)removeStub:(id<OHHTTPStubsDescriptor>)stubDesc
- (void)removeAllStubs
这些方法都是用来操作单例持有的数组的,而另一种方法用来设置相应事件发生时的回调:
+ (void)onStubActivation:( nullable void(^)(NSURLRequest* request, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub) )block
+ (void)onStubRedirectResponse:( nullable void(^)(NSURLRequest* request, NSURLRequest* redirectRequest, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub) )block
+ (void)afterStubFinish:( nullable void(^)(NSURLRequest* request, id<OHHTTPStubsDescriptor> stub, OHHTTPStubsResponse* responseStub, NSError* error) )block
类中最重要的实例方法就是
- firstStubPassingTestForRequest:,它遍历自己持有的全部 stub,通过
testBlock的调用返回第一个符合条件的
stub:
- (OHHTTPStubsDescriptor*)firstStubPassingTestForRequest:(NSURLRequest*)request { OHHTTPStubsDescriptor* foundStub = nil; @synchronized(_stubDescriptors) { for(OHHTTPStubsDescriptor* stub in _stubDescriptors.reverseObjectEnumerator) { if (stub.testBlock(request)) { foundStub = stub; break; } } } return foundStub; }
相比之下
OHHTTPStubsDescriptor仅仅作为一个保存信息的类,其职能相对单一、实现相对简单:
@interface OHHTTPStubsDescriptor : NSObject <OHHTTPStubsDescriptor> @property(atomic, copy) OHHTTPStubsTestBlock testBlock; @property(atomic, copy) OHHTTPStubsResponseBlock responseBlock; @end @implementation OHHTTPStubsDescriptor + (instancetype)stubDescriptorWithTestBlock:(OHHTTPStubsTestBlock)testBlock responseBlock:(OHHTTPStubsResponseBlock)responseBlock { OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor new]; stub.testBlock = testBlock; stub.responseBlock = responseBlock; return stub; } @end
两个属性以及一个方法构成了
OHHTTPStubsDescriptor类的全部实现。
OHHTTPStubsResponse
伪造 HTTP 响应
OHHTTPStubsResponse类为请求提供了相应所需要的各种参数,HTTP 状态码、请求时间以及数据的输入流也就是用于模拟网络请求的
inputStream。
指定构造器
- initWithFileURL:statusCode:headers:完成了对这些参数的配置:
- (instancetype)initWithInputStream:(NSInputStream*)inputStream dataSize:(unsigned long long)dataSize statusCode:(int)statusCode headers:(nullable NSDictionary*)httpHeaders { if (self = [super init]) { _inputStream = inputStream; _dataSize = dataSize; _statusCode = statusCode; NSMutableDictionary * headers = [NSMutableDictionary dictionaryWithDictionary:httpHeaders]; static NSString *const ContentLengthHeader = @"Content-Length"; if (!headers[ContentLengthHeader]) { headers[ContentLengthHeader] = [NSString stringWithFormat:@"%llu",_dataSize]; } _httpHeaders = [NSDictionary dictionaryWithDictionary:headers]; } return self; }
同时,该类也提供了非常多的便利构造器以及类方法帮助我们实例化
OHHTTPStubsResponse,整个类中的所有构造方法大都会调用上述构造器;只是会传入不同的参数:
- (instancetype)initWithFileURL:(NSURL *)fileURL statusCode:(int)statusCode headers:(nullable NSDictionary *)httpHeaders { NSNumber *fileSize; NSError *error; const BOOL success __unused = [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:&error]; return [self initWithInputStream:[NSInputStream inputStreamWithURL:fileURL] dataSize:[fileSize unsignedLongLongValue] statusCode:statusCode headers:httpHeaders]; }
比如
- initWithFileURL:statusCode:headers:方法就会从文件中读取数据,然后构造一个数据输入流。
其他内容
使用 NSURLProtocol拦截 HTTP 请求时会有一个非常严重的问题,如果发出的是 POST 请求,请求的 body 会在到达 OHHTTPStubs 时被重置为空,也就是我们无法直接在
testBlock中获取其
HTTPBody;所以,我们只能通过通过方法调剂在设置
HTTPBody时,进行备份:
typedef void(*OHHHTTPStubsSetterIMP)(id, SEL, id); static OHHHTTPStubsSetterIMP orig_setHTTPBody; static void OHHTTPStubs_setHTTPBody(id self, SEL _cmd, NSData* HTTPBody) { if (HTTPBody) { [NSURLProtocol setProperty:HTTPBody forKey:OHHTTPStubs_HTTPBodyKey inRequest:self]; } orig_setHTTPBody(self, _cmd, HTTPBody); } @interface NSMutableURLRequest (HTTPBodyTesting) @end @implementation NSMutableURLRequest (HTTPBodyTesting) + (void)load { orig_setHTTPBody = (OHHHTTPStubsSetterIMP)OHHTTPStubsReplaceMethod(@selector(setHTTPBody:), (IMP)OHHTTPStubs_setHTTPBody, [NSMutableURLRequest class], NO); } @end
除了对于
HTTPBody的备份之外,OHHTTPStubs 还提供了一些用于从文件中获取数据的 C 函数:
NSString* __nullable OHPathForFile(NSString* fileName, Class inBundleForClass); NSString* __nullable OHPathForFileInBundle(NSString* fileName, NSBundle* bundle); NSString* __nullable OHPathForFileInDocumentsDir(NSString* fileName); NSBundle* __nullable OHResourceBundle(NSString* bundleBasename, Class inBundleForClass);
这些 C 语言函数能够帮助我们构造 HTTP 响应。
总结
如果阅读过上一篇文章中的内容,理解这里的实现原理也不是什么太大的问题。在需要使用到 HTTP mock 进行测试时,使用 OHHTTPStubs 还是很方便的,当然现在也有很多其他的 HTTP stub 框架,不过实现基本上都是基于 NSURLProtocol的。
相关文章推荐
- C语言解析pcap文件得到HTTP信息实例(原创,附源码)
- oschina 客户端源码解析 【转】http://blog.csdn.net/wangchun8926/article/category/1315637
- Go http源码解析(一)
- Spring-web源码解析之HttpRequestHandler
- tomcat http get 参数中文乱码,tomcat源码解析
- HTTP协议中的Tranfer-Encoding|HTTP,协议,Tranfer-Encoding,chunked编码,解析-中国源码网
- 使用AFNetworking, SDWebimage和OHHTTPStubs
- tinyxml源码解析(上)http://www.cnblogs.com/marchtea/archive/2012/11/09/2762669.html
- iOS单元测试:Specta + Expecta + OCMock + OHHTTPStubs + KIF
- 使用AFNetworking, SDWebimage和OHHTTPStubs
- Spring-web源码解析之Filter-HiddenHttpMethodFilter
- opencv源码解析之(6):hog源码分析 -http://www.cnblogs.com/tornadomeet
- 使用AFNetworking, SDWebimage和OHHTTPStubs
- C语言解析pcap文件得到HTTP信息实例(原创,附源码)
- OKHttp源码解析-ConnectionPool对Connection重用机制&Http/Https/SPDY协议选择
- C语言解析pcap文件得到HTTP信息实例(原创,附源码)
- Spring Security3源码分析(2)-http标签解析
- [nginx源码分析]配置解析(http作用域)
- DynamicLoadApk 源码解析 http://codekk.com/open-source-project-analysis
- 封装的不错的解析http 命令参数的c++ 源码