【iOS】通过NSURLProtocol提高Web加载速度

一.项目需求

项目中有个海报功能,是用UIWebView加载h5网页的形式。因为海报的使用率比较高,如果网页加载得比较慢会严重影响用户体验,因此我们想了一个方法,在用户启动APP后,如果连接了Wi-Fi,就将一些css和图片资源,先下载到本地,在加载网页内容时,优先使用本地的css和图片,以加快网页载入速度。

实现原理:
拦截所有Http请求,并伪造Http请求。我们只需要写一个继承自 NSURLProtocol 的类并注册。在Http请求开始时,系统就会通过此类对请求进行处理。

二.Demo:百度首页的logo替换为本地图片

下面demo实现:加载百度的首页,百度logo图片改用本地提前下载好的图片代替。

(一) 注册JXURLProtocol类
  1. 新建JXURLProtocol类,继承自NSURLProtocol
@interface JXURLProtocol : NSURLProtocol

@end
  1. 在加载网页前,注册JXURLProtocol类:
	NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLProtocol registerClass:[JXURLProtocol class]];
    [self.webView loadRequest:request];
(二 )拦截请求
  1. 在JXURLProtocol.m中实现canInitWithRequest:方法,所有请求都会先调用此方法,返回NO表示自己需要通过自己处理,正常发出请求,否则不发出请求,因为需要经过JXURLProtocol处理。
    我们需要拦截百度图片url的请求,即在canInitWithRequest:方法中,根据url判断,如果是百度logo图的url(https://m.baidu.com/static/index/plus/plus_logo.png), 返回YES,表示先不发出请求而是自己处理:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    
    NSString *urlString = request.URL.absoluteString;    
    //此处防止重复拦截(与后面的三(3)对应)
    if ([NSURLProtocol propertyForKey:@"JXProtocol" inRequest:request]) {
        return NO;
    }
    
    if ([urlString isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo.png"]) {
        return YES;
    }
    return NO;
}
  1. 上一步返回YES后,会继续执行方法:
- (void)startLoading{}
(三) 返回本地图片
  1. 根据请求的url获取本地对应的图片:
- (NSData*)imageDataWithUrl:(NSURL*)url{
    if ([url.absoluteString isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo.png"]) {
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"long" ofType:@"png"];
        NSData *data = [NSData dataWithContentsOfFile:filePath];
        return data;
    }
    return nil;
}
  1. 尝试获取本地图片,获取成功后,构建响应头,回调请求成功。
- (void)startLoading{

    NSData *imageData = [self imageDataWithUrl:self.request.URL];

    if (imageData) {
        //获取本地图片成功

        //构建请求头
        NSString *mimeType = @"image/jpeg";
        
        NSMutableDictionary *header = [NSMutableDictionary dictionary];
        
        NSString *contentType = [mimeType stringByAppendingString:@";chartset=UTF-8"];
        header[@"Content-type"] = contentType;
        header[@"Content-Length"] = [NSString stringWithFormat:@"%ld",imageData.length];
        
        NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL
                                                                  statusCode:200 HTTPVersion:@"1.1" headerFields:header];
        //回调
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
        [self.client URLProtocol:self didLoadData:imageData];
        [self.client URLProtocolDidFinishLoading:self];
    }else{
        //获取本地图片失败
    }
}
  1. 如果获取本地图片失败,设置标志防止重复拦截,并重新构建请求来请求线上的资源:
- (void)startLoading{
    NSData *imageData = [self imageDataWithUrl:self.request.URL];
    if (imageData) {
        //获取本地图片成功
        //...
    }else{
        [NSURLProtocol setProperty:@(YES) forKey:@"JXProtocol" inRequest:self.request];
        NSMutableURLRequest *newRequset = [self.request mutableCopy];
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        NSURLSessionTask *task = [session dataTaskWithRequest:newRequset];
        [task resume];
    }
}
  1. 最后补上实现必须的方法:
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

- (void)stopLoading{
}


#pragma mark- NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    
    [self.client URLProtocol:self didFailWithError:error];
}

#pragma mark - NSURLConnectionDataDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
}
  1. 运行APP,百度的logo已经换成本地的long.png,小恐龙了:
    这里写图片描述

完整代码:https://github.com/dolacmeng/NSURLProtocolDemo

参考资料:
https://developer.apple.com/documentation/foundation/url_loading_system
https://github.com/draveness/analyze/blob/master/contents/OHHTTPStubs/iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求.md

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值