Нужен контент в UIWebView для быстрого отображения

Часть моего приложения кэширует веб-страницы для автономного просмотра. Для этого я сохраняю HTML, извлеченный из сайта, и переписываю URL-адреса img, чтобы указать на файл в локальном хранилище. Когда я загружаю html в UIWebView, он загружает изображения, как ожидалось, и все в порядке. Таким образом, я также кэширую таблицы стилей.

Проблема заключается в том, что, когда я помещаю телефон в режим самолета, загрузка этого кэшированного html заставляет UIWebView отображать пустой экран и приостанавливать какое-то время перед отображением страницы. Я выяснил, что это вызвано не кэшированными URL-адресами, указанными в исходном HTML-документе, который пытается просмотреть веб-представление. Эти другие URL-адреса включают изображения в кэшированных таблицах стилей, содержимое в iframe и javascript, которые открывают соединение для извлечения других ресурсов. Пауза происходит, когда UIWebView пытается извлечь эти ресурсы, и веб-страница появляется только после истечения всех этих других изъятий.

Мои вопросы: как я могу заставить UIWebView отображать только то, что я кэшировал немедленно? Вот мои мысли:

  • напишите еще больше кода для кэширования этих других ссылок. Это потенциально более тонкий код, чтобы поймать все крайние случаи и т.д., Особенно для того, чтобы проанализировать Javascript, чтобы увидеть, что он загружает после загрузки страницы.
  • заставить UIWebView немедленно отключиться, чтобы пауза не была. Я не понял, как это сделать.
  • каким-то образом получить то, что уже загружено для отображения, даже если внешние ссылки еще не завершили выборку
  • разделите код всех скриптов, тегов ссылок и фреймов на "стереть" внешние ссылки. Я пробовал это, но для некоторых сайтов результирующая страница сильно перепуталась.

Может ли кто-нибудь помочь мне здесь? Я работаю над этим навсегда, и у меня заканчиваются идеи.

Ответ 1

Изменить: просто указывая, что этому вопросу и его ответам 4 года. Я общаюсь с iOS 3.x ниже. Я могу легко представить, что все изменилось за последнее время - черт возьми, одни устройства быстрее, чем iPhone 3G. YMMV.:)

Я смотрел на это сам в прошлое, а на самом деле ничего не реализовал, просто в Google. Похоже, что это очень сложно/невозможно сделать на iPhone с помощью DontLoad. Даже в марте появились сообщения о том, что он работает в Simulator, но не на устройстве: http://discussions.apple.com/message.jspa?messageID=9245292

Я просто догадываюсь, но на основе даже других вопросов StackOverflow (например, этот). Я думаю, что кэширование на устройстве как-то более ограничено, чем как его Mac, так и Safari.

Другие на форумах Apple сообщают о проблемах с грубыми инженерами, когда спрашивают о UIWebView и NSURLCache, где в отчетах об ошибках инженеры говорят, что это должно сработать, но фактические разработчики говорят, что это не так. http://discussions.apple.com/thread.jspa?threadID=1588010 (29 июля - 20 августа)

Некоторые решения могут быть найдены здесь. Учитывая [UIImage imageWithData:...], Дэвид описывает некоторый код для "реализации асинхронного кэширующего загрузчика изображений для iPhone".

И теперь я обнаружил это письмо 10 августа в списке рассылки cocoa -dev:

10 августа 2009 года, в 13:38, Майк Манзано написал:

Кто-нибудь смог успешно   получить систему загрузки URL-адресов на iPhone   обратить внимание на пользовательские версии   NSURLCache? Мне кажется, что это   Быть игнорированным. Подробнее здесь:

     

[ссылка на эту страницу; удалены.]

     

(см. второй "ответ" на этот   страница).

     

Просто нужна какая-то подсказка, если я   что-то не так, или если мой код   просто игнорируются.

Да, я работал, это просто требует создания собственного кеша экземпляр и общий кэш этого нового экземпляра.

Но это абсолютно сработало - один раз реализовано, мой "настроить представление таблицы образ ячейки к изображению из сети" работает красиво (и без нее прокрутка было больно в лучшем случае).

Гленн Андреас gandreas @xxxxxxxxxxxx http://www.gandreas.com/ злая забава! Безумный, Плохой и Опасный Знать

Вышеупомянутое электронное письмо можно найти на http://www.cocoabuilder.com/archive/message/cocoa/2009/8/10/242484 (без дальнейших ответов)

Дальнейшее доказательство того, что NSURLCache может работать, находится в Блог iCab: фильтрация URL-адресов для UIWebView на iPhone (18 августа 2009 г.)

Любой, кто здесь, вероятно, также должен искать пример приложения Apple URLCache, поскольку он может иметь отношение к их работе: https://developer.apple.com/iphone/library/samplecode/URLCache/index.html За исключением того, что просто посмотрел на нем сейчас говорится: "Это приложение не использует диск NSURLCache или кеш памяти, поэтому наша политика кэширования должна удовлетворять запросу, загружая данные из его источника". и использует cachePolicy:NSURLRequestReloadIgnoringLocalCacheData. Так что это менее актуально, чем я думал, и почему этот человек вызывал шумиху NSURLCache.

Итак, кажется, что NSURLCache не так сложно или невозможно, как я начал этот пост, сказав. Должен любить находить способ, который работает, исследуя причины, которые он не сработает. Я все еще не могу поверить, что есть только 25 результатов поиска Google для "iphone" и "NSURLRequestReturnCacheDataDontLoad"... Этот ответ имеет примерно каждый из них. И я рад, что написал это, чтобы потом поговорить об этом, -)

Ответ 2

Сгенерируйте a NSURLRequest с помощью +requestWithURL:cachePolicy:timeoutInterval:. Задайте политику кэша NSURLRequestReturnCacheDataDontLoad. Загрузите запрос в веб-просмотр с помощью -loadRequest:. Полный docs здесь.

Ответ 3

Спасибо за лидерство! Это меня отклеило. К сожалению, я, кажется, сталкиваюсь с кучей блокпостов.

Проблема в том, что если я использую 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 печатает null для "cache =% @".

Затем я попытался перегрузить 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 игнорирует тот факт, что он получает что-то отличное от нуля и вообще не работает. Затем я получил подозрение и задался вопросом, просто ли вся система загрузки 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 вызывается и возвращает DebugCachedURLResponses для всех URL-адресов файлов, ни одна из моей отладки не вызывается, сообщая мне, что мои экземпляры DebugCachedURLResponse полностью игнорируются!

Итак, опять же, я застрял. Я не думаю, что у вас есть другие идеи?

Большое спасибо за ваш первый ответ.

Ответ 4

Эй, ребята, я думаю, что нашел проблему. Похоже, что NSURLRequestReturnCacheDataElseLoad и NSURLRequestReturnCacheDataDontLoad разбиты на iPhone. Когда NSCachedURLResponse возвращается из кеша и содержит заголовки HTTP, указывающие, что срок действия контента истек (например, Expires, Cache-Control и т.д.), Кешированный ответ игнорируется и запрос выполняется в исходный источник.

Решение выглядит следующим образом:

  • Внедрите свой собственный подкласс NSHTTPURLResponse, который позволяет вам изменить словарь allHeaderFields.
  • Внедрите свой собственный NSURLCache, переопределите cachedResponseForRequest и верните новый NSCachedURLResponse, содержащий экземпляр вашего подкласса NSHTTPURLResponse с разделенными заголовками HTTP "expiry".