需要UIWebView中的内容能够快速显示

发布于 2024-08-01 20:36:02 字数 723 浏览 3 评论 0原文

我的应用程序的一部分缓存网页以供离线查看。 为此,我保存从网站获取的 HTML 并重写 img url 以指向本地存储上的文件。 当我将 html 加载到 UIWebView 中时,它会按预期加载图像,一切都很好。 我也以这种方式缓存样式表。

问题是,当我将手机置于飞行模式时,加载此缓存的 html 会导致 UIWebView 显示空白屏幕并在显示页面之前暂停一段时间。 我发现这是由 Web 视图尝试获取的原始 HTML 文档引用的非缓存 URL 引起的。 这些其他 URL 包括缓存样式表中的图像、iframe 中的内容以及打开连接以获取其他资源的 javascript。 当 UIWebView 尝试获取这些资源时会发生暂停,并且网页仅在所有其他获取超时后才会出现。

我的问题是,如何让 UIWebView 立即显示我缓存的内容? 这是我的想法:

  • 编写更多代码来缓存这些其他引用。 这可能需要大量代码来捕获所有边缘情况等,特别是必须解析 Javascript 以查看页面加载后加载的内容,
  • 强制 UIWebView 立即超时,因此不会有暂停。 我还没想出如何做到这一点。
  • 以某种方式获取已加载的内容以显示,即使外部引用尚未完成获取,但仍
  • 剥离所有脚本、链接标签和 iframe 的代码以“擦除”外部引用。 我已经尝试过这个,但对于某些网站,生成的页面严重混乱

有人可以帮助我吗? 我一直在研究这个问题,但现在已经没有想法了。

Part of my app caches web pages for offline viewing. To do that, I am saving the HTML fetched from a site and rewriting img urls to point to a file on the local store. When I load the html into a UIWebView, it loads the images as expected and everything's fine. I am also caching stylesheets in this fashion.

The problem is that when I put the phone into airplane mode, loading this cached html causes the UIWebView to display a blank screen and pause for a while before displaying the page. I've figured out that it's caused by non-cached URLs referenced from the original HTML doc that the web view is trying to fetch. These other URLs include images within the cached stylesheets, content in iframes, and javascript that opens a connection to fetch other resources. The pause happens when the UIWebView tries to fetch these resources, and the web page only appears after all these other fetches have timed out.

My questions is, how can I make UIWebView just display the stuff I've cached immediately? Here are my thoughts:

  • write even more code to cache these other references. This is potentially a ton more code to catch all the edge cases, etc., especially having to parse the Javascript to see what it loads after the page is loaded
  • force UIWebView to time out immediately so there's no pause. I haven't figured out how to do this.
  • somehow get what's already loaded to display even though the external references haven't finished fetching yet
  • strip the code of all scripts, link tags and iframes to "erase" the external references. I've tried this one, but for some sites, the resultant page is severely messed up

Can anyone help me here? I've been working on this forever, and am running out of ideas.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

旧人哭 2024-08-08 20:36:02

编辑:只是指出这个问题及其答案已有 4 年历史了。 下面我正在谈论 iOS 3.x。 我可以很容易地想象到事情已经发生了变化——哎呀,光是这些设备就比 iPhone 3G 更快了。 YMMV。 :)

我自己过去一直在研究这个问题,但还没有真正实现任何东西,只是谷歌搜索。 总的来说,使用 DontLoad 在 iPhone 上执行此操作非常困难/不可能。 即使在三月份,也有报道称它可以在模拟器中运行,但不能在设备上运行: http: //discussions.apple.com/message.jspa?messageID=9245292

我只是猜测,但基于其他 StackOverflow 问题(例如 这个) 我想也许设备上的缓存在某种程度上比它的 Mac 和Safari 同行。

苹果论坛上的其他人报告了在询问 UIWebView 和 NSURLCache 时遇到粗鲁工程师的经历,在错误报告中工程师说它应该可以工作,但实际的开发人员却说它不能。 http://discussions.apple.com/thread.jspa?threadID=1588010(7月29日-8月20日)

可能会找到一些解决方案 这里。 考虑到 [UIImage imageWithData:...],David 描述了一些“iPhone 异步缓存图像下载器的实现”的代码。

