首页 > 代码库 > NSURLProtocol

NSURLProtocol

由于UIWebView无法实现离线缓存,因此想利用Archieve机制来实现文件形式的离线缓存机制。同时,由于NSURLRequest每一次对链接的请求,都将触发NSURLProtocol的回调,因此对NSURLProtocol合理应用可以很好的达到离线缓存的目的。

一、NSURLProtocol与NSURLProtocolClient简介:

    首先,我先介绍一下NSURLProtocol与NSURLProtocolClient:

    NSURLProtocol是一组方法,其中苹果文档是这样描述的:

 

    NSURLProtocol is an abstract class which provides the basic structure for performing protocol-specific loading of URL data.

 

    它是一个抽象类,为载入URL的data的一些特定协议提供基础的结构。要实现它里面的函数就必须继承它,因此小Potti将在后面创建一个MWURLProtocol类继承它,并实现它其中的一系列函数。

    而NSURLProtocol其中有个成员就是NSURLProtocolClient的一个实例。因为NSURLProtocol是由一系列的回调函数构成的(注册函数除外),而要对URL的data进行各种操作时就到了调用NSURLProtocolClient实例的时候了,这就实现了一个钩子,去操作URL data。

   NSURLProtocol有以下一系列的回调方法:

 

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client;

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

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

- (void)startLoading;

- (void)stopLoading;

   其中canInitWithRequest是询问是否处理该请求的回调,如果不处理则后面所有函数都不会再调用。startLoading和stopLoading是分别对于loading开始从网页上抓取数据,从网页上抓取完数据的回调。其中startLoading称为我们可以重点利用的函数。

  NSURLProtocolClient主要有以下方法:

 

- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;

 

- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;

 

- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;

 

- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;

 

- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;

 

- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;

 

- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

 

- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

 

   其中wasRedirectedToRequest是重定向函数,cachedResponseIsValid是对cached的操作,didReceiveResponse是受到Response时的调用处理函数, didLoadData是load完数据时的调用,而后面的大致也与函数题目意思一样。而这些函数有个好处就是通通只需要我们调用它,系统就会做对应的事情,重载它也可以,不过一般不用这么麻烦。

   看到这些函数是不是想到了NSURLConnectionDataDelegate中的回调呢?哈哈,其实小Potti将在后面对2者有一个很好的结合。

二、离线缓存算法的流程简介:

   1.向NSURLProtocol注册MWURLProtocol;

   2.系统回调canInitWithRequest和canonicalRequestForRequest;其中,为了防止后续startLoading中可能存在的死循环,因此在canInitWithRequest我们会检查是否是已经处理过的request。如果不是则返回YES,否则返回NO。
   3.系统回调startLoading;如果当前网络不可用,直接读取文件中的data,request,response,构建一个假的请求并直接调用NSURLProtocolClient中的处理函数进行处理。结束。

若网络可用,则创建一个NSURLConnection,将处理的request与这个connection钩起来,同时实现NSConnectionDataDelegate的回调,这样,我们就将NSURLConnection与NSURLProtocol钩起来。钩起来后会执行4:

   4.在statLoading调用之后系统将回调NSConnectionDataDelegate中的各个回调函数,在回调函数中,我们在各个位置分别调用合适的NSURLProtocolClient的方法,同时到下载完成后,我们就将下载到的Data,request等信息存储起来。

三、离线缓存算法源代码:

 

@interface NSURLRequest(MutableCopyWorkaround)

- (id) mutableCopyWorkaround;

@end

 

@implementation MWURLProtocol

static NSString *RNCachingURLHeader = @"PottiTest";

 

 

@synthesize connection = connection_;

@synthesize data =http://www.mamicode.com/ data_;

@synthesize response = response_;

 

 

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

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

        ([request valueForHTTPHeaderField:RNCachingURLHeader] == nil)) {

        return YES;

    }

    NSLog(@"%@",[request allHTTPHeaderFields]);

    returnNO;

}

 

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

{

    return request;

}

 

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

{

    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

    return [cachesPath stringByAppendingPathComponent:[NSStringstringWithFormat:@"%x", [[[aRequestURL] absoluteString] hash]]];

}

 

- (void)startLoading

