介绍
正常情况下当你需要写一个具备网络连接的iOS程序,你会想要一个本地的iOS接口能够接收网络上的所有数据。然而,在项目中总是有一些限制你可以实现的东西,而且有时候你可能想要为用户显示一个规整的页面。如果你打算采用这种方式,你最好确信网络接口尽可能流畅。你可以采取的措施之一是将图片的本地复本和其他非更新的资源包含到程序中。为了在一个远程加载的网页中使用本地资源,或者需要远程页面以某种方式参考本地资源(例如通过URL主题),或者需要用本地地址来代替远程地址。
在这个文章中,我将讲述当网页包含远程资源时如何用本地资源来替代。
NSURLCache
在Mac上,你可以在WebViewDelegate上使用一系列不同的方式来实现,包括实现webView:resource:willSendRequest:redirectResponse:fromDataSource来使得NSURLRequest代替另一个。不幸的是,iOS中的UIWebViewDelegate并不如此好用因此我们需要以另外的方式来实现。幸运的是,还有一点你可以利用:就是NSURLCache在几乎每个请求下都会被调用。
正常情况下,只有很少的数据存储在NSURLCache中,特别是在更旧的iOS设备上,这个存储区很小。即使你利用setMemoryCapacity:函数来增加这个缓存的大小,它相对于Mac上的NSURLCache来说还是太小了以至于不能存储资源。
当然在这个例子中那不是问题,因为我们将会子类化NSURLCache并且实现自定义的版本,该版本将保证可以存储我们所需的资源而且不需要pre-caching(在程序运行之前所有的资源都要保证准备在存储去内)。
cachedResponseForRequest:
唯一一个我们需要重写的函数是cachedResponseForRequest:,这能够允许我们在它发送前查看每一个请求而且如果我们需要的话返回本地数据。在这个代码中,我会使用词典来将远程URL映射为在本地程序相关库中的资源的文件名。如果一个请求是指向特定的URL,那么将返回本地文件内容。
下面给出了这个词典。
- (NSDictionary *)substitutionPaths
{
return [NSDictionary dictionaryWithObjectsAndKeys:@"fakeGlobalNavBG.png",
@"http://images.apple.com/global/nav/images/globalnavbg.png",
nil];
}
只要针对URL:http://image.apple.com/global/nav/images/globalnavbg.png请求发出,那么下面的cachedResponseForRequest:可以利用资源文件夹中的fakeGlobalNavBG.png文件来代替。
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
// Get the path for the request
NSString *pathString = [[request URL] absoluteString];
// 判断我们是否为这个路径提供了替代资源
NSString *substitutionFileName = [[self substitutionPaths] objectForKey:pathString];
if (!substitutionFileName)
{
// 没有替代资源,返回默认值
return [super cachedResponseForRequest:request];
}
// 如果我们已经创建了一个缓存实例,那么返回它
NSCachedURLResponse *cachedResponse = [cachedResponses objectForKey:pathString];
if (cachedResponse)
{
return cachedResponse;
}
// 获得替代文件的路径
NSString *substitutionFilePath =
[[NSBundle mainBundle] pathForResource:[substitutionFileName stringByDeletingPathExtension]
ofType:[substitutionFileName pathExtension]];
NSAssert(substitutionFilePath, @"File %@ in substitutionPaths didn't exist", substitutionFileName);
// 加载替代数据
NSData *data = [NSData dataWithContentsOfFile:substitutionFilePath];
// 创建可缓存的响应
NSURLResponse *response = [[[NSURLResponse alloc] initWithURL:[request URL]
MIMEType:[self mimeTypeForPath:pathString]
expectedContentLength:[data length]
textEncodingName:nil] autorelease];
cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:response data:data] autorelease];
// 为后续响应,把它加入我们的响应词典中
if (!cachedResponses)
{
cachedResponses = [[NSMutableDictionary alloc] init];
}
[cachedResponses setObject:cachedResponse forKey:pathString];
return cachedResponse;
}
设置我们的缓存区作为共享缓存
一个UIWebView试图使用当前的+[NSURLCache sharedURLCache]。为了调用我的代码,你需要创建一个NSURLCache的子类并且调用+[NSURLCache setSharedURLCache:]。这里需要注意:一旦你设置新的网络缓存,你可能打算保持它工作直到你的程序退出。
当UIWebView向你的NSURLCache请求资源时,它假设NSURLCache具备NSCachedURLResponse。如果当UIWebView正在使用它的时候你释放了NSCachedURLResponse,有可能你的程序会崩溃。不幸的是,迫使WebKit释放它的参考(references)—在某些例子里它何时释放是不确定的。只有WebKit去调用removeCachedResponseForRequest:的时候它才通知你可以丢弃那些资源。
这意味着你必须保证程序中只有一个NSURLCache,在application:didFinishLaunchingWithOptions方法中进行设置并且不要移去它。
一个限制
显然地,如果你设置了要用来存储本地数据的缓存区,只有一个查看缓存区的请求才是使其生效。
这意味这如果URL请求是requestWithURL:cachePolicy:timeoutInterval:,缓存策略是NSURLRequestReloadIgnoringCacheData,那么这个请求将忽略本地替代。
默认情况下,NSURLRequests的缓存策略是NSURLRequestUseProtocolCachePolicy。这个HTTP的缓存策略是相当复杂的而且我从来没有见过一个正常的NSURLRequest忽视缓存,这些规则可能会在某些情况下产生它忽视缓存的情况。如果这些情况发生的话,你的程序应该保持正常工作。
本地替代缓存示例程序:LocalSubstitutionCache.zip
下面是程序截图:
利用我们的NSURLCache子类调用了后,顶部灰色链接栏上的灰色链接按钮被在本地资源文件中的蓝色图像所代替。
结论:
这个工作的意图是允许UIWebView响应更灵敏而且更像本地用户界面。
事实上,UIWebView决不会具有本地用户界面那样的集成度和灵敏的响应。但是
使得本地存储尽可能多的资源有助于尽可能少的带给用户不好的体验。