Нужен контент в UIWebView для быстрого отображения - PullRequest
11 голосов
/ 07 августа 2009

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

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

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

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

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

Ответы [ 4 ]

10 голосов
/ 28 августа 2009

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

Я сам смотрел на это в прошлом, пока ничего не реализовывал, просто гуглил. В целом кажется, что это очень трудно / невозможно сделать на iPhone с помощью DontLoad. Еще в марте были сообщения, что он работает в симуляторе, но не на устройстве: 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? Мне кажется, что это Быть игнорированным. Больше информации здесь:

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

(см. Второй «ответ» по этому вопросу страница).

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

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

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

Гленн Андреас Гандреас @ 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" ... Этот ответ имеет почти каждый из них. И я рад, что написал это, чтобы я мог обратиться к нему позже; -)

1 голос
/ 02 ноября 2009

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

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

  1. Реализуйте свой собственный подкласс NSHTTPURLResponse, который позволяет вам изменять словарь allHeaderFields.
  2. Реализуйте свой собственный NSURLCache, переопределите cachedResponseForRequest: и верните новый NSCachedURLResponse, содержащий экземпляр вашего подкласса NSHTTPURLResponse с соответствующими вырезанными заголовками HTTP «expiry».
1 голос
/ 08 августа 2009

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

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

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

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

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

1 голос
/ 07 августа 2009

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

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