【问题标题】:Am I leaking memory in my UIScrollView implementation?我的 UIScrollView 实现是否泄漏了内存?
【发布时间】:2011-05-16 21:23:00
【问题描述】:

我正在开发一个使用 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;
}

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

【问题讨论】:

    标签: iphone ipad memory-management uiscrollview


    【解决方案1】:

    你也可以通过window+shift+a使用xcode分析器来找出内存泄漏。

    【讨论】:

      【解决方案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 语法重新分配属性时,先前的值已对其调用释放。如果你只是使用prop = newVal;,那么你是直接访问该字段而不是调用set消息,所以不会调用release。

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

      【讨论】:

      • dealloc 在应用程序执行结束时被调用。我正在尝试重置属性。我明白你在说什么。
      • dealloc 可以在此之前调用。它在其定义的对象的保留计数降至 1 以下后的某个时间被调用。
      • 但是如何释放我想重新分配其他内容的属性?
      • 哦,我是在调用 release,而不是 dealloc。
      • 你不调用dealloc,你实现dealloc,当retainCount变为0时调用它。我更新了答案以提供更多信息
      【解决方案3】:

      我没有阅读你的代码,但可能是你试图释放一个已经被释放的对象。我建议你在开发的时候打开zombies,但是在编译release版本的时候记得关闭它,因为当zombies开启时,内存不会被释放。调试时真的很有帮助,如果你的代码试图访问一个不再存在的对象。 谷歌“启用僵尸 xcode”或类似的东西

      【讨论】:

      • 我知道如何开启 NSZombie,但是如何查看 Zombies 的保留数?
      • Zombies 的 retainCount 始终为 0——这就是它试图帮助你解决的问题——向可能被释放的对象发送消息。如果您向僵尸发送消息,您将在控制台中看到消息。
      猜你喜欢
      • 2016-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-13
      • 2020-07-22
      • 2010-10-13
      相关资源
      最近更新 更多