iOS-利用本地数据来代替远程UIWebView请求
在这篇文章中,我将讲述如何在iOS中的UIWebView中加载一个网页,使用修正的NSURLCache来用本地网页资源复本来代替基于远程网页的数据复本。
介绍
正常情况下当你需要写一个具备网络连接的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,那么将返回本地文件内容。
下面给出了这个词典。
12345678 |
-(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文件来代替。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 |
-(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request{// Get the path for the requestNSString*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决不会具有本地用户界面那样的集成度和灵敏的响应。但是
使得本地存储尽可能多的资源有助于尽可能少的带给用户不好的体验。
原文作者:Matt Gallagher
原文链接:http://cocoawithlove.com/2010/09/substituting-local-data-for-remote.html
Substituting local data for remote UIWebView requests
In this post, I’ll show you how you can load a webpage in a UIWebView in iOS while using a modified NSURLCache to substitute local copies of resources within the webpage for the remote copies referred to by the actual page.
Introduction
Normally if you’re writing an iOS app with network connectivity, you’ll want to put a native iOS interface on all data received over the network.
However, there are always scheduling and other constraints on a project that limit what you can implement and sometimes you may simply choose to show a regular, webpage to the user.
If you choose to take this approach, it is best to make sure the web interface feels as smooth as possible. One of the steps you can take to ensure this is to include local copies of all image and other non-updating resources within the application itself.
To use a local resource in an iOS webpage loaded from a remote location, either the remote page must refer to the local resource in some way (e.g. through a custom URL scheme) or you must swap a local location in place of a remote locations.
In this post, I’ll look at how we can substitute a local resource when the webpage contains references to remote resources.
NSURLCache
On the Mac, you could use a range of different approaches in the WebViewDelegate to do this, including implementing webView:resource:willSendRequest:redirectResponse:fromDataSource: to substitute one NSURLRequest for another. Unfortunately, the UIWebViewDelegate in iOS is not nearly as capable so we need to do this another way.
Fortunately, there is one point you can hook into that is invoked for (almost) every request: the NSURLCache.
Normally, very little is actually cached in the NSURLCache, particularly on older iOS devices where the cache size is downright miniscule. Even if you use the setMemoryCapacity: method to increase the size of the cache, it seems significantly less likely to store resources than the NSURLCache on the Mac.
Of course that doesn’t matter in this case, since we’re going to subclass NSURLCache and implement our own version that will be guaranteed to hold all the resources we need and won’t need pre-caching (all the resources will be there before the program is started).
cachedResponseForRequest:
The only important method we need to override is cachedResponseForRequest:. This will allow us to examine every request before it is sent and return local data if we prefer.
For this code, I’ll use a dictionary that maps remote URLs to local file names in the Resources folder of the application bundle. If any request is made for the specified URLs, the contents of the local file will be returned instead.
So given the following dictionary containing a single path for substitution
12345678 |
-(NSDictionary*)substitutionPaths{return[NSDictionary dictionaryWithObjectsAndKeys:@"fakeGlobalNavBG.png", @"http://images.apple.com/global/nav/images/globalnavbg.png", nil];} |
The following cachedResponseForRequest: implementation will substitute the contents of the fakeGlobalNavBG.png file in the Resources folder any time the URL http://images.apple.com/global/nav/images/globalnavbg.png is requested
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 |
-(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request{// Get the path for the requestNSString*pathString =[[request URL] absoluteString]; // See if we have a substitution file for this pathNSString*substitutionFileName =[[self substitutionPaths] objectForKey:pathString]; if(!substitutionFileName){// No substitution file, return the default cache responsereturn[super cachedResponseForRequest:request]; } // If we've already created a cache entry for this path, then return it.NSCachedURLResponse*cachedResponse =[cachedResponses objectForKey:pathString]; if(cachedResponse){return cachedResponse; } // Get the path to the substitution fileNSString*substitutionFilePath =[[NSBundle mainBundle] pathForResource:[substitutionFileName stringByDeletingPathExtension] ofType:[substitutionFileName pathExtension]]; NSAssert(substitutionFilePath, @"File %@ in substitutionPaths didn't exist", substitutionFileName); // Load the dataNSData*data =[NSData dataWithContentsOfFile:substitutionFilePath]; // Create the cacheable responseNSURLResponse*response =[[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:[self mimeTypeForPath:pathString] expectedContentLength:[data length] textEncodingName:nil] autorelease]; cachedResponse =[[[NSCachedURLResponse alloc] initWithResponse:response data:data] autorelease]; // Add it to our cache dictionary for subsequent responsesif(!cachedResponses){ cachedResponses =[[NSMutableDictionary alloc] init]; }[cachedResponses setObject:cachedResponse forKey:pathString]; return cachedResponse;} |
Setting our cache as the shared cache
AUIWebView will try to use the current +[NSURLCache sharedURLCache]. To get our code called, you’ll need to create an instance of our NSURLCache subclass and invoke +[NSURLCache setSharedURLCache:].
A big warning here: once you set a new web cache, you probably want to leave it set until your program exits.
When the UIWebView requests resources from your NSURLCache, it assumes that the NSURLCache retains the NSCachedURLResponse. If you release the NSCachedURLResponse while any UIWebView is using it, it will probably crash your app.
Unfortunately, it is pretty hard to force WebKit to let go of its references — it can hold onto them indefinitely in some cases. Until WebKit itself chooses to invoke removeCachedResponseForRequest: to tell you that you can throw away the resource you must hold onto it.
What this means is that you should only have one NSURLCache in your program. Set it in your application:didFinishLaunchingWithOptions: method and never remove it.
A limitation…
Obviously, if you’re overriding the cache to substitute local data, it will only work if the request actually looks at the cache.
This means that if the URL is requested with requestWithURL:cachePolicy:timeoutInterval: with a cache policy of NSURLRequestReloadIgnoringCacheData, the the request will bypass this local substitution.
By default, NSURLRequests have a cache policy of NSURLRequestUseProtocolCachePolicy. The HTTP cache policy is pretty complicated and while I’ve never actually seen a normal NSURLRequest actually bypass the cache, the number of rules involved create a situation where it seems like it may be possible in some situations. Your app should not misbehave if this were to happen for some reason.
The LocalSubstitutionCache sample app
You can download theLocalSubstitutionCache.zip(66kb) sample project
Here’s a small screenshot of today’shttp://www.apple.comrunning in a UIWebView
After invoking +[NSURLCache setSharedURLCache:] with our NSURLCache subclass, the gray links bar across the top are replaced with a blue graphic stored in the app’s bundle
Conclusion
The purpose of this work is to allow UIWebViews to feel more responsive and a bit more like native user-interfaces.
In reality, a UIWebView will never feel as responsive or integrated as a native user-interface but sometimes making one screen of your app a remote webpage is a big enough saving in developer resources that you’re prepared to make the sacrifice in user quality. Making sure as many resources as possible are stored locally will help make any negative impact on user quality as minor as possible.
相关推荐
为了解决这个问题,开发者可以利用本地数据来代替远程UIWebView请求,这样即使在网络不可用时也能提供内容。这个"iphone 利用本地数据来代替远程UIWebView请求实例"就是针对这一需求的解决方案。 首先,我们要理解...
在iOS开发中,为了优化用户体验和减少网络流量,开发者经常需要缓存网络请求的数据,特别是对于UIWebView的加载内容。`NSURLCache`是苹果提供的一个关键类,它允许我们在本地存储HTTP和HTTPS请求的响应,以便在后续...
“UIWebView-WKWebView-js 交互”可能包含如何在UIWebView和WKWebView中执行JavaScript,以及如何将JavaScript的返回值传递给原生代码的示例。这些示例可能涵盖了JavaScript到原生的事件通知、数据传递,以及如何...
以上就是利用NJKWebViewProgress库为UIWebView添加加载进度条的基本步骤。通过这个库,你可以为你的iOS应用提供更佳的用户体验,让用户知道网页正在加载,减少他们的等待焦虑感。同时,NJKWebViewProgress的使用也...
总结,"ios-UIWebView简易浏览器"项目展示了如何利用UIWebView控件构建一个简单的移动Web浏览器。通过学习这个项目,开发者可以了解UIWebView的基本使用、网页加载流程、交互回调以及如何实现浏览器的基本功能。同时...
在iOS开发中,UIWebView是用于展示网页内容的重要组件,然而在处理大量数据或网络状况不稳定的情况下,如何实现UIWebView的缓存并确保信息的实时性是一个常见的挑战。本篇文章将深入探讨这个问题,并提供一种解决...
1. **UIWebView与WKWebView**: 早期的iOS应用中,开发者常使用UIWebView来实现富文本编辑,因为它可以渲染HTML内容。然而,UIWebView已不再推荐,因为它的性能较差且消耗较多资源。现在,WKWebView已成为首选,它...
基于NSURLSession NSURLProtol的UIWebView离线缓存 主要是使用苹果的黑魔法类:NSURLProtocol来对网络请求进行拦截,拦截后使用自定义的网络去加载数据后进行离线缓存。这样保证在没有网络的情况下,也能保证离线能...
WebView与JavaScript的交互是iOS应用与Web技术融合的关键,它使得原生应用能够利用Web技术来增强用户体验,同时也让网页能够调用iOS原生功能。本资料“ios-webView与js的交互.zip”主要探讨了如何在iOS应用中实现...
这个"移动应用开发-iOS开发示例教程之UIWebview.zip"资源可能包含了一系列关于如何使用UIWebView进行移动应用开发的教学材料。下面将详细阐述UIWebView的主要功能、使用方法及其在iOS开发中的应用。 UIWebView是...
在iOS开发中,UIWebView是苹果提供的一种用于加载和展示网页内容的组件。然而,UIWebView在加载网页的过程中,没有内置的loading提示效果,这可能导致用户在等待网页加载时感到困惑或不耐烦。因此,自定义一个加载...
本项目“ios-利用JavaScriptCore实现简单H5交互.zip”主要关注如何通过JavaScriptCore来实现iOS应用(通常是一个UIWebView)与HTML5(H5)页面的双向通信,以实现更丰富的用户体验和功能扩展。 首先,UIWebView是...
iOS WKWebView UIWebView “ WKWebView对象显示交互式Web内容,例如用于应用程序内浏览器的内容。 对于新开发,请使用此类而不是较早的UIWebView类。” 初始化Web视图 配置 WKWebViewConfiguration对象是用于初始...
在iOS开发中,有时我们需要实现从远程服务器下载资源并在本地进行处理,比如解压后在WebView中展示。这个场景在【标题】"ios-远程下载html资源zip,解压webview打开.zip"中被提到,涉及到的主要技术点包括网络请求...
【标题】:“IOS UIWebView Demo”是一个iOS应用开发中的实例,它主要展示了如何在iOS应用中集成和使用UIWebView组件来加载和展示网页内容。UIWebView是Apple iOS SDK提供的一种原生控件,用于在iOS应用程序内部显示...
- 使用网络请求库(如Alamofire或 URLSession)获取远程API中的新闻数据。 - UIWebView或WKWebView的使用,如果新闻内容以HTML形式提供,可能需要内嵌网页展示。 - 状态管理,如使用MVVM(Model-View-ViewModel)...
在本示例中,"ios-一个视图中两个控制器来回的切换 UISegmentedControl UIwebView.zip" 提供了一个使用UISegmentedControl来切换UIWebView展示内容的实现方式。这个场景广泛应用于构建具有导航菜单的应用,如“菜单...
UIWebView需要先实例化并添加到视图层次结构中,然后可以使用`loadRequest:`方法加载URL或者使用`loadHTMLString:baseURL:`方法加载本地或远程的HTML字符串。例如: ```objective-c UIWebView *webView = [...
这个“ios-WebViewLianJieShowDemo”项目显然关注的是如何在WebView中处理链接点击事件,以及如何捕获和利用这些链接进行后续操作。以下是关于iOS中的WebView链接处理和网络交互的一些关键知识点: 1. **UIWebView...