现在我在 cocoa-dev 邮件列表中发现了这封 8 月 10 日的电子邮件:

2009 年 8 月 10 日下午 1:38,Mike Manzano 写道:

<块引用>

有人能成功吗
获取iPhone上的URL加载系统
注意自定义版本
NSURL 缓存? 在我看来,它是
被忽略了。 更多信息请参见:

[此页面的链接; 已删除。]

(请参阅第二个“答案”
页)。

如果我需要一些线索
做错了什么或者我的代码
只是被忽视了。

是的,我已经成功了 - 只是
需要自己制作缓存
实例并明确设置
共享缓存到该新实例。

但它绝对有效 - 一次
实施,我的“设置表视图
将细胞图像转换为网络图像”
效果很好(并且没有滚动
充其量是痛苦的)。

格伦·安德烈亚斯·甘德里亚斯@xxxxxxxxxxxx
http://www.gandreas.com/ 太有趣了!
疯狂、糟糕和危险的事情

上述电子邮件可以在 http://www.cocoabuilder.com/archive/message/cocoa/2009/8/10/242484(没有进一步的回复)

NSURLCache 可以工作的进一步证明可以在 iCab 博客:iPhone 上 UIWebView 的 URL 过滤(2009 年 8 月 18 日)

这里的任何人可能还应该查看 Apple 的 URLCache 示例应用程序,因为它可能与他们的工作相关:https://developer.apple.com/iphone/library/samplecode/URLCache/index.html 除了现在刚刚查看它之外,它还显示“此应用程序不使用 NSURLCache 磁盘或内存缓存,因此我们的缓存策略是通过从源加载数据来满足请求。” 并使用cachePolicy:NSURLRequestReloadIgnoringLocalCacheData。 所以这并不像我想象的那么相关,以及为什么这个人称 NSURLCache 为笑话。

所以看来 NSURLCache 并不像我在这篇文章开始时所说的那么棘手或不可能。 一定要喜欢寻找一种有效的方法,同时研究它行不通的原因。 我仍然不敢相信“iphone”和“NSURLRequestReturnCacheDataDontLoad”的 Google 搜索结果只有 25 个……这个答案几乎包含了其中的每一个。 我很高兴我写了它,这样我以后就可以参考它;-)

Edit: Just pointing out that this question, and its answers, are 4 years old. I'm chatting about iOS 3.x below. I can easily imagine that things have changed in the meantime -- heck, the devices alone are faster than the iPhone 3G was. YMMV. :)

I've been looking at this myself for the past while, not actually implementing anything yet, just Googling. It seems overall that it's very hard / impossible to do this on the iPhone using DontLoad. Even in March, there were reports that it works in Simulator, but not on device: http://discussions.apple.com/message.jspa?messageID=9245292

I'm just guessing, but based on even other StackOverflow questions (e.g. this one) I'm thinking perhaps the on-device caching is somehow more limited than both its Mac and Safari counterparts.

Others on the Apple forums report experiences with rude engineers when asking about UIWebView and NSURLCache, where in bug reports the engineers say it should work, but actual developers say it doesn't. http://discussions.apple.com/thread.jspa?threadID=1588010 (Jul 29-Aug 20)

Some solutions might be found here. Considering [UIImage imageWithData:...], David describes some code for "an implementation of an asynchronous, caching image downloader for iPhone."

And now I've discovered this Aug 10th email on the cocoa-dev mailing list:

On Aug 10, 2009, at 1:38 PM, Mike Manzano wrote:

Has anyone been able to successfully
get the URL loading system on iPhone
to pay attention to custom versions of
NSURLCache? It seems to me that it's
being ignored. More info here:

[a link to this page; removed.]

(See the second "answer" on that
page).

Just need some sort of clue if I'm
doing something wrong or if my code's
just being ignored.

Yes, I've had it work - it just
requires making your own cache
instance and explicitly setting the
shared cache to that new instance.

But it absolutely did work - once
implemented, my "set the table view
cell's image to an image from the net"
works nicely (and without it scrolling
was painful at best).

Glenn Andreas gandreas@xxxxxxxxxxxx
http://www.gandreas.com/ wicked fun!
Mad, Bad, and Dangerous to Know

