NSURLProtocol的使用
- 注册protocol。
Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");
[self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]];
以上方法适合于NSURLSession的网络请求,替换掉protocolClasses。如果使用的是NSURLConnection,则应使用下面的方法:
[NSURLProtocol registerClass:[GYMockURLProtocol class]];
- 实现protocol中必须实现的方法。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client;
- (void)startLoading;
- (void)stopLoading;
canInitWithRequest
是自己注册的URLProtocol类(比如此处的GYMockURLProtocol)的方法入口。只有这个方法返回YES时,才会执行下面的方法,返回NO时就直接运行系统的方法。
canonicalRequestForRequest
返回规范化的request。This method returns a canonical version of the given request
。一般直接返回传入的request,而且不要在这里做规范化的操作,否则可能出现反复调用等奇怪现象。
requestIsCacheEquivalent
请求是使用缓存还是发起新请求。直接返回NO,设置每次都发起新请求。
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client;
初始化方法,非必须实现。
startLoading
核心方法,真正的网络请求和mock方法这这个方法里面定义并执行。
stopLoading
网络请求完成后需要调用的方法。
GYHttpMock使用示例:
- 添加需要mock的地址
mockRequest(@"GET", @"https://httpbin.org/get").isUpdatePartResponseBody(YES);
- 发起网络请求
NSURLSession * session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error);
}else{
NSLog(@"%@",data);
}
}] resume];
第二步中发起的网络请求就会被GYHttpMock mock住。
注意:如果第一步中不添加isUpdatePartResponseBody(YES)
,则第二步中发起的网络请求就会被GYHttpMock mock住,不会真的发起网络请求,只是返回code码是200的请求头。当添加了isUpdatePartResponseBody(YES)
时才会发起网络请求。
关于GYHttpMock的具体API见微信团队的博客。
代码详解:
mockRequest
方法会初始化GYHttpMock,添加两个hook。依据传入的URL和方法名创建GYMockRequest
实例并保存。添加的两个hook是:GYNSURLConnectionHook,GYNSURLSessionHook。命名看起来是添加两个hook,其实是在完成上面说的方法注册和方法交换的功能。
当发起网络请求时,会先调用GYHttpMock
的canInitWithRequest
方法。此时会检查第一步中是否有该url对应的GYMockRequest
实例,如果有则返回YES,表明我们想要自己拦截请求或者修改response,GYHttpMock
的startLoading
等方法会被调用。如果返回NO就会把控制权交还给系统。
GYHttpMock
使用的是NSURLConnection
发起真正的网络请求。
无论是拦截请求,还是修改response,都应该主动调用
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
等URLProtocol
的方法,将最终数据返回给系统,系统会将数据传递给外部的网络请求者,比如AFNetworking或者NSURLSession的delegate。
GYHttpMock
还有一个值得一提的点是他使用了函数式编程的思想。GYMockRequestDSL
和GYMockResponseDSL
里面通过block实现了函数式编程。实现方法是定义blcok属性,在block中完成操作,并最终在block中将self返回给调用者。
举例如下:
typedef GYMockResponseDSL *(^ResponseWithHeadersMethod)(NSDictionary *);
@property (nonatomic, strong, readonly) ResponseWithHeaderMethod withHeader;
- (ResponseWithHeadersMethod)withHeaders; {
return ^(NSDictionary *headers) {
for (NSString *header in headers) {
NSString *value = [headers objectForKey:header];
[self.response setHeader:header value:value];
}
return self;
};
}
GYHttpMock
修改网络返回数据需要调用者清楚返回数据的结构,并预先定义好和该结构相符的字典才能完成修改。具体的修改过程是通过 递归,层级检查返回的json结构,在合适的地方添加新内容。调用的方法如下:
- (void)addEntriesFromDictionary:(NSDictionary *)dict to:(NSMutableDictionary *)targetDict