Как безопасно отключить загрузку UIWebView в viewWillDisappear?

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

В моем viewWillDisappear обработчик у меня есть это

map.delegate=nil;
[self.map stopLoading];

похоже, что он обрабатывает большинство случаев, так как ничтожество делегата не прекращает его отправку didFailLoadWithError в мой контроллер представления. Однако, если я выпущу веб-представление в своем методе dealloc, иногда (с перерывами), я все равно получаю сообщение, отправленное на освобожденный экземпляр, который, как представляется, связан с javascript, запущенным на фактической странице, например:

-[UIWebView webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:]: message sent to deallocated instance 0x4469ee0

Если я просто не освобожу веб-просмотр, то я не получаю эти сообщения, хотя, я думаю, я тогда утечка веб-просмотра.

Если я не отправлю сообщение "stopLoading" и просто отпустите веб-просмотр в viewWillDisappear, я увижу такие сообщения:

/SourceCache/WebCore/WebCore-351.9.42/wak/WKWindow.c:250 WKWindowIsSuspendedWindow:  NULL window.

Возможно, я иногда (опять полностью прерывистый) получаю уродливый гейзенбуг, когда щелчок по кнопке "Назад" на каком-то другом навигаторе откроет название, но не представление. Другими словами, я остаюсь с заголовком представления n в стеке, но показ представления по-прежнему отображается n + 1 (в результате вы оказались в ловушке на этом экране и не можете вернуться к корневому представлению - вы можете пойти в другом направлении, т.е. нажимать больше просмотров и возвращаться к представлению, которое не срабатывало корректно, а не в корневом представлении. Единственный выход - выйти из приложения). В других случаях одна и та же последовательность нажатий и всплываний на одних и тех же представлениях отлично работает.

Этот конкретный меня заводит. Я думаю, что это может быть связано с исчезновением представления до того, как веб-просмотр загружен, т.е. В этом случае я подозреваю, что он может писать в память и путать стек представления. Или это может быть совершенно не связано и ошибка где-то еще (я никогда не мог воспроизвести ее в режиме отладки сборки, это происходит только с настройками сборки релиза, когда я не могу смотреть его с помощью gdb:-). Из моих отладочных запусков я не думаю, что я перевыпускаю что-нибудь. И я только, похоже, могу вызвать его, если в какой-то момент я попал в представление, имеющее веб-представление, и это происходит не сразу после этого.

Ответ 1

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

- (void)loadRequest:(NSURLRequest *)request
{
    [self retain];
    if ([webView isLoading])
        [webView stopLoading];
    [webView loadRequest:request];
    [self release];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [self retain];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self release];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [self release];
}

- (void)viewWillDisappear
{
    if ([webView isLoading])
        [webView stopLoading];
}

- (void)dealloc
{
    [webView setDelegate:nil];
    [webView release];
    [super dealloc];
}

Ответ 2

Есть несколько способов справиться с этим, но это должно сработать. Вы хотите сообщение didFailLoadWithError, это то, что говорит вам, что оно остановлено.

Установить флаг isLeaving = YES; Отправьте веб-обозревателю stopLoading.

В didFailLoadWithError:, проверьте наличие ошибки, которую вы получаете веб-просмотр останавливается:

if ((thiserror.code == NSURLErrorCancelled) && (isLeaving == YES)) {

[otherClass performSelector: @selector (shootWebview) withObject: nil withDelay: 0]

}

отпустите webView в shootWebview:


вариации: если вы хотите быть кавалером об этом, вы можете сделать performSelector: withObject: withDelay: с задержкой [fillintheblank], назовите его 10-30 секунд без проверки, и вы почти наверняка уйдете с ним, хотя я Не рекомендую.

Вы можете установить флажок didFailLoadWithError и очистить его в другом месте.

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

Отладка отличается от выпуска, вы можете проверить свою конфигурацию, чтобы убедиться, что она точно такая же. Баунти была на воспроизводимой части вопроса, верно?; -.)

- О, подождите секунду, вы можете взять контейнер View View с помощью WebView. Вы можете сделать вариант выше и ждать, чтобы освободить весь контейнер в shootWebView.

Ответ 3

Ошибка UINavigationController, которую вы описываете во второй части сообщения, может быть связана с обработкой предупреждений о памяти. Я испытал это явление, и я смог воспроизвести его на виду n в стеке, смоделировав предупреждение памяти во время просмотра вида (n + 1) в стеке.

UIWebView - это eater памяти, поэтому получение предупреждений о памяти не удивительно, если оно используется как часть иерархии представлений.

Ответ 4

Простое сообщение release в dealloc должно быть достаточно.

Ваша вторая проблема звучит как преждевременно освобожденный вид, но я не могу сказать много, не видя кода.

Ответ 5

У меня была аналогичная проблема с использованием UIWebView в OS3 - это описание было хорошей отправной точкой, однако я обнаружил, что просто отказался от делегата веб-представления, прежде чем выпускать webView решил мою проблему.

Чтение образца кода (принятый ответ - выше) - кажется, много перебор. Например. [webView release] и webView = нильские строки делают то же самое, что и автор, описывающий переменную, объявленную (так что вам не нужны оба). Я также не полностью убежден всеми остальными и релизными линиями - но я думаю, что ваш пробег будет меняться.

Ответ 6

Возможно, я связан (иногда) полностью прерывистый) получить уродливый heisenbug, при нажатии на спину на другом экране появится название, но не вид. Другими словами, я остаюсь с название представления n в стеке, но просмотр пока еще отображается n + 1 ( результат: вы попали в ловушку на этом экрана и не может вернуться к корню вид - вы можете пойти в другую сторону, т.е. нажать больше просмотров и вернуться к мнение, которое не поправилось правильно, просто не в корневом представлении. Единственный выход - выйти из приложения). На другом раз в той же последовательности толканий и всплывающие окна с одинаковыми представлениями прекрасно работают.

У меня та же проблема, когда я использую навигационный контроллер с контроллерами представлений в стеке > 2 и текущем индексе указателя просмотрa > 2, если в этих моментах возникает память. Это вызывает те же проблемы.

Существует 1 решение, которое я нашел после многих экспериментов с переопределяющими методами pop и push в NavigationController со стеке контроллеров представлений, с представлениями и наблюдениями для многоуровневых ViewControllers и т.д.

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@interface FixedNavigationController : 
UINavigationController <UINavigationControllerDelegate>{

}

@end

#import "FixedNavigationController.h"

static BOOL bugDetected = NO;

@implementation FixedNavigationController

- (void)viewDidLoad{
    [self setDelegate:self];
}

- (void)didReceiveMemoryWarning{
    // FIX navigationController & memory warning bug
    if([self.viewControllers count] > 2)
        bugDetected = YES;
}

- (void)navigationController:(UINavigationController *)navigationController 
didShowViewController:(UIViewController *)viewController 
animated:(BOOL)animated
{

    // FIX navigationController & memory warning bug
    if(bugDetected){
        bugDetected = NO;

        if(viewController == [self.viewControllers objectAtIndex:1]){
            [self popToRootViewControllerAnimated:NO];
            self.viewControllers = [self.viewControllers arrayByAddingObject:viewController];
        }
    }
}

@end

Он отлично работает для 3-х контроллеров представления в стеке.