The above email can be found at http://www.cocoabuilder.com/archive/message/cocoa/2009/8/10/242484 (with no further replies)

Further proof that NSURLCache can work is found in iCab Blog: URL filtering for UIWebView on the iPhone (Aug 18, 2009)

Anyone here probably should also look to Apple's URLCache sample app as it may be relevant to their work: https://developer.apple.com/iphone/library/samplecode/URLCache/index.html Except having just looked at it now, it says "This application does not use a NSURLCache disk or memory cache, so our cache policy is to satisfy the request by loading the data from its source." and uses cachePolicy:NSURLRequestReloadIgnoringLocalCacheData. So this is less relevant than I thought, and why this person was calling NSURLCache a joke.

So it seems NSURLCache is not as tricky or impossible as I started this post by saying. Gotta love finding a way that works while researching reasons it won't work. I still can't believe there are only 25 Google search results for "iphone" and "NSURLRequestReturnCacheDataDontLoad"... This answer has just about every one of em. And I'm glad I wrote it so I can refer to it later ;-)

污味仙女 2024-08-08 20:36:02

使用 +requestWithURL:cachePolicy:timeoutInterval: 生成 NSURLRequest。 将缓存策略设置为NSURLRequestReturnCacheDataDontLoad。 使用 -loadRequest: 将请求加载到 Web 视图中。 完整文档 这里

Generate a NSURLRequest with +requestWithURL:cachePolicy:timeoutInterval:. Set the cache policy to NSURLRequestReturnCacheDataDontLoad. Load the request into the webview using -loadRequest:. Full docs here.

未蓝澄海的烟 2024-08-08 20:36:02

感谢您的领导! 它让我摆脱了困境。 不幸的是,我似乎遇到了一堆障碍。

问题是,如果我使用 NSURLRequestReturnCacheDataDontLoad,我认为我正在加载的资源必须已经在缓存中。

