我在UIScrollView实现中泄漏了内存吗?

时间:2010-11-28 14:59:25

标签: iphone ipad memory-management uiscrollview

我正在开发一个使用UIScrollView滚动浏览一堆幻灯片的应用。当应用程序打开时,它会创建幻灯片并将它们传递给滚动视图。我的应用程序还有一个计时器,使UIScrollView滚动幻灯片。

我还有一个设置按钮。当我按下该按钮时,它会打开设置面板。计时器无效并设置为nil。当设置面板打开时,用户可以更改应用程序主题,幻灯片和动画方向等内容。

当关闭设置面板时,将应用设置:从UIScrollView中删除幻灯片,并为UIScrollView提供新的NSArray幻灯片以供使用。然后,UIScrollView以正确的顺序和方向布置新幻灯片。计时器重置。

一次,大约每14-16次,应用程序在运行dismissAndRestart(“关闭”代码)时崩溃。我不知道为什么。我以为我是在泄漏记忆,显然我是,但我不知道在哪里。

这是我的代码:

这在启动时运行:

    - (void)viewDidLoad {    
        [scrollView setPagingEnabled:YES];
    }

    - (void) viewWillAppear:(BOOL)animated{
        [self prepareViews];
        [self applyTheme];  
    }

    - (void) viewDidAppear:(BOOL)animated{
        [self createTimerWithInterval:kScrollInterval];
    }

将视图加载到滚动条的主力方法。 (它也删除了旧的):

- (void)loadViews:(NSArray *)views IntoScroller:(UIScrollView *)scroller withDirection:(NSString *)direction{
    [scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    [scrollView setShowsHorizontalScrollIndicator: NO];
    [scrollView setShowsVerticalScrollIndicator:NO];
    scrollView.scrollsToTop = NO;

    if([direction isEqualToString:@"horizontal"]){
        scrollView.frame = CGRectMake(0, 0, 1024, 768);
        scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [[NSNumber numberWithUnsignedInt:[views count]] floatValue], scrollView.frame.size.height);
    }else if([direction isEqualToString:@"vertical"]){
        scrollView.frame = CGRectMake(0, 0, 1024, 768);
        scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, scrollView.frame.size.height * [[NSNumber numberWithUnsignedInt:[views count]] floatValue]);
    }

    for (int i=0; i<[[NSNumber numberWithUnsignedInt:[views count]] intValue]; i++) {

        [[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:scrollView.frame];

        if([direction isEqualToString:@"horizontal"]){
            [[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:CGRectMake(i * [[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view].frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height)];//CGRectMake(i * announcementView.view.frame.size.width, -scrollView.frame.origin.x, scrollView.frame.size.width, scrollView.frame.size.height)];
        }else if([direction isEqualToString:@"vertical"]){
            [[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view] setFrame:CGRectMake(0, i * [[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view].frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height)];
        }

        [scrollView addSubview:[[views objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntValue]] view]];
    }
}

创建自动滚动的计时器:

#pragma mark -
#pragma mark Create the Timer to automate scrolling

- (void) createTimerWithInterval:(float)interval{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(scrollWrapperForTimer) userInfo:nil repeats:YES];
}

定时器功能的包装功能。 (在NSUserDefault方向之前,这是必要的,现在我不需要这个。)

#pragma mark -
#pragma mark Scroll Automatically Every N seconds

- (void) scrollWrapperForTimer{
    [self scrollToNewViewInDirection:kDirection];
}

- (void)scrollToNewViewInDirection:(NSString *)direction{
    if([[NSUserDefaults standardUserDefaults] boolForKey:@"animated_scrolling"] == YES){
        if([direction isEqualToString:@"horizontal"]){
            if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedAscending)){
                [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
            }else if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedSame)){
                [scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
            }
        }else if([direction isEqualToString:@"vertical"]){
            if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedAscending)){
                [scrollView scrollRectToVisible:CGRectMake(0, scrollView.contentOffset.y + scrollView.frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
            }else if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedSame)){
                [scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:YES];
            }
        }
    }else {
        if([direction isEqualToString:@"horizontal"]){
            if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedAscending)){
                [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
            }else if([[NSNumber numberWithFloat:scrollView.contentOffset.x] compare:[NSNumber numberWithFloat:scrollView.contentSize.width - scrollView.frame.size.width]] == (NSOrderedSame)){
                [scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
            }
        }else if([direction isEqualToString:@"vertical"]){
            if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedAscending)){
                [scrollView scrollRectToVisible:CGRectMake(0, scrollView.contentOffset.y + scrollView.frame.size.height, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
            }else if([[NSNumber numberWithFloat:scrollView.contentOffset.y] compare:[NSNumber numberWithFloat:scrollView.contentSize.height - scrollView.frame.size.height]] == (NSOrderedSame)){
                [scrollView scrollRectToVisible:CGRectMake(0, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
            }
        }
    }

}

在设置面板中按下完成按钮时,将调用此方法。

#pragma mark -
#pragma mark Restart the program

- (void) dismissAndRestart{
    [self prepareViews];
    [self applyTheme];
    [self dismissModalViewControllerAnimated:YES];
    [self createTimerWithInterval:kScrollInterval];
}

这将创建正确的图像文件并将其加载到位:

#pragma mark -
#pragma mark Apply the theme to the main view

- (void) applyTheme{

    //Front panel
    UIImage *frontImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_front", kTheme]description] ofType:@"png"]];  
    [self.overlayImage setImage:frontImage];
    [frontImage release];

    //Back Panel
    if([kTheme isEqualToString:@"walnut"]){
        [self.backgroundImage setHidden:NO];
        UIImage *backImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_back", kTheme]description] ofType:@"png"]];    
        [self.backgroundImage setImage:backImage];
        [backImage release];            
    }else if([kTheme isEqualToString:@"metal"]){
        [self.backgroundImage setHidden:YES];
        [self.view setBackgroundColor: [UIColor scrollViewTexturedBackgroundColor]];
    }

    //Gabbai Button
    UIImage *gabbaiImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[[NSString stringWithFormat:@"%@_settings_button", kTheme]description] ofType:@"png"]];   
    [self.gabbaiButton setImage:gabbaiImage forState:UIControlStateNormal];
    [gabbaiImage release];  


}

另一个需要重构的包装函数:

#pragma mark -
#pragma mark Slide View related

- (void) prepareViews{
    [self loadViews:[self createandReturnViews] IntoScroller:scrollView withDirection:kDirection];
}

创建要传递给滚动条的幻灯片数组:

//recreation of the views causes a delay each time the admin panel is closed.
- (NSArray*) createandReturnViews{

    NSMutableArray *announcements = [NSMutableArray array];
    NSArray *announcementsArray = [NSArray arrayWithObjects: @"Welcome to\nGabbai HD!",
                                   @"First slide",
                                   @"Second Slide",
                                   @"Third Slide",
                                   @"etc.",
                                   nil];
    for (int i = 0; i<[[NSNumber numberWithUnsignedInteger:[announcementsArray count]] intValue]; i++) {
        MBAnnouncementViewController *announcement = [[MBAnnouncementViewController alloc] initWithNibName:@"MBAnnouncementViewController" bundle:nil];
        [announcement setAnnouncementText:[announcementsArray objectAtIndex:[[NSNumber numberWithInt:i] unsignedIntegerValue]]];
        [announcements addObject:announcement];
        [announcement release];
    }

    return announcements;
}

为什么我的设置面板会在关闭时崩溃应用?

3 个答案:

答案 0 :(得分:2)

您显示的代码有几个alloc调用。在每一个之后,你设置一个属性,然后释放。我假设该属性声明为retain

如果是这样,那么最终,您需要释放这些属性。通常在dealloc消息实现中。

具有保留属性的类需要像这样的dealloc

 -(void) dealloc() {
    self.prop = nil; // calls release
    self.prop2 = nil;
    [super dealloc];
 }

如果您的属性声明为

  @property (nonatomic, retain) Type* prop;

然后,当您使用set消息或self.prop语法重新分配属性时,先前的值会在其上调用release。如果你只使用prop = newVal;,那么你直接访问该字段而不是调用set消息,因此不会调用release。

通常,始终使用set消息或点语法,这样您就不必担心 - 这是您将属性声明为保留的主要原因,因此请充分利用它。

答案 1 :(得分:0)

我还没有读过你的代码,但可能是你试图发布一个已经发布的对象。我建议您在开发时打开僵尸,但是在编译发布版本时请重新启用僵尸,因为当启用僵尸时,内存不会被释放。在调试时非常有用,如果你是代码试图访问不再存在的对象。 谷歌“启用僵尸xcode”或类似的东西

答案 2 :(得分:0)

你也可以使用xcode analyzer使用window + shift + a找出内存泄漏。