【问题标题】:How to save the content in UIWebView for faster loading on next launch?如何在 UIWebView 中保存内容以便在下次启动时更快地加载?
【发布时间】:2010-11-23 12:45:34
【问题描述】:

我知道最近在 iphone sdk 中引入了一些缓存类,并且还有一个来自three20 的库的 TTURLRequest,可以让您缓存对 URL 的请求。但是,因为我是通过调用 UIWebView 的 loadRequest 在 UIWebView 中加载网页,所以这些技术并不真正适用。

有什么想法可以保存网页,以便在下一次应用启动时,我不必再次从网络上获取整个页面?页面本身已经有一些自动更新自身部分的 ajax 机制。

【问题讨论】:

    标签: html ios iphone url uiwebview


    【解决方案1】:

    您可以将 HTML 保存在文档目录中,并在启动时直接从文档目录加载页面。

    要保存 webview 内容: Reading HTML content from a UIWebView

    加载:

        NSString* path = [[NSBundle mainBundle] pathForResource:@"about" ofType:@"html"];
        NSURL* url = [NSURL fileURLWithPath:path];
    
        NSURLRequest* request = [NSURLRequest requestWithURL:url];
        [webView loadRequest:request];
    

    【讨论】:

      【解决方案2】:

      如果页面已经有 AJAX,为什么不将 JavaScript/HTML 存储在应用程序包中以启动,而不是在第一次启动时下载它?然后使用 Corey 在下面给出的代码加载页面,让 AJAX 处理访问网络以获取页面的更新部分。

      【讨论】:

        【解决方案3】:

        关于 UIWebView 的缓存工作方式的文章有很多,总体感觉是,即使某些机制在 MacOS X 下似乎可以正常工作,但相同的方法在 iPhone 下可能会有奇怪的行为。


        但是,我正在通过使用由任何NSURLConnectionUIWebView 访问的全局缓存来做到这一点。 就我而言,它有效;)。

        你需要了解的是全局流:

        • 你 -> loadRequest UIWebView
        • 这会进入NSURLCache 询问“是否为此请求缓存了某些内容?”:
        - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

        从那以后,这是我在磁盘上处理缓存的方法,在我这边,以加快 UIWebView 的加载:

        • 子类化 NSURLCache 并覆盖对 -(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 选择器的 get 控制
        • 重新实现此选择器,如果在 FS 上没有为此请求写入任何内容(无缓存),则在您这边执行请求并将内容存储在 FS 上。否则,返回之前缓存的内容。
        • 创建子类的实例并将其设置到系统,以便您的应用程序使用它

        现在是代码:

        MyCache.h

        @interface MyCache : NSURLCache {
        }
        @end
        

        MyCache.m

        @implementation MyCache
        
        -(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSLog(@"CACHE REQUEST S%@", request);
            NSString *documentsDirectory = [paths objectAtIndex:0];
            NSArray* tokens = [request.URL.relativePath componentsSeparatedByString:@"/"];
            if (tokens==nil) {
                NSLog(@"ignoring cache for %@", request);
                return nil;
            }
            NSString* pathWithoutRessourceName=@"";
            for (int i=0; i<[tokens count]-1; i++) {
                pathWithoutRessourceName = [pathWithoutRessourceName stringByAppendingString:[NSString stringWithFormat:@"%@%@", [tokens objectAtIndex:i], @"/"]];
            }
            NSString* absolutePath = [NSString stringWithFormat:@"%@%@", documentsDirectory, pathWithoutRessourceName];
            NSString* absolutePathWithRessourceName = [NSString stringWithFormat:@"%@%@", documentsDirectory, request.URL.relativePath];
            NSString* ressourceName = [absolutePathWithRessourceName stringByReplacingOccurrencesOfString:absolutePath withString:@""];
            NSCachedURLResponse* cacheResponse  = nil;
            //we're only caching .png, .js, .cgz, .jgz
            if (
                [ressourceName rangeOfString:@".png"].location!=NSNotFound || 
                [ressourceName rangeOfString:@".js"].location!=NSNotFound ||
                [ressourceName rangeOfString:@".cgz"].location!=NSNotFound || 
                [ressourceName rangeOfString:@".jgz"].location!=NSNotFound) {
                NSString* storagePath = [NSString stringWithFormat:@"%@/myCache%@", documentsDirectory, request.URL.relativePath];
                //this ressource is candidate for cache.
                NSData* content;
                NSError* error = nil;
                //is it already cached ? 
                if ([[NSFileManager defaultManager] fileExistsAtPath:storagePath]) {
                    //NSLog(@"CACHE FOUND for %@", request.URL.relativePath);
                    content = [[NSData dataWithContentsOfFile:storagePath] retain];
                    NSURLResponse* response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:@"" expectedContentLength:[content length] textEncodingName:nil];
                    cacheResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:content];
                } else {
                    //trick here : if no cache, populate it asynchronously and return nil
                    [NSThread detachNewThreadSelector:@selector(populateCacheFor:) toTarget:self withObject:request];
                }
            } else {
                NSLog(@"ignoring cache for %@", request);
            }
            return cacheResponse;
        }
        
        -(void)populateCacheFor:(NSURLRequest*)request {
            NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            //NSLog(@"PATH S%@", paths);
            NSString *documentsDirectory = [paths objectAtIndex:0];
            NSArray* tokens = [request.URL.relativePath componentsSeparatedByString:@"/"];
            NSString* pathWithoutRessourceName=@"";
            for (int i=0; i<[tokens count]-1; i++) {
                pathWithoutRessourceName = [pathWithoutRessourceName stringByAppendingString:[NSString     stringWithFormat:@"%@%@", [tokens objectAtIndex:i], @"/"]];
            }
            NSString* absolutePath = [NSString stringWithFormat:@"%@/myCache%@", documentsDirectory, pathWithoutRessourceName];
            //NSString* absolutePathWithRessourceName = [NSString stringWithFormat:@"%@%@", documentsDirectory, request.URL.relativePath];
            //NSString* ressourceName = [absolutePathWithRessourceName stringByReplacingOccurrencesOfString:absolutePath withString:@""];
            NSString* storagePath = [NSString stringWithFormat:@"%@/myCache%@", documentsDirectory, request.URL.relativePath];
            NSData* content;
            NSError* error = nil;
            NSCachedURLResponse* cacheResponse  = nil;
            NSLog(@"NO CACHE FOUND for %@", request.URL);
            //NSLog(@"retrieving content (timeout=%f) for %@ ...", [request timeoutInterval], request.URL);
            content = [NSData dataWithContentsOfURL:request.URL options:1 error:&error];
            //NSLog(@"content retrieved for %@  / error:%@", request.URL, error);
            if (error!=nil) {
                NSLog(@"ERROR %@ info:%@", error, error.userInfo);
                NSLog(@"Cache not populated for %@", request.URL);
            } else {
                NSURLResponse* response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:@"" expectedContentLength:[content length] textEncodingName:nil];
                cacheResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:content];
                //the store is invoked automatically.
                [[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:YES attributes:nil error:&error];
                BOOL ok;// = [[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:YES attributes:nil error:&error];
                ok = [content writeToFile:storagePath atomically:YES];
                NSLog(@"Caching %@ : %@", storagePath , ok?@"OK":@"KO");
            }
            [pool release];
        }
        @end
        

        以及在您的应用程序中使用它:

        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString* documentsDirectory = [paths objectAtIndex:0];
        NSString* diskCachePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory, @"myCache"];
        NSError* error; 
        [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:&error];
        MyCache* cacheMngr = [[MyCache alloc] initWithMemoryCapacity:10000 diskCapacity:100000000 diskPath:diskCachePath];
        [NSURLCache setSharedURLCache:cacheMngr];
        

        这段代码值得大量清理。但主要的东西应该在那里。我在完成这项工作时遇到了很多麻烦,希望这会有所帮助。

        【讨论】:

        • 谢谢。您的方法似乎是在没有 html 解析的情况下使用所有资源保存网页内容的唯一方法。但正如我所见,我们实际上加载了每个资源两次:当我们返回 nil 响应时通过 webView 本身,然后我们在“populateCacheFor”方法中加载相同的请求。任何想法如何解决这个问题?
        • 您在 cachedResponseForRequest:request: 中使用的技巧很聪明,但是两次加载资源听起来有点邪恶。 :-) 真的没有办法让 UIWebView 与世界其他地方一起玩吗?
        • 嗯,是的,我没有注意这一点,但你是对的。我不记得我为什么要异步填充缓存,但也许让它同步并将结果而不是 nil 返回给 UIWebView 是有意义的(避免请求再次由 Web 视图执行......:/)我可能会看看那个,因为我还在那个东西里......(不幸的是:/)
        • 我查过了。同步执行,因此正确的流程是(同步),检查缓存是否已填充,如果没有,则同步执行,然后返回。结果是 nil 不再返回。
        • 不幸的是,您被困在了困境和困难之间——异步加载,您将获取每个资源两次。同步加载,您在下载未缓存的资源时阻止加载其他资源(最明显的图像)。这两种方法都比完全未缓存的系统要好。
        【解决方案4】:

        我最近在 github 下发现了这个项目: http://github.com/rs/SDURLCache 该方法与我之前在此处描述的答案 How to save the content in UIWebView for faster loading on next launch? 完全相同,但代码看起来更精致,所以尝试一下可能是有意义的。

        【讨论】:

          【解决方案5】:
          猜你喜欢
          • 2010-11-23
          • 1970-01-01
          • 2015-04-12
          • 1970-01-01
          • 1970-01-01
          • 2016-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多