为了将它们放入缓存中,我尝试了以下方法:

    NSURLRequest *request =     [NSURLRequest 
                            requestWithURL:cachedURL 
                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData 
                            timeoutInterval:60.0] ;

    NSURLResponse *responseToCache = 
                        [[NSURLResponse alloc]
                            initWithURL:[request URL] 
                            MIMEType:@"text/html" 
                            expectedContentLength:[dataAtURL length] 
                            textEncodingName:nil] ;

    NSCachedURLResponse *cachedResponse = 
                        [[NSCachedURLResponse alloc]
                            initWithResponse:responseToCache data:dataAtURL
                            userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

        // Store it
    [[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:request] ;

    [responseToCache release] ;
    [cachedResponse release] ;

    NSLog(  @"*** request %@, cache=%@", request ,[[NSURLCache sharedURLCache] 
            cachedResponseForRequest:request]                           ) ;

我从网络上的其他地方获取了此代码,但我认为虽然它可以在 Mac 上运行,但它不能在我的目标平台 iPhone 上运行。

它似乎没有将项目插入到缓存中; NSLog 为“cache=%@”打印 null。

然后,我尝试重载 NSURLCache:

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
    {
    NSLog( @"FileFriendlyURLCache asked from request %lx of type %@ for data at URL: %@" , 
        request , [request class] , [[request URL] absoluteString] ) ;

    NSCachedURLResponse *result = nil ;

    if( [[[request URL] absoluteString] hasPrefix:@"file://"] )
        {
        NSLog( @"\tFulfilling from cache" ) ;

        NSError *error = nil ;
        NSData *dataAtURL = [NSData dataWithContentsOfURL:[request URL] 
                        options:0 error:&error] ;
        if( error )
            NSLog( @"FileFriendlyURLCache encountered an error while loading %@: %@" ,
                [request URL] , error ) ;

        NSURLResponse *reponse = [[NSURLResponse alloc]
                                    initWithURL:[request URL] 
                                    MIMEType:@"text/html" 
                                    expectedContentLength:[dataAtURL length] 
                                    textEncodingName:nil] ;
        result = [[NSCachedURLResponse alloc] initWithResponse:reponse data:dataAtURL
            userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

        #warning LEOPARD BUG MAKES IT SO I DONT AUTORELEASE result NSCachedURLResponse

        [reponse release] ;
        }
    else
        {
        NSLog( @"\tFulfilling from web" ) ;
        result = [super cachedResponseForRequest:request] ;
        }

    NSLog( @"Result = %@" , result ) ;

    return result ;
    }

在我的请求中,我指定了 NSURLRequestReturnCacheDataDontLoad 的缓存策略。 这个方法看起来调用得很好,但是它在 iPhone 上有奇怪的行为。 无论我是否返回 NSCachedURLResponse 实例,UIWebView 仍然返回错误:

Error Domain=NSURLErrorDomain Code=-1008 UserInfo=0x45722a0 "resource unavailable"

就好像 UIWebView 忽略了它返回的不是 nil 以外的东西并且无论如何都会失败的事实。 然后我开始怀疑,想知道整个 URL 加载系统是否只是忽略了来自 -cachedResponseForRequest 的所有内容,因此我创建了一个 NSCachedURLResponse 的子类,如下所示:

@implementation DebugCachedURLResponse

- (NSData *)data
    {
    NSLog( @"**** DebugCachedURLResponse data accessed." ) ;
    return [super data] ;
    }

- (NSURLResponse *)response
    {
    NSLog( @"**** DebugCachedURLResponse response accessed." ) ;
    return [super response] ;
    }

- (NSURLCacheStoragePolicy)storagePolicy
    {
    NSLog( @"**** DebugCachedURLResponse storagePolicy accessed." ) ;
    return [super storagePolicy] ;
    }

- (NSDictionary *)userInfo
    {
    NSLog( @"**** DebugCachedURLResponse userInfo accessed." ) ;
    return [super userInfo] ;
    }
@end

然后我修改 -cachedResponseForRequest 以使用此类:

    result = [[DebugCachedURLResponse alloc] initWithResponse:reponse data:dataAtURL
        userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

我将请求设置为NSURLRequestUseProtocolCachePolicy 缓存策略并运行程序。 虽然我可以看到我的 -cachedResponseForRequest 版本被调用并为所有文件 URL 返回 DebugCachedURLResponses,但我的调试没有被调用,告诉我我的 DebugCachedURLResponse 实例被完全忽略!

所以,我又被困住了。 我想你没有其他想法吧?

非常感谢您的第一次回复。

Thanks for the lead! It got me unstuck. Unfortunately, I seem to be running into a bunch of roadblocks.

The problem is that if I use NSURLRequestReturnCacheDataDontLoad, I think the resources that I'm loading must already be in the cache.

To put them in the cache, I tried this:

    NSURLRequest *request =     [NSURLRequest 
                            requestWithURL:cachedURL 
                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData 
                            timeoutInterval:60.0] ;

    NSURLResponse *responseToCache = 
                        [[NSURLResponse alloc]
                            initWithURL:[request URL] 
                            MIMEType:@"text/html" 
                            expectedContentLength:[dataAtURL length] 
                            textEncodingName:nil] ;

    NSCachedURLResponse *cachedResponse = 
                        [[NSCachedURLResponse alloc]
                            initWithResponse:responseToCache data:dataAtURL
                            userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

        // Store it
    [[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:request] ;

    [responseToCache release] ;
    [cachedResponse release] ;

    NSLog(  @"*** request %@, cache=%@", request ,[[NSURLCache sharedURLCache] 
            cachedResponseForRequest:request]                           ) ;

I got this code from elsewhere on the 'net, but I think that although it worked on the Mac, it isn't working on the iPhone, my target platform.

It doesn't seem to insert the item into the cache; the NSLog prints null for "cache=%@".

Then, I tried overloading NSURLCache:

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
    {
    NSLog( @"FileFriendlyURLCache asked from request %lx of type %@ for data at URL: %@" , 
        request , [request class] , [[request URL] absoluteString] ) ;

    NSCachedURLResponse *result = nil ;

    if( [[[request URL] absoluteString] hasPrefix:@"file://"] )
        {
        NSLog( @"\tFulfilling from cache" ) ;

        NSError *error = nil ;
        NSData *dataAtURL = [NSData dataWithContentsOfURL:[request URL] 
                        options:0 error:&error] ;
        if( error )
            NSLog( @"FileFriendlyURLCache encountered an error while loading %@: %@" ,
                [request URL] , error ) ;

        NSURLResponse *reponse = [[NSURLResponse alloc]
                                    initWithURL:[request URL] 
                                    MIMEType:@"text/html" 
                                    expectedContentLength:[dataAtURL length] 
                                    textEncodingName:nil] ;
        result = [[NSCachedURLResponse alloc] initWithResponse:reponse data:dataAtURL
            userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

        #warning LEOPARD BUG MAKES IT SO I DONT AUTORELEASE result NSCachedURLResponse

        [reponse release] ;
        }
    else
        {
        NSLog( @"\tFulfilling from web" ) ;
        result = [super cachedResponseForRequest:request] ;
        }

    NSLog( @"Result = %@" , result ) ;

    return result ;
    }

In my request, I specify a cache policy of NSURLRequestReturnCacheDataDontLoad. This method seems to be called just fine, but it has strange behaviors on the iPhone. Regardless of whether or not I return an NSCachedURLResponse instance, the UIWebView still returns an error:

Error Domain=NSURLErrorDomain Code=-1008 UserInfo=0x45722a0 "resource unavailable"

It's as if the UIWebView ignores the fact that it's getting something other than nil back and failing anyway. I then got suspicious and wondered if the entire URL loading system simply just ignored everything coming from -cachedResponseForRequest, so I created a subclass of NSCachedURLResponse that looks like this:

@implementation DebugCachedURLResponse

- (NSData *)data
    {
    NSLog( @"**** DebugCachedURLResponse data accessed." ) ;
    return [super data] ;
    }

- (NSURLResponse *)response
    {
    NSLog( @"**** DebugCachedURLResponse response accessed." ) ;
    return [super response] ;
    }

- (NSURLCacheStoragePolicy)storagePolicy
    {
    NSLog( @"**** DebugCachedURLResponse storagePolicy accessed." ) ;
    return [super storagePolicy] ;
    }

- (NSDictionary *)userInfo
    {
    NSLog( @"**** DebugCachedURLResponse userInfo accessed." ) ;
    return [super userInfo] ;
    }
@end

I then modified -cachedResponseForRequest to use this class instead:

    result = [[DebugCachedURLResponse alloc] initWithResponse:reponse data:dataAtURL
        userInfo:nil storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] ;

I set the request to have a NSURLRequestUseProtocolCachePolicy cache policy and ran the program. Although I can see my version of -cachedResponseForRequest being called and returning DebugCachedURLResponses for all file URLs, none of my debugging is being called, telling me that my DebugCachedURLResponse instances are being completely ignored!

So, here again, I'm stuck. I don't suppose you have any other ideas?

Many thanks for your first reply.

倾其所爱 2024-08-08 20:36:02

嘿伙计们,我想我发现了问题。 看起来 NSURLRequestReturnCacheDataElseLoad 和 NSURLRequestReturnCacheDataDontLoad 在 iPhone 上被破坏了。 当从缓存返回 NSCachedURLResponse 并且它包含指示内容已过期的 HTTP 标头(例如 Expires、Cache-Control 等)时,缓存的响应将被忽略,并向原始源发出请求。

解决方案如下:

  1. 实现您自己的 NSHTTPURLResponse 子类,它允许您修改 allHeaderFields 字典。
  2. 实现你自己的 NSURLCache,覆盖 cachedResponseForRequest: 并返回一个新的 NSCachedURLResponse,其中包含 NSHTTPURLResponse 子类的实例,并删除相关的“过期”HTTP 标头。

Hey guys, I think I found the problem. It looks like NSURLRequestReturnCacheDataElseLoad and NSURLRequestReturnCacheDataDontLoad is broken on the iPhone. When a NSCachedURLResponse is returned from the cache and it contains HTTP headers indicating that the content has expired (e.g. Expires, Cache-Control etc), the cached response is ignored and the request is made to the original source.

The solution is as follows:

  1. Implement your own subclass of NSHTTPURLResponse which allows you to modify the allHeaderFields dictionary.
  2. Implement your own NSURLCache, override cachedResponseForRequest: and return a new NSCachedURLResponse containing an instance of your NSHTTPURLResponse subclass with the relevant "expiry" HTTP headers stripped.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文