UIWebView在使用ARC时不能释放所有的活跃字节

9

我目前正在iOS 5.1上构建一个使用ARC的导航控制器应用程序。我经常需要显示网页,并且已经创建了一个网页查看器,它只是一个带有一些自定义内容的UIWebView。当用户完成浏览页面后,他们点击后退按钮,这应该释放与自定义网页查看器相关联的所有内存。我的问题是,当点击后退按钮时,并不是所有内存都被释放。我已经构建了一个玩具应用程序(在github上),它只是几个按钮,每个按钮都有一个第一响应者,调用不同的页面。

@implementation ViewController
-(IBAction)googlePressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.google.com"];
   [self.navigationController pushViewController:customWebView animated:NO];
 }
-(IBAction)ksbwPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.ksbw.com/news/money/Yahoo-laying-off-2-000-workers-in-latest-purge/-/1850/10207484/-/oeyufvz/-/index.html"];
   [self.navigationController pushViewController:customWebView animated:NO];
}
-(IBAction)feedProxyPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://feedproxy.google.com/~r/spaceheadlines/~3/kbL0jv9rbsg/15159-dallas-tornadoes-satellite-image.html"];
   [self.navigationController pushViewController:customWebView animated:NO];
}
-(IBAction)cnnPressed:(id)sender
{
   CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.cnn.com/2012/04/04/us/california-shooting/index.html?eref=rss_mostpopular"];
   [self.navigationController pushViewController:customWebView animated:NO];
}

CustomWebView只是在IB中链接到UIWebView属性的UIWebView。
@implementation CustomWebView

@synthesize webView, link;

- (id)initWithStringURL:(NSString *) stringURL
{
   if (self = [super init]) {
       link = stringURL;
   } 
   return self;
}


- (void)viewDidLoad
{
   [super viewDidLoad];
   NSURL *url = [NSURL URLWithString:link];
   NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
   [webView loadRequest:urlRequest];
}

- (void)viewDidUnload
{
   [super viewDidUnload];
}

我的问题是,一旦所有内容加载完毕,我将堆基线设置为初始视图控制器。然后,在加载页面并返回到视图控制器后,我会检查堆,得到以下堆快照:

Heapshots

每次点击按钮并返回到 ViewController 后,堆仍然会继续增长,这表明即使 CustomWebView 应该被释放,它们仍然存在。
编辑:
上述示例项目可以在 github 上找到。

你确定已经禁用了僵尸吗? - MishieMoo
试一试将您的IBOutlet属性(weak)设置为弱引用。 - Rengers
6个回答

8
我遇到了类似的问题,后来我找到了这个神奇的小技巧:传送门
/*

 There are several theories and rumors about UIWebView memory leaks, and how
 to properly handle cleaning a UIWebView instance up before deallocation. This
 method implements several of those recommendations.

 #1: Various developers believe UIWebView may not properly throw away child
 objects & views without forcing the UIWebView to load empty content before
 dealloc.

 Source: https://dev59.com/F3RB5IYBdhLWcg3wXWK2

 */        
[webView loadHTMLString:@"" baseURL:nil];

/*

 #2: Others claim that UIWebView's will leak if they are loading content
 during dealloc.

 Source: http://stackoverflow.com/questions/6124020/uiwebview-leaking

 */
[webView stopLoading];

/*

 #3: Apple recommends setting the delegate to nil before deallocation:
 "Important: Before releasing an instance of UIWebView for which you have set
 a delegate, you must first set the UIWebView delegate property to nil before
 disposing of the UIWebView instance. This can be done, for example, in the
 dealloc method where you dispose of the UIWebView."

 Source: UIWebViewDelegate class reference    

 */
[webView setDelegate:nil];


/*

 #4: If you're creating multiple child views for any given view, and you're
 trying to deallocate an old child, that child is pointed to by the parent
 view, and won't actually deallocate until that parent view dissapears. This
 call below ensures that you are not creating many child views that will hang
 around until the parent view is deallocated.
 */

[webView removeFromSuperview];

1
我在一个应用程序中使用这种方法,将多个Web视图推送到导航堆栈上,但它不起作用。当我收到内存警告(清除堆栈中更远的Web视图,然后在视图重新出现时重新创建它们),当父视图控制器被处理等情况时,我会执行上述操作,但它不起作用。首先,当弹出堆栈时,内存消耗会增加。此外,在返回到相当深的导航之后,我确信每个Web视图都已像上面那样被释放时,该应用程序仍然消耗大量完全分配的内存。 - Daniel Saidi

1

UIWebView 是一个内存消耗巨大的东西,而且不能很好地释放内存。我在你的CustomWebView类中放了一个-(void)dealloc调用,当点击返回按钮时,它会被按预期调用。

你可以尝试[[NSURLCache sharedURLCache] removeAllCachedResponses]或使用然后释放你自己的NSURLCache副本,但老实说,UIWebView从来没有完全清理自己。


1

我现在已经使用ARC有一段时间了,过去我发现网页视图会一直保持加载的内容,直到被告知加载其他内容为止。我解决这个问题的方法是在视图将要消失的方法中使用JavaScript调用,像这样:

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView loadHTMLString:@"<html></html>" baseURL:nil];
}

这为我释放了内存。


3
我将这段代码添加到我的项目中,但它并没有解决内存问题。您可以在https://github.com/bfruin/WebViewMemory找到一个展示我的问题的示例项目。 - brendan

0

我还没有使用过ARC,但是看了你的自定义webview代码后,我觉得你创建的webview还在继续工作。

如果你想要确保你的webview被正确地销毁,确保在viewWill/DidDisappear中调用[webview stopLoading],并将webview置为nil。

(注意:如果你正在推送另一个视图,那么也会销毁webview,所以要确保处理这些问题)

我经常遇到webview的问题,即使在弹出viewController后,回调到某个webView代理方法会导致应用程序崩溃(我猜测是因为ARC阻止了它的崩溃)。

如果这对你有帮助,请告诉我。


Calvin,谢谢你的回复,但是调用stopLoading并将webview设置为nil并不会改变heapshot或总活动字节数。对于这些密集页面,直接在移动Safari中打开是否更好?上面演示的示例项目表明,即使在所有内容都被释放后,UIWebView仍然会留下内存印记。 - brendan
Webview在资源消耗方面是昂贵的。我不确定为什么你的总活跃比特没有变化。在你的ViewController IBActions中,在将视图控制器推入导航堆栈后,你是否释放了正在创建的对象?我之前错过了那个细节。 - Nitin Alabur
理想情况下,应该是这样的 - (IBAction)googlePressed:(id)sender { CustomWebView *customWebView = [[CustomWebView alloc] initWithStringURL:@"http://www.google.com"]; [self.navigationController pushViewController:customWebView animated:NO]; [customWebView release]; } - Nitin Alabur
我不确定为什么在你的问题中我错过了ARC细节。现在我不确定为什么它没有释放。 - Nitin Alabur
在仪器上进行了测试并搜索了“UIWebView”对象。当点击返回按钮时,它们确实消失了... - Christian Schnorr
显示剩余2条评论

0
Brendan,有几点需要注意:
你没有发布足够的代码来确定发生了什么。但是,为什么不让控制器拥有一个单独的UIWebView,而不是在每次按钮按下时创建另一个UIWebView,只需传递一个包含相关URL的字符串即可?这是一种更好的设计模式,可以保证您永远不会有多个UIWebView存在。

1
我已经按照指定的方法尝试过只有一种方式,但是我遇到了相同的问题。我在github上发布了一个示例项目,以展示我的内存问题。https://github.com/bfruin/WebViewMemory - brendan
我快速查看了你在Git上的代码,没有发现明显的问题,但是最终分析来说,我必须问:问题出在哪里?你正在使用ARC,内存管理不是你的问题。除非你看到了泄漏,而且这在你发布的截图中并没有被指示出来,那么实际上的问题是什么?尝试使用Allocations工具,查看实例化的对象及其计数。 - Cliff Ribaudo
我的问题是,git上发布的代码显示,当从一个包含Web视图的视图控制器返回时,我没有获得所有的内存。如果应用程序只是一个Web浏览器,这可能没关系,但这只是应用程序的一小部分,随着时间的推移,我最终会收到低内存警告。我宁愿避免这些内存问题,但只要没有泄漏,仅在viewDidUnload中保存应用程序状态是否可以? - brendan
1
如果你在其他地方和其他时间遇到低内存问题,那么可能存在更接近你遇到问题的地方/时间的其他内存问题。你之前没有提到过这个。我建议你从在此控制器中只有一个UIWebView开始,并将URL字符串传递给它,然后再从那里开始解决问题。无论在iOS中是否使用ARC,关于内存的底线都非常简单。如果你负责释放它,那么这是你的问题,但如果不是,那就不是你的问题。 - Cliff Ribaudo

0

@interface WebViewViewController : UIViewController<UIWebViewDelegate>

的翻译内容是:

- (void)viewDidLoad
{
[super viewDidLoad];
NSString* urlAddress = @"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
self.webView.delegate = self;
[self.webView loadRequest:requestObj];
}

-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[_webView stopLoading];
[_webView loadHTMLString:@"<html><head></head><body></body></html>" baseURL:nil];
[_webView stopLoading];
[_webView removeFromSuperview];
}

- (void)dealloc
{
[_webView stopLoading];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[_webView stopLoading];
_webView = nil;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接