{

    if([netWorkReachabilityconnectedToNetWork])

    {

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

        NSLog(@"%@",[connectionRequest HTTPBody]);

        NSLog(@"%@",[connectionRequest URL]);

        [connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];

        NSLog(@"%@",[connectionRequest allHTTPHeaderFields]);

        NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest

                                                                    delegate:self];

        [self setConnection:connection];

    }

    else

    {

        MWCacheData *cacheData=http://www.mamicode.com/[NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];

        if(cacheData)

        {

            NSData *data = http://www.mamicode.com/[cacheData data];

            NSURLResponse *response = [cacheData response];

            NSURLRequest *redirectRequest = [cacheData request];

            if(redirectRequest)

            {

                [[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectRequest redirectResponse:response];

            }

            else

            {

                [[selfclient] URLProtocol:selfdidReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves.

                [[self client] URLProtocol:self didLoadData:data];

                [[selfclient] URLProtocolDidFinishLoading:self];

            }

        }

        else

        {

                [[selfclient] URLProtocol:selfdidFailWithError:[NSErrorerrorWithDomain:NSURLErrorDomaincode:NSURLErrorCannotConnectToHostuserInfo:nil]];

        }

    }

    

}

 

- (void)stopLoading

{

    [[self connection] cancel];

}

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response

{

    if(response!=nil)

    {

        NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround];

        [redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader];

        

        MWCacheData *cacheData = http://www.mamicode.com/[MWCacheData new];

        [cacheData setData:[self data]];

        [cacheData setResponse:response];

        [cacheData setRequest:redirectableRequest];

        [NSKeyedArchiver archiveRootObject:cacheData toFile:[self cachePathForRequest:[self request]]];

        

        [[selfclient] URLProtocol:selfwasRedirectedToRequest:redirectableRequest redirectResponse:response];

        return redirectableRequest ;

    }

    return request;

}

 

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

{

    [[selfclient] URLProtocol:selfdidFailWithError:error];

    [selfsetResponse:nil];

    [self setData:nil];

    [selfsetConnection:nil];

}

 

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

{

    NSLog(@"didReceiveResponse");

    [self setResponse:response];

    [[selfclient] URLProtocol:selfdidReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];

 

}

 

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)adata

{

    NSLog(@"didReceiveData");

    [[selfclient] URLProtocol:selfdidLoadData:adata];

    if([self data] == nil) [self setData:[NSMutableData dataWithData: adata]];

    else [[self data] appendData:adata];

    

}

 

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

{

    NSLog(@"willCacheResponse");

    return cachedResponse;

}

 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

{

    NSLog(@"connectionDidFinishLoading");

    [[selfclient] URLProtocolDidFinishLoading:self];

    MWCacheData *cacheData = http://www.mamicode.com/[MWCacheData new];

    [cacheData setData:[self data]];

    [cacheData setResponse:[self response]];

    [NSKeyedArchiverarchiveRootObject:cacheData toFile:[selfcachePathForRequest:[selfrequest]]];

    

    [self setData:nil];

    [selfsetConnection:nil];

    [selfsetResponse:nil];

}

 

@end

 

 

@implementation NSURLRequest(MutableCopyWorkaround)

 

- (id) mutableCopyWorkaround {

    NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequestalloc] initWithURL:[selfURL]

                                                                          cachePolicy:[self cachePolicy]

                                                                      timeoutInterval:[self timeoutInterval]];

    

    [mutableURLRequest setAllHTTPHeaderFields:[selfallHTTPHeaderFields]];

    NSLog(@"%@",[mutableURLRequest allHTTPHeaderFields]);

    NSLog(@"%@",[mutableURLRequest valueForHTTPHeaderField:RNCachingURLHeader]);

    NSLog(@"%@",[mutableURLRequest HTTPBody]);

    NSLog(@"%@",[mutableURLRequest HTTPMethod]);

    return mutableURLRequest;

}

 

@end

 

@implementation MWCacheData

@synthesize response=response_;

@synthesize request=request_;

@synthesize data=http://www.mamicode.com/data_;

 

-(id) initWithCoder:(NSCoder *) aDecoder

{

    self = [super init];

    if(!self) returnnil;

    [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