NSURLProtocol以及GYHttpMock学习记录

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)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)client;初始化方法,非必须实现。

startLoading核心方法,真正的网络请求和mock方法这这个方法里面定义并执行。

stopLoading网络请求完成后需要调用的方法。

GYHttpMock使用示例:

  1. 添加需要mock的地址mockRequest(@"GET", @"https://httpbin.org/get").isUpdatePartResponseBody(YES);
  2. 发起网络请求
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,其实是在完成上面说的方法注册和方法交换的功能。

当发起网络请求时,会先调用GYHttpMockcanInitWithRequest方法。此时会检查第一步中是否有该url对应的GYMockRequest实例,如果有则返回YES,表明我们想要自己拦截请求或者修改response,GYHttpMockstartLoading等方法会被调用。如果返回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还有一个值得一提的点是他使用了函数式编程的思想。GYMockRequestDSLGYMockResponseDSL里面通过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

你可能感兴趣的:(NSURLProtocol以及GYHttpMock学习记录)