1. 程式人生 > >使用NSURLProtocol實現離線快取

使用NSURLProtocol實現離線快取

一、說明:

NSURLProtocol可以攔截任何網路請求,包含UIWebView中發出的所有請求。但是在WKWebView中,只能攔截到最初始的請求,內嵌的資源下載攔截不到。比如通過WKWebView載入"http://www.baidu.com",則只能攔截到"http://www.baidu.com",網頁內部的資源載入攔截不到。頁面跳轉屬於最初始請求之內,可以攔截到。

二、建立NSURLProtocol的子類,通過下面的程式碼註冊此協議類:

[NSURLProtocolregisterClass:[MyURLProtocolclass]];

三、下面是此子類的程式碼:

#import "MyURLProtocol.h"

#define MyURLProtocolHandled @"MyURLProtocolHandled"

//建立archive資料模型,重寫編碼解碼協議

@interface MyCacheData : NSObject

@property(nonatomic,strong) NSURLRequest *request;

@property(nonatomic,strong) NSURLResponse *response;

@property(nonatomic,strong) NSData *data;

@end

@interface NSURLRequest (MutableCopyWorkaround)

- (id)mutableCopyWorkaround;

@end

@interfaceMyURLProtocol ()

@property(nonatomic,strong) NSURLConnection *connection;

@property(nonatomic,strong) NSMutableData *httpData;

@property(nonatomic,strong) NSURLResponse *response;

@end

@implementation MyURLProtocol

#pragma mark - 重寫NSURLProtocol子類方法

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

//如果此請求是攔截到請求之後,接管請求而發起的新請求,則不處理。

    if ([request.URL.scheme isEqualToString:@"http"] &&

        [request valueForHTTPHeaderField:MyURLProtocolHandled] == nil)

    {

        return YES;

    }

returnNO;

}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

{

    return request;

}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a

                       toRequest:(NSURLRequest *)b

{

return [superrequestIsCacheEquivalent:a toRequest:b];

}

- (void)startLoading

{

//如果發現已經存在此請求的快取資料,則返回快取資料,否則發起新的請求從服務求載入資料

MyCacheData *cacheData = [NSKeyedUnarchiverunarchiveObjectWithFile:

                              [self cachePathForRequest:self.request]];

    if(cacheData != nil)

    {

        NSData *data = cacheData.data;

        NSURLResponse *response = cacheData.response;

        NSURLRequest *redirectRequest = cacheData.request;

//使用NSURLProtocolClient做請求轉向,直接將請求和資料轉發到之前的請求

        if(redirectRequest != nil)

        {

            [[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectRequest

                      redirectResponse:response];

        }

        else

        {

            [[selfclient] URLProtocol:selfdidReceiveResponse:response

cacheStoragePolicy:NSURLCacheStorageNotAllowed];

            [[selfclient] URLProtocol:selfdidLoadData:data];

            [[selfclient] URLProtocolDidFinishLoading:self];

        }

    }

    else

    {

//接管此網路請求,發起一個新的請求,後續會將新請求拿到的資料交給之前的舊請求

        NSMutableURLRequest *connectionRequest = [[self request] mutableCopyWorkaround];

//增加標記,標示是由我們接管而發出的請求

        [connectionRequest setValue:@"Tag" forHTTPHeaderField:MyURLProtocolHandled];

        self.connection = [NSURLConnection connectionWithRequest:connectionRequest

                                                        delegate:self];

    }

}

- (void)stopLoading

{

    [self.connectioncancel];

self.connection = nil;

}

#pragma mark - 網路請求代理

- (NSURLRequest *)connection:(NSURLConnection *)connection

             willSendRequest:(NSURLRequest *)request

            redirectResponse:(NSURLResponse *)response

{

    if(response != nil)

    {

        NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround];

        //快取資料

        MyCacheData *cacheData = [MyCacheData new];

        [cacheData setData:self.httpData];

        [cacheData setResponse:response];

        [cacheData setRequest:redirectableRequest];

        [NSKeyedArchiver archiveRootObject:cacheData

                                    toFile:[self cachePathForRequest:[self request]]];

//將請求和快取的響應資料轉向到之前的請求

        [[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectableRequest

                  redirectResponse:response];

        return redirectableRequest ;

    }

    return request;

}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection

{

returnYES;

}

- (void)connection:(NSURLConnection *)connection

didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

{

    [self.clientURLProtocol:selfdidReceiveAuthenticationChallenge:challenge];

}

- (void)connection:(NSURLConnection *)connection

didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

{

    [self.clientURLProtocol:selfdidCancelAuthenticationChallenge:challenge];

}

- (void)connection:(NSURLConnection *)connection

didReceiveResponse:(NSURLResponse *)response

{

//儲存響應物件

    self.response = response;

    [self.clientURLProtocol:selfdidReceiveResponse:response

cacheStoragePolicy:NSURLCacheStorageNotAllowed];

}

- (void)connection:(NSURLConnection *)connection

    didReceiveData:(NSData *)data

{

    [self.clientURLProtocol:selfdidLoadData:data];

//儲存伺服器返回的資料

    if(self.httpData == nil) {

        self.httpData = [NSMutableData dataWithData: data];

    }

    else

    {

        [self.httpData appendData:data];

    }

}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection

                  willCacheResponse:(NSCachedURLResponse *)cachedResponse

{

    return cachedResponse;

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

{

    [self.clientURLProtocolDidFinishLoading:self];

//請求載入完畢之後,將資料快取

    MyCacheData *cacheData = [MyCacheData new];

    [cacheData setData:self.httpData];

    [cacheData setResponse:self.response];

    [NSKeyedArchiverarchiveRootObject:cacheData

                                toFile:[self cachePathForRequest:self.request]];

self.connection = nil;

    self.httpData = nil;

    self.response = nil;

}

- (void)connection:(NSURLConnection *)connection

  didFailWithError:(NSError *)error

{

    [self.clientURLProtocol:selfdidFailWithError:error];

self.connection = nil;

    self.httpData = nil;

    self.response = nil;

}

#pragma mark - 為請求建立快取路徑

- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest

{

NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

                                                  NSUserDomainMask, YES) lastObject];

return [cachesPath stringByAppendingPathComponent:

            [NSString stringWithFormat:@"%ld", [[[aRequest URL] absoluteString] hash]]];

}

@end

@implementation NSURLRequest (MutableCopyWorkaround)

- (id) mutableCopyWorkaround {

NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequestalloc]

                                              initWithURL:[self URL]

                                              cachePolicy:[self cachePolicy]

                                              timeoutInterval:[self timeoutInterval]];

    [mutableURLRequest setAllHTTPHeaderFields:[selfallHTTPHeaderFields]];

    return mutableURLRequest;

}

@end

@implementation MyCacheData

-(id) initWithCoder:(NSCoder *) aDecoder

{

    self = [super init];

    if(!self) {

        return nil;

    }

    [self setData:[aDecoder decodeObjectForKey:@"data"]];

    [self setRequest:[aDecoder decodeObjectForKey:@"request"]];

    [self setResponse:[aDecoder decodeObjectForKey:@"response"]];

returnself;

}

- (void)encodeWithCoder:(NSCoder *)aCoder

{

    [aCoder encodeObject:[self data] forKey:@"data"];

    [aCoder encodeObject:[self request] forKey:@"request"];

    [aCoder encodeObject:[self response] forKey:@"response"];

}

@end

部分程式碼轉自:http://tanlimin201.blog.163.com/blog/static/38171407201383032914736/