代码之家  ›  专栏  ›  技术社区  ›  Moshe

我的UIScrollView实现中是否有内存泄漏?

  •  0
  • Moshe  · 技术社区  · 14 年前

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

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

    关闭“设置”面板后,将应用设置:幻灯片将从UIScrollView中移除,UIScrollView将获得一个新的幻灯片序列以供使用。然后,UIScrollView按照正确的顺序和方向排列新幻灯片。计时器复位。

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

    这是我的代码:

    启动时运行:

        - (void)viewDidLoad {    
            [scrollView setPagingEnabled:YES];
        }
    
        - (void) viewWillAppear:(BOOL)animated{
            [self prepareViews];
            [self applyTheme];  
        }
    
        - (void) viewDidAppear:(BOOL)animated{
            [self createTimerWithInterval:kScrollInterval];
        }
    

    将视图加载到滚动条中的Workhorse方法。(它也会删除旧的):

    - (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 回复  |  直到 14 年前
        1
  •  2
  •   Lou Franco    14 年前

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

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

    具有保留属性的类需要这样的释放

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

    如果您的属性声明如下

      @property (nonatomic, retain) Type* prop;
    

    然后,当您使用 set 消息或 self.prop 语法,上一个值已对其进行释放调用。如果你只是使用 prop = newVal; ,则直接访问该字段,而不调用 设置 消息,因此不会调用释放。

    通常,总是使用 设置 消息或点语法,这样您就不必担心它了——这是您将属性声明为retain的主要原因,所以要利用它。

        2
  •  0
  •   oddi    14 年前

    我没有读过你的代码,但可能是你试图释放一个已经释放的对象。我建议您在开发时打开zombies,但在编译发行版时请记住关闭此选项,因为启用zombies时,不会释放内存。在调试时非常有用,如果您是代码,则试图访问不再存在的对象。 谷歌“启用僵尸xcode”之类的

        3
  •  0
  •   Mahesh    14 年前

    也可以使用window+shift+a的xcode分析器来查找内存泄漏。