在UIWebView中动态加载javascript文件,本地缓存无法正常工作

时间:2012-02-14 15:55:10

标签: javascript ios uiwebview

我正在尝试编写一个在UIWebView中使用大量java脚本的应用程序。这个java脚本中的一些在需要时动态加载(使用jquery)。

index.html和jquery文件按预期加载,但不加载code.js文件。 code.js文件是这样的请求(从index.html剪下了java脚本代码):

function require(moduleName,filePath) {
    var uri = filePath;
    jQuery.ajax({
        type: "GET",
        url: uri,           
        async: false,
        dataType: "script",
        success: function(content) {
            console.log("Receive content of "+moduleName);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {      
            console.log("Error content of "+moduleName);
            console.log("Status: " + textStatus);
            console.log("Thrown error: " + errorThrown);
            console.log("h" + XMLHttpRequest.getAllResponseHeaders());
            console.log(XMLHttpRequest.responseText);
        }
    });
}

function start() {
    require("test", "code.js");
}

在index.html的body标签的onload事件中调用start()函数。 code.js文件只包含以下三行代码:

alert("Loaded file syncronosly");
// HEllo
alert("second");

我从这段代码得到的输出看起来像这样(我有一些额外的js代码,它们将console.log调用转发给我省略的Xcode控制台):

UIWebView console: Error content of test
UIWebView console: Status: error
UIWebView console: Thrown error: 
UIWebView console: h
UIWebView console: HTTP Error (0 error).
UIWebView console: alert("Loaded file syncronosly");
// HEllo
alert("second");

一旦我尝试在重写的NSURLCache类中返回code.js的内容,我就会得到这种行为。最后的想法是拥有一个应用程序,其中一个部分在UIWebView中运行,而无需从外部Web服务器加载内容。

这是缓存类(LocalSubstitutionCache)的缩短代码:

NSString *pathString = [[request URL] absoluteString];
NSLog(@"request => %@", pathString);

NSString* fileToLoad = nil;
if ([pathString isEqualToString:@"http://example.com/index.html"]) {
   fileToLoad = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
}
else if ([pathString hasPrefix:@"http://example.com/code.js"]) {
    fileToLoad = [[NSBundle mainBundle] pathForResource:@"code" ofType:@"js"];
}
else if ([pathString isEqualToString:@"http://example.com/jquery-1.6.2.min.js"]) {
    fileToLoad = [[NSBundle mainBundle] pathForResource:@"jquery-1.6.2.min" ofType:@"js"];
}

if (!fileToLoad) {
    // No local data needed for this request let super handle it:
    return [super cachedResponseForRequest:request];
}

NSData *data = [NSData dataWithContentsOfFile:fileToLoad];

NSURLResponse *response =
    [[NSURLResponse alloc]
        initWithURL:[request URL]
        MIMEType:[self mimeTypeForPath:pathString]
        expectedContentLength:[data length]
        textEncodingName:nil];
cachedResponse =
    [[NSCachedURLResponse alloc] initWithResponse:response data:data];

(缓存代码来自此处:http://cocoawithlove.com/2010/09/substituting-local-data-for-remote.html

现在在我的视图控制器(包含Web视图)中,我执行以下操作:

- (void)viewDidLoad {
    [super viewDidLoad];

    // This line loads the content with the cache enabled (and does not work):
    [_webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]]];
}

现在有趣的是,当我使用以下代码替换viewDidLoad方法中的代码时:

- (void) viewDidLoad {
    [super viewDidLoad];

    // This two line load the content without the cache:
    NSString* fileToLoad = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    [_webview loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:fileToLoad]]];
}

现在一切正常,我得到了两个警报。所以我怀疑我在缓存中返回的内容与原始请求在被缓存捕获时返回的内容不同。

以下是我已经检查过的内容:

  • 不使用文件网址作为工作案例。我的原始代码从Web服务器获取未缓存的代码。但是对于示例项目,我正在使用文件URL,因为这使项目更简单。
  • 返回了内容类型。两种情况都是text / javascript
  • 特价标头设置。我已经使用http代理检查了哪些标头在未缓存的请求上设置。没有特殊的标题集。

我最大的问题是jquery错误回调不会返回任何有用的错误信息。我只是得到了textStatus参数的错误输出。

您可以从此处下载示例项目:http://dl.dropbox.com/u/5426092/LocalCacheJsInjectTest.zip

希望有人可以帮助我

1 个答案:

答案 0 :(得分:11)

经过更多的工作,我找到了解决问题的方法。并在此回答我的发现。

解决方案是创建自定义NSURLProtocol实现,并在应用程序中将其注册为http协议的处理程序。代码看起来像这样(我在代码中省略了一些错误处理):

#import "MyHttpURLProtocol.h"

@implementation MyHttpURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    return [[[request URL] scheme] isEqualToString:@"http"]; // we handle http requests
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

- (void) startLoading {
    id<NSURLProtocolClient> client = [self client];
    NSURLRequest* request = [self request];
    NSString *pathString = [[request URL] absoluteString];

    NSString* fileToLoad = nil;
    if ([pathString isEqualToString:@"http://example.com/index.html"]) {
        fileToLoad = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    }
    else if ([pathString hasPrefix:@"http://example.com/code.js"]) {
        fileToLoad = [[NSBundle mainBundle] pathForResource:@"code" ofType:@"js"];
    }
    else if ([pathString isEqualToString:@"http://example.com/jquery-1.6.2.min.js"]) {
        fileToLoad = [[NSBundle mainBundle] pathForResource:@"jquery-1.6.2.min" ofType:@"js"];
    }

    NSData* data = [NSData dataWithContentsOfFile:fileToLoad];

    NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:[request URL] statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:[NSDictionary dictionary]];

    [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [client URLProtocol:self didLoadData:data];
    [client URLProtocolDidFinishLoading:self];
}

- (void) stopLoading {}

@end

此代码现在允许加载index.html文件,访问以下代码:

[_webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]]];

不要忘记在您的app delegate中注册自定义协议:

[NSURLProtocol registerClass:[TOHttpURLProtocol class]];

[NSURLProtocol registerClass:[MyHttpURLProtocol class]];

在找到解决方案后,解决方案的简单程度令人惊讶。以下是更新的示例项目的链接:http://dl.dropbox.com/u/5426092/LocalCacheJsInjectTest-working.zip

为了完整起见,这里是博客文章的链接,它帮助我找到解决方案:http://www.infinite-loop.dk/blog/2011/09/using-nsurlprotocol-for-injecting-test-data/