Утечка памяти в моей реализации UIScrollView? - PullRequest
0 голосов
/ 28 ноября 2010

Я работаю над приложением, которое использует UIScrollView для прокрутки множества слайдов. Когда приложение открывается, оно создает слайды и передает их в представление с прокруткой. Мое приложение также имеет таймер, который заставляет UIScrollView прокручивать слайды.

У меня также есть кнопка настроек. Когда я нажимаю на эту кнопку, она открывает панель настроек. Таймер недействителен и установлен на nil. Когда откроется панель настроек, пользователь может изменить такие вещи, как тема приложения, слайды и направление анимации.

Когда панель настроек закрыта, настройки применяются: слайды удаляются из UIScrollView, а UIScrollView получает новый NSArray слайдов для работы. Затем UIScrollView размещает новые слайды в правильном порядке и направлении. Таймер сброшен.

Один раз, примерно каждые 14-16 раз, происходит сбой приложения при запуске dismissAndRestart (код "close"). Я не уверен почему. Я думал, что у меня утечка памяти, и, видимо, так и есть, но я не вижу, где.

Вот мой код:

Это запускается при запуске:

    - (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 ]

2 голосов
/ 28 ноября 2010

Код, который вы показываете, имеет несколько вызовов 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 или синтаксис точки,так что вам не нужно об этом беспокоиться - это основная причина, по которой вы объявляете свойство как сохраняемое, поэтому воспользуйтесь этим преимуществом.

0 голосов
/ 30 ноября 2010

Вы также можете использовать xcode analyzer, используя window + shift + a, чтобы обнаружить утечку памяти.

0 голосов
/ 28 ноября 2010

Я не читал ваш код, но, возможно, вы пытаетесь освободить объект, который уже был освобожден. Я бы порекомендовал вам включать зомби при разработке, но помните, чтобы отключить это при компиляции релизной версии, потому что при включении зомби память не освобождается. Действительно полезно при отладке, если ваш код пытается получить доступ к объекту, который больше не существует. Google "включить зомби xcode" или что-то в этом роде

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...