UIWebView - 什么时候(或如何)释放CFData?

14

经过多天的头疼和失眠,我希望在这里找到一些帮助。我已经浏览了这里的各种帖子,但是没有一个答案似乎能为我提供解决方案。

简而言之,我的问题是,当UIWebView被频繁使用(例如连续打开较大的新闻网站)后,我的应用程序会崩溃。

为了提供更多细节: 我正在编写一个iPhone应用程序,从MainViewController中推送browserViewController到导航控制器上。browserViewController加载一个包含UWebView的nib文件(我不是通过编程方式创建WebView)。UIWebView使用Interface Builder进行连接。

当返回到Main并再次转到browserViewController时,仅在其为nil时才重新创建browserViewController。(我想保留已加载的UIWebView内容 - 仅当出现内存警告时,此视图应该被卸载并释放所有使用的内存)。

在MainViewController和browserViewController中,我都会响应内存警告,但这似乎并不能提供足够的缓解。

查看Instruments,我注意到例如CFData(store)不断增加。即使我模拟内存警告(见下面的代码)并在browserViewController上调用viewDidUnload,CFData仍然被分配,并且不会被释放。

所以我最大的问题是:
如何释放从“浏览”中创建的内存?

这适用于两种情况:
- 如何确保viewDidUnload正确释放由CFData(store)分配的内存?
- 当用户在browserViewController中继续加载页面时,如何释放内存?
.
谁管理CFData?

请参见下面的简化样本代码:

MainViewController.h

//  MainViewController.h
#import "myAppDelegate.h"
#import "BrowserViewController.h"

@interface MainViewController : UIViewController {
    BrowserViewController *browViewController;
}

- (void) switchToBrowserViewController;

@end

MainViewController.m

//  MainViewController.m
#import "MainViewController.h"

@implementation MainViewController

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    [browViewController release];
    browViewController = nil;
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [browViewController release];
    browViewController = nil;
    [super dealloc];
}

- (void) switchToBrowserViewController {

    // create new browViewController if needed
    if ( browViewController == nil ) {
        browViewController = [[BrowserViewController alloc]     initWithNibName:@"BrowserViewController" bundle:nil];
    }

    browViewController.navigationItem.hidesBackButton = YES;

    [((myAppDelegate *)[UIApplication sharedApplication].delegate).navController setNavigationBarHidden:YES animated:NO];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration: 1];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:
  ((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController.view cache:YES];

    [((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController pushViewController:browViewController animated:NO];
    [UIView commitAnimations];

}

@end

BrowserViewController.h

//  BrowserViewController.h

#import <UIKit/UIKit.h>
#import "myAppDelegate.h"

@interface BrowserViewController : UIViewController <UIWebViewDelegate> {
    IBOutlet UITextField *browserURLField;
    IBOutlet UIWebView *browserWebView;
}

@property (nonatomic, retain) UIWebView *browserWebView;

- (void) loadURLinBrowser;

@end

BrowserViewController.m

//  BrowserViewController.m
#import "BrowserViewController.h"

@implementation BrowserViewController
@synthesize browserWebView;

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

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload { 
    [super viewDidUnload];

    [browserWebView stopLoading];
    [browserWebView setDelegate:nil];
    [browserWebView removeFromSuperview];
    [browserWebView release];
    browserWebView = nil;

    browserURLField = nil;
}

- (void)dealloc {
    [browserURLField release];

    browserWebView.delegate = nil;
    [browserWebView stopLoading];
    browserWebView = nil;
    [browserWebView release];

    [super dealloc];
}

- (void) switchBackToMainViewController {

    [((myAppDelegate *)[UIApplication sharedApplication].delegate).navController setNavigationBarHidden:NO animated:NO];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration: 1];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController.view cache:YES];

    [((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController popViewControllerAnimated:NO];

    [UIView commitAnimations];
}

- (void) loadURLinBrowser {
    NSURL *url = [[NSURL alloc] initWithString:browserURLField.text];
    NSMutableURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
    [browserWebView loadRequest: request];
    [request release];
    [url release];
}

@end

我尝试了其他帖子中提到的各种建议,比如:

  • 1) Loading an empty page into the WebView.

        NSString *html = @"<html><head></head><body></body></html>";
        [browserWebView loadHTMLString:html baseURL:nil];
    
  • 2) using removeAllCachedResponses on various places in the above code

        [[NSURLCache sharedURLCache] removeAllCachedResponses];
    
  • 3) setSharedURLCache did also not provide relief ( I also used this in the AppDelegate applicationDidFinishLaunching).

        NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
        [NSURLCache setSharedURLCache:sharedCache];
        [sharedCache release];
    

很遗憾,这些都没有帮助“清除缓存”或释放由CFData(store)分配的内存。

如果有人能为此提供一些指导,并让我知道我错过了什么或做错了什么,我将不胜感激。

.
.

编辑:
在KiwiBastard的初始回复之后,我添加了一个屏幕截图,显示我在Instruments中观察到的情况:

enter image description here

.
.

2010年6月的编辑:
我仍然无法解决这个问题。
在第二次尝试中,我完全以编程方式创建了UIWebView。
仍然存在同样的问题。但是我注意到了奇怪的行为。如果我将PDF文档加载到webView中,并且不滚动PDF页面,webView和数据将被成功释放。但是,一旦我滚动到第二页,dealloc就不再起作用,我的应用程序最终会在某个时候耗尽内存。这是非常奇怪的,我无法解决这个问题。
有人有任何想法吗?帮帮忙?


你好,你解决了吗?我也遇到了同样的问题,展示PDF文件时会出现相同的情况。如果有多页并且用户滚动,内存会飙升。我正在使用QLPreviewController并发布了一个问题,但仍然没有答案。http://stackoverflow.com/questions/7500628/qlpreviewcontroller-memory-issues - Metodij Zdravkin
  1. 为什么你使用NSMutableURLRequest而不是只用NSURLRequest
  2. 你试过[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]吗?
  3. 不要模拟内存警告,把browViewController推送后释放并置空,以查看是否有任何影响。
- nicerobot
3个回答

2
为了释放 CFData,您只需要调用 CFRelease(您的 CFData 对象名称)

1

我认为可能发生的情况是您的浏览器没有被清除,而且viewDidUnload也很可能从未被调用。

因为您的MainViewController有一个类型为BrowserViewController的变量,该变量从不释放,将在应用程序的整个生命周期中一直存在。此外,由于您只是切换视图,因此视图也将保留在内存中。

我可以建议您尝试在需要时创建BrowserViewController变量,并在通过navcontroller推送后释放它。

 BrowserViewController *browViewController = [[BrowserViewController alloc]     initWithNibName:@"BrowserViewController" bundle:nil];

 browViewController.navigationItem.hidesBackButton = YES;

 [((myAppDelegate *)[UIApplication sharedApplication].delegate).navController setNavigationBarHidden:YES animated:NO];

 [UIView beginAnimations:nil context:NULL];
 [UIView setAnimationDuration: 1];
 [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:
  ((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController.view cache:YES];

 [((myAppAppDelegate *)[UIApplication sharedApplication].delegate).navController pushViewController:browViewController animated:NO];
 [UIView commitAnimations];
 [browViewController release];

我知道每次加载nib会稍微影响性能,但是你显然也不想缓存vc吧?


1
我在 viewDidUnload 上设置了断点并成功被调用。然而,我尝试了您的建议,在需要时创建 BrowserViewController 变量,并在被 navcontroller 推动后释放它。但不幸的是,这并没有解决这个问题。 - Ralph
我添加了两个屏幕截图,展示了我在Instruments中观察到的情况。感谢上面的建议。希望你或其他人会有另一个想法。 - Ralph

1
我曾经在某个地方读到,这是UIWebView的一个众所周知的bug。有些人建议使用静态webview对象来避免反复初始化,但是找不到合适的解决方案。即使你采用了同样的方法。幸运的是,我的需求只是一个带有图像的简单网页视图。因此,我最终使用了一个自定义控制器,其中包含一个UIImageView和一个UITextView,而且不需要编辑。对我来说很好用。

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