Динамическая загрузка файлов JavaScript в UIWebView с локальным кэшем не работает - PullRequest
5 голосов
/ 14 февраля 2012

Я пытаюсь написать приложение, которое использует множество java-скриптов внутри UIWebView. И часть этого java-скрипта загружается динамически (с помощью jquery), когда это необходимо.

Файлы index.html и jquery загружаются должным образом, но не файл code.js. Файл code.js представляет собой запрос, подобный этому (отрывок кода java-скрипта из index.html):

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");
}

Функция start () вызывается в событии onload тега body файла index.html. Файл 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");

Я получаю такое поведение, как только пытаюсь вернуть содержимое code.js в переопределенный класс NSURLCache. В конце концов, идея состоит в том, чтобы иметь приложение, часть которого выполняется в UIWebView без необходимости загружать контент с внешнего веб-сервера.

Это сокращенный код класса кэша (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)

Теперь в моем контроллере представления (который содержит веб-представление) я делаю следующее:

- (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]]];
}

Теперь все работает нормально, и у меня отображаются два предупреждения. Поэтому я подозреваю, что то, что я возвращаю в кеш, не совпадает с тем, что возвращает исходный запрос, когда он перехватывается кешем.

Вот что я уже проверил:

  • Не использовать URL-адреса файлов для рабочего случая. Мой оригинальный код получает не кешированный код с веб-сервера. Но для примера проекта я использую URL файла, так как это упрощает проект.
  • Тип содержимого возвращен. На обоих случаях текст / javascript
  • Набор заголовков спец. Я проверил с помощью прокси-сервера http, какие заголовки установлены в запросе, который не кэшируется. Специальные заголовки не установлены.

Моя самая большая проблема заключается в том, что обратный вызов jquery не возвращает никакой полезной информации об ошибке. Я просто получаю вывод ошибки для аргумента textStatus.

Пример проекта можно скачать здесь: http://dl.dropbox.com/u/5426092/LocalCacheJsInjectTest.zip

Надеюсь, кто-нибудь может помочь мне здесь

1 Ответ

11 голосов
/ 22 февраля 2012

После еще большей работы я нашел решение своей проблемы.И ответьте здесь с тем, что я нашел.

Решение состоит в том, чтобы создать собственную реализацию 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"]]];

И не забудьте зарегистрировать пользовательский протокол в делегате приложения:

[NSURLProtocol registerClass: [TOHttpURLProtocol class]];

[NSURLProtocol registerClass:[MyHttpURLProtocol class]];

ЭтоУдивительно, насколько простым может быть решение после того, как вы его нашли.Вот ссылка на обновленный пример проекта: http://dl.dropbox.com/u/5426092/LocalCacheJsInjectTest-working.zip

А для полноты картины ссылка на пост в блоге, который помог мне найти решение: http://www.infinite -loop.dk /блог / 2011/09 / с использованием-nsurlprotocol-для-инъекционным-тест-данных /

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...