【问题标题】:How to safely shut down a loading UIWebView in viewWillDisappear?如何在 viewWillDisappear 中安全地关闭正在加载的 UIWebView?
【发布时间】:2010-10-21 20:35:26
【问题描述】:

我有一个包含 UIWebView 的视图,它正在加载谷歌地图(很多 javascript 等)。我遇到的问题是,如果用户在 web 视图完成加载之前点击导航栏上的“后退”按钮,我不清楚如何整齐地告诉 web 视图停止加载然后释放它,而不是发送到已释放实例的消息。我也不确定 web 视图是否喜欢它的容器视图在它完成之前消失(但如果用户在加载之前点击后退按钮,我别无选择)。

在我看来WillDisappear 处理程序中我有这个

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

这似乎可以处理大多数情况,因为取消委托会阻止它将 didFailLoadWithError 发送到我的视图控制器。但是,如果我在视图的 dealloc 方法中释放 Web 视图,有时(间歇性地)我仍然会收到一条消息发送到已释放的实例,这似乎与实际页面中运行的 javascript 有关,例如:

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

如果我只是不发布 webview,那么我就不会收到这些消息,尽管我猜我会泄露 webview。

如果我不发送“stopLoading”消息,而只是在 viewWillDisappear 中释放 webview,那么我会看到如下消息:

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

可能相关,我有时(再次完全断断续续)得到一个丑陋的 heisenbug,单击其他视图导航栏上的后退按钮会弹出标题,但不会弹出视图。换句话说,我在堆栈上留下了视图 n 的标题,但显示的视图仍然是视图 n+1(结果是你被困在这个屏幕上并且无法返回根视图 - 你可以去另一个方向,即推送更多视图并弹回没有正确弹出的视图,只是不到根视图。唯一的出路是退出应用程序)。在其他时候,相同视图上的相同推送和弹出序列可以正常工作。

这个特别的让我发疯。我认为这可能与加载 Web 视图之前视图消失有关,即在这种情况下,我怀疑它可能会在内存上乱涂乱画并混淆视图堆栈。或者,这可能是完全不相关的并且是其他地方的错误(我从来没有能够在调试构建模式下重现它,它只会在我无法使用 gdb 观看它时发生在发布构建设置中:-)。从我的调试运行来看,我认为我没有过度发布任何东西。而且我似乎只有在某个时候点击了具有 web 视图的视图时才能触发它,并且在那之后它不会立即发生。

【问题讨论】:

  • 如果我是你,我会向 Apple 提交错误。 Web 视图在被释放后不应该接收任何消息——它应该在释放时终止 JS 处理和 URL 加载。我偶尔会看到导航项问题,但只是在我安装了 iPhone OS 3.0 beta 之后。也许这也是一个操作系统错误?
  • 是的,这就是我的想法(dealloc 应该清理)。苹果文档通常不太清楚与他们提供的对象的合同。我以前从未见过导航的东西,它在我的程序中经常发生,我认为这一定是我正在做的事情 - 如果我设法修复它,希望与这个 webview 事情有关。
  • 哦,补充一下,这发生在 2.2.1
  • 尝试将 webView 设置为关联对象而不是 ivar/property
  • 我对相关问题的回答:stackoverflow.com/a/19609252/423171

标签: iphone cocoa-touch uiwebview


【解决方案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];
}

【讨论】:

  • 是的,很好的答案,我正在考虑尝试这样的事情。你的答案有一些我没有想到的东西,所以我会尝试一下,如果它有效,我会接受这个。不幸的是,在我有时间尝试之前,赏金将到期 - 但我要做的是重新打开赏金并接受这种情况下的答案(只是提醒自己,这是 250 赏金)。
  • 我不会太担心赏金。这只是一个数字(此外,无论如何都将一半奖励给最佳答案;)
  • 终于开始尝试这个 - 到目前为止看起来还不错。没有有趣的消息,它甚至似乎已经治愈了 heisenbug - 虽然它很断断续续,所以在我测试更多之前我不会欢呼。
  • @rpetrich:我知道你的回答已经过去两周年了,但为什么修复在self 上使用retain/release?这不会给didReceiveMemoryNotification 或强制调用viewDidUnload 带来问题吗?您能否进一步说明为什么retain/release 用于(我假设)具有UIWebView 插座作为属性的视图控制器?
  • @rpetrich:我认为在viewDidUnload 中为[webView isLoading] == YES 调用[self release] 应该平衡保留计数,例如webView 超时时间长,连接速度慢,视图被强制卸载。我说的对吗?
【解决方案2】:

有几种方法可以处理它,但这应该可以。您想要 didFailLoadWithError 消息,它告诉您它已停止。

设置标志 isLeaving=YES; 向 Webview 发送 stopLoading。

在 didFailLoadWithError: 中,检查你在什么时候得到的错误 webview 停止:

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

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

}

在shootWebview中释放webView:


变化: 如果你想对它不屑一顾,你可以执行 performSelector:withObject:withDelay: 延迟 [fillintheblank],在没有检查的情况下调用它 10-30 秒,你几乎肯定会逃脱它,虽然我不不推荐。

您可以让 didFailLoadWithError 设置一个标志并在其他地方清理它。

还是我最喜欢的,也许你离开时不需要全部释放。您不会再次显示该视图容器吗?为什么不把它放在身边重复使用呢?

您的调试与发布问题不同,您可能需要检查您的配置以确保它完全相同。赏金是问题的可重复部分,对吧? ;-)。

-- 哦,等一下,您可能会使用 WebView 来关闭整个 View 容器。您可以对上述内容进行变体,然后等待在 shootWebView 中释放整个容器。

【讨论】:

  • 我的问题是,一旦我收到 viewWillDisappear 消息,我的视图控制器就会消失并且很快就会被释放(连同它包含的所有内容,即 webview)。我看不出有任何方法可以防止视图控制器被释放(不泄漏),因为不是我这样做的。因此,任何委托消息都会(有时)最终到达我的控制器的已释放实例。
  • 这听起来很奇怪,即使我输入它,但我想你可以在你的 webview 加载调用之前 [self retain] 并通过 didfinishload 和 didfailloads 中的 [self release] 来平衡,以确保一切都持续到WebViews 正确完成。
  • 是的,我自己也是这么想的,和你一样,我认为这有点奇怪——不明白为什么它不应该起作用。将按照 rpetrich 的回答进行尝试。
【解决方案3】:

您在帖子第二部分中描述的 UINavigationController 错误可能与您对内存警告的处理有关。我经历过这种现象,并且通过在查看堆栈中的视图 (n+1) 时模拟内存警告,我能够在堆栈中的视图 n 上重现它。

UIWebView 是一个内存消耗者,因此当它用作视图层次结构的一部分时,收到内存警告也就不足为奇了。

【讨论】:

  • 这很有趣,谢谢。我将阅读我对内存警告的反应(现在我根本不处理它)。自从我迁移到 3.0 并转储 webview 以支持 mapkit 后,我​​实际上还没有看到这个问题,并且通常加强了内存处理的其他领域。
【解决方案4】:

dealloc 中的一个简单的release 消息应该就足够了。

您的第二个问题听起来像是一个过早释放的视图,但如果没有看到一些代码,我不能说太多。

【讨论】:

  • 是的,dealloc 中的一个简单释放就是我所拥有的,这就是导致消息被发送到已释放实例的原因。如果有区别,则使用 @property (nonatomic,retain) 声明 webview。另外,我有 NSZombie 检查,否则我看不到任何消息将释放视图 - 正如我所说的,我在调试模式下根本无法重现它,它只会在发布模式下发生。
【解决方案5】:

我在 OS3 中使用 UIWebView 时遇到了类似的问题 - 这个描述是一个很好的起点,但是我发现在发布 webView 之前简单地取消 web 视图代理解决了我的问题。

阅读示例代码(已接受的答案 - 上面) - 这似乎有点矫枉过正。例如。 [webView release] 和 webView = nil 行的作用完全相同,因为作者描述了声明变量的方式(因此您不需要两者)。我也不完全相信所有的保留和释放行 - 但我猜你的里程会有所不同。

【讨论】:

    【解决方案6】:

    可能相关,我有时(再次 完全断断续续)变得丑陋 heisenbug 在哪里点击返回 其他视图导航栏上的按钮 会弹出标题,但不会弹出视图。 换句话说,我只剩下 堆栈上视图 n 的标题,但 视图显示仍然是视图 n+1( 结果是你被困在这个 屏幕,无法回到根目录 视图 - 你可以去另一个方向, 即推送更多视图并弹回 没有正确弹出的视图, 只是没有到根视图。唯一的 出路是退出应用程序)。在其他 时间相同的推动序列和 在相同的视图上弹出效果很好。

    我有同样的问题,当我在堆栈中使用具有视图控制器 > 2 和当前视图控制器索引 > 2 的导航控制器时,如果此时发生 memoryWarning,它会引发同样的问题。

    只有 1 种解决方案,我在多次实验后发现了该解决方案,包括在 NavigationController 中重写 pop 和 push 方法、视图控制器堆栈、堆栈视图控制器的视图和超级视图等。

    #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 个视图控制器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 2015-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多