iPhone应用程序具有多个视图/子视图:内存未被释放

5
我有一个iPhone应用程序,它基于此链接中所述的框架,根据这个框架,一个主要的ViewController通过displayView方法加载/移除其他视图。然而,在我的应用程序中,我使用NIBs(示例链接使用编码视图),因此每个ViewController都有其相应的nib文件。
在Instruments中调试显示没有泄漏,但如果我进入/离开一个部分(带有View.xib的ViewController),则nib文件仍会保留在内存中,因此在几次进入和退出后,内存开始积累。
我知道nib文件没有被卸载,因为其中一个是几乎以编程方式创建的(IB中没有任何内容),而另一个具有在IB中创建的图像和按钮。大的先加载,小的后加载。您应该期望在Instruments中减少分配。
如何防止这种情况发生?
我的结构如下,下面有一些注释:
`MyAppDelegate.h`

#import <UIKit/UIKit.h>

@class RootViewController;

@interface MyAppDelegate : NSObject <UIApplicationDelegate> {
 UIWindow *window;
 RootViewController *viewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet RootViewController *viewController;

-(void) displayView:(int)intNewView;

@end

`MyAppDelegate.m`

#import "MyAppDelegate.h"
#import "RootViewController.h"

@implementation MyAppDelegate

@synthesize window;
@synthesize viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 [window addSubview:viewController.view];
 [window makeKeyAndVisible];
 return YES;
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
}

-(void) displayView:(int)intNewView {
 [viewController displayView:intNewView];
}

- (void)dealloc {
 [viewController release];
 [window release];
 [super dealloc];
}

@end

这个控制器处理子视图的加载和移除:

`RootViewController.h`

#import <UIKit/UIKit.h>

@interface RootViewController : UIViewController {
}

- (void) displayView:(int)intNewView;

@end

`RootViewController.m`

#import "RootViewController.h"
#import "ViewController.h"

@implementation RootViewController

UIViewController *currentView;

- (void) displayView:(int)intNewView {
 NSLog(@"%i", intNewView);
 [currentView.view removeFromSuperview];
 [currentView release];
 switch (intNewView) {
  case 1:
   currentView = [[ViewController alloc] initWithNibName:@"View" bundle:nil];
   break;
 }

 [self.view addSubview:currentView.view];
}

- (void)viewDidLoad {
 currentView = [[ViewController alloc]
   initWithNibName:@"View" bundle:nil];
 [self.view addSubview:currentView.view];
 [super viewDidLoad];
}

- (void)dealloc {
 [currentView release];
 [super dealloc];
}

@end

我有多个“详细信息”ViewControllers,就会有多个case(现在有3个case,但这将增加到10个或更多)。这种结构的目的是轻松地从应用程序的一个“部分”移动到另一个部分(NavBar控制器或TabBar控制器不适合我的特定需求)。

`ViewController.h`

// Generic View Controller Example

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController {
 UIImageView *_image1;
 UIImageView *_image2;
 NSTimer *_theTimer;
}

@property (nonatomic, retain) IBOutlet UIImageView *image1;
@property (nonatomic, retain) IBOutlet UIImageView *image2;
@property (nonatomic, retain) NSTimer *theTimer;

@end

`ViewController.m`

#import "ViewController.h"
#import "MyAppDelegate.h"

@synthesize image1 = _image1, image2 = _image2, theTimer = _theTimer;

- (void)loadMenu {
 [self.theTimer invalidate];
 self.theTimer = nil;
 MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
 [appDelegate displayView:2];
} 

-(void)setView:(UIView*)aView {
 if (!aView){
  self.image1 = nil;
  self.image2 = nil;
 }
 [super setView:aView];
}

- (void)viewDidLoad {
 //some code
 [super viewDidLoad];
}

- (void)viewDidUnload {
 self.image1 = nil;
 self.image2 = nil;
}

- (void)dealloc {
 NSLog(@"dealloc called");
 [self.theTimer invalidate];
 [self.theTimer release];
 [self.image1 release];
 [self.image2 release];
 [super dealloc];
}

请注意在 dealloc 中的 NSLog。它被调用了(我可以在控制台中看到),但是 nib 所需的内存没有被释放(因为加载了新的 nib,在离开某个部分时,Instruments 显示内存分配增加)。

非常感谢您的任何帮助。我已经尝试了无数种方法,但我无法卸载 nibs。

2个回答

6
经过无数次尝试,我终于遇到了这个论坛
它说:

显然,IB中分配的图像是使用imageNamed加载到图像视图中的。 imageNamed以一种使它们无法卸载的方式缓存图像。 您可以使用initWithContentsOfFileviewDidLoad中加载图像,然后将它们分配给视图。

我曾在其他地方读到imageNamed是恶魔,所以我不想用那种方式加载我的图像。
(顺便说一句,我使用的是iPhone OS 3.1)
我最终的解决方法是保留IB中的UIImageView,但其.image值为空。 修改后的代码类似于:
- (void)viewDidLoad {
    NSString *path = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], @"myImageThatBeforeWasAValueinIB.jpg"];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    outlet.image = image;
    // do the rest of my stuff as it was
    [super viewDidLoad];
}

- (void)dealloc {
    outlet.image = nil;
    [outlet release], outlet = nil;
    [super dealloc];
}

现在一切都像魔术般地运作!当我卸载nib和收到内存警告时,内存得到恢复。
因此,如果您拥有UIImageView的IBOutlets并且内存是一个问题(我想它总是这样),您可以在IB中设计所有内容,并在连接到outlets时从IB中删除图像引用并从代码中创建它。 IB非常适合布置应用程序。通过代码完成所有这些事情会很糟糕,但我也发现了一个不错的实用程序this nice utility that converts nibs to objective c,虽然我还没有测试过它。

你可以在这里下载我修复后的框架版本:http://www.mauriciogiraldo.com/blog/2009/10/09/multiples-views-no-jerarquicas-en-iphone/ 向下滚动查看英文翻译。 - mga

0

你尝试在dealloc中将outlet变量设置为nil了吗? 你正在正确实现setView方法,但是你在viewDidUnload方法中而不是dealloc中将你的outlet变量设置为nil。如此处所讨论的那样,你应该按照以下方式实现dealloc:

- (void)setView:(UIView *)aView {
    if (!aView) { // view is being set to nil
        // set outlets to nil, e.g.
        self.anOutlet = nil;
    }
    // Invoke super's implementation last
    [super setView:aView];
}

- (void)dealloc {
    // release outlets and set outlet variables to nil
    [anOutlet release], anOutlet = nil;
    [super dealloc];
}

编辑:如果插座是UIImageView,则可能需要执行以下操作

anOutlet.image = nil;

因为设置UIImage实例的图像属性应该将UIImage实例的保留计数增加1。


感谢您的回复。这对于“_name”实例变量和“name”属性如何工作呢?[self.anOutlet release],self.anOutlet = nil会停止并出现“向已释放的实例发送消息”的错误,而[self.anOutlet release],_anOutlet = nil在分配方面没有任何影响。 - mga
...或者我是不是应该不使用_name实例变量和name属性呢? - mga
[_name release],_name = nil; 不会出错,但也不会有任何分配更改。 - mga
我在dealloc中使用了NSLog(@"RC: %i", [anOutlet retainCount]);,结果输出为2。还有谁可能会保留这个对象?我唯一使用它的场合是[self.outlet setAlpha:0.0]; 在这种情况下,outlet是一个UIImageView。在IB中,outlet被连接到相应的UIImageView,没有其他地方使用过。 - mga
感谢您的编辑。问题是:图像是通过IB插入的,而不是程序化的。插座只是对图像的引用,以便我可以对其进行动画处理。在dealloc中,我现在正在执行:self.outlet.image = nil; [self.outlet release]; self.outlet = nil;(这会导致像我第一条评论中一样的崩溃),而_outlet.image = nil; [_outlet release]; _outlet = nil; 不会崩溃,但不会卸载任何内存。 - mga

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