Получение утечки памяти в UIKit?

Когда я тестировал свое приложение в инструментах на утечку памяти, я ничего не нашел (работает с помощью симулятора). Но когда я запускаю его на мобильном телефоне, а затем проверяю, в объектах UIKit возникает много утечек. Это происходит при любом взгляде. В симуляторе таких утечек нет.

Ниже приведен скриншот прибора, где произошла утечка.

Когда я перешел на secondViewController из HomeView, утечек не обнаружено. Если я снова вернусь домой, эти утечки найдены. Итак, значит ли это, что я должен освободить / обнулить все объекты пользовательского интерфейса, которые я использовал в этом secondView. Для вашей информации ниже приведены объекты пользовательского интерфейса, которые я использовал в secondView.

1.Two Background UIImageView
2.One TitleBar UIImageView
3.3 UIButtons(Back,left and right button for iCarousel)
4.One iCarousel view
5.UIPageController(For this I have used a third Party code SMPageControl)
6.One title label. 

Примечание: мой код не-ARC.

Кто-нибудь сталкивался с этой проблемой раньше. Как я могу преодолеть эту проблему, так как у меня есть эта проблема в каждом представлении в моем приложении. Из-за этого мое приложение часто получало память и часто зависало.

Спасибо.

Ниже приведен мой файл реализации этого представления.

EDIT1:

 @implementation CatalogueViewController

@synthesize deptCarousel    = _deptCarousel;
@synthesize carouselItems   = _carouselItems;
@synthesize categorymAr     = _categorymAr;
@synthesize spacePageControl = _spacePageControl;
@synthesize wrap;

- (void)dealloc {
    _deptCarousel = nil;
    [_categorymAr               release];
    _categorymAr                = nil;
    _deptCarousel.delegate      = nil;
    _deptCarousel.dataSource    = nil;
    [_deptCarousel              release];
    [_carouselItems             release];
    [viewGesture release];
    viewGesture = nil;
    [_spacePageControl release];
    _spacePageControl = nil;
    imgViewBG = nil;
    imgViewBG2 = nil;
    btnPrev = nil;
    btnNext = nil;
//    [self releaseObjects];
    [super dealloc];
}


- ( IBAction) btnBackClicked {
    [self.navigationController popViewControllerAnimated:YES];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = NSLocalizedString(@"catalogue", @"Catalogue");

    // Do any additional setup after loading the view from its nib.

    _deptCarousel.type  = iCarouselTypeLinear;
    _deptCarousel.scrollSpeed = 0.3f;
    _deptCarousel.bounceDistance = 0.1f;
    _deptCarousel.scrollToItemBoundary = YES;
    _deptCarousel.stopAtItemBoundary = YES;
    [_deptCarousel setScrollEnabled:NO];

    UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeNext:)];
    swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    [viewGesture addGestureRecognizer:swipeLeft];
    [swipeLeft release];

    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipePrev:)];
    swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
    [viewGesture addGestureRecognizer:swipeRight];
    [swipeRight release];

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [viewGesture addGestureRecognizer:singleTap];
    [singleTap release];

    _carouselItems      = [[NSMutableArray alloc] initWithCapacity:1];
    _categorymAr        = [[NSMutableArray alloc] initWithCapacity:1];
    [self addCatalogues];
    _spacePageControl.numberOfPages = [_categorymAr count];
    [_spacePageControl setPageIndicatorImage:[UIImage imageNamed:IS_IPAD?@"Marker1.fw.png" : @"Markeri.png"]];
    [_spacePageControl setCurrentPageIndicatorImage:[UIImage imageNamed:IS_IPAD?@"Marker-Highlight.png" : @"Marker-Highlight_i.png"]];
    [_spacePageControl addTarget:self action:@selector(spacePageControl:) forControlEvents:UIControlEventValueChanged];

}



- (void)spacePageControl:(SMPageControl *)sender{
    [_deptCarousel scrollToItemAtIndex:sender.currentPage animated:YES];
}

- ( void ) addCatalogues {
    [_categorymAr addObjectsFromArray:[[DBModel database] categoryList]];

    for (int i = 0; i < [_categorymAr count]; i++) {
        [_carouselItems addObject:[NSNumber numberWithInt:i]];
    }
    [_deptCarousel reloadData];
}

- (void)viewDidUnload{

    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [self phoneType];
    [super viewWillAppear:animated];
    if (IS_IPAD) {
        UIInterfaceOrientation statusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation];
        [self handleOrientation:statusBarOrientation];
    }
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
 }


- ( void ) phoneType{

    if(!IS_IPAD){
        if(IS_IPHONE5){
            imgViewBG.image = [UIImage imageNamed:@"Background5_5.jpg"];
            imgViewBG.center = CGPointMake(162,265);
            imgViewBG2.image = [UIImage imageNamed:@"Background11_5.png"];
            _spacePageControl.center = CGPointMake(160, 478);
            _deptCarousel.center = CGPointMake(160, 355);
            viewGesture.center = CGPointMake(160, 355);
            btnPrev.center = CGPointMake(25, 355);
            btnNext.center = CGPointMake(295, 355);
        }
        else{
            imgViewBG.image = [UIImage imageNamed:@"Background5.jpg"];
            imgViewBG2.image = [UIImage imageNamed:@"Background9.png"];            
        }
    }

}


-(void)textFieldDidBeginEditing:(UITextField *)textField{

    textFieldSearch.placeholder = @"";
    UIButton *clearButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
    [clearButton setImage:[UIImage imageNamed:IS_IPAD?@"Btn_X_Large.fw.png":@"Btn_X.fw.png"] forState:UIControlStateNormal];
    [clearButton addTarget:self action:@selector(btnClearTextField) forControlEvents:UIControlEventTouchUpInside];
    [textFieldSearch setRightViewMode:UITextFieldViewModeAlways];
    [textFieldSearch setRightView:clearButton];
    [clearButton release];

}

-(void)textFieldDidEndEditing:(UITextField *)textField{
    [textFieldSearch setRightView:nil];
    if ([textFieldSearch.text isEqualToString:@""]) {
        textFieldSearch.placeholder = NSLocalizedString(@"hud_search_for_a_product_here",@"");
    }
}

-(IBAction)btnClearTextField{
    textFieldSearch.text = @"";
}

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskAll;
}



- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if (IS_IPAD) {
        return YES;
    } else {
        return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
    }
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation )toInterfaceOrientation duration:(NSTimeInterval)duration{
    if (IS_IPAD) {
        [self handleOrientation:toInterfaceOrientation];
    }
}

- ( void ) handleOrientation:(UIInterfaceOrientation )toInterfaceOrientation {



    if (toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
        imgViewBG.image = [UIImage imageNamed:@"Background_Catalogue_P.jpg"];
        imgViewBG2.image = [UIImage imageNamed:@"Background_Overlay_P.fw.png"];
        btnPrev.center = CGPointMake(90, 640);
        btnNext.center = CGPointMake(677, 640);
        textFieldSearch.frame = CGRectMake(187, 54, 418, 25);
        _deptCarousel.frame = CGRectMake(235, 250, 300, 800);
        _spacePageControl.center = CGPointMake(385, 920);
        viewGesture.center = CGPointMake(385, 658);

    }else {
        imgViewBG.image = [UIImage imageNamed:@"Background_Catalogue_L.jpg"];
        imgViewBG2.image = [UIImage imageNamed:@"Background_Overlay_L.fw.png"];
        btnPrev.center = CGPointMake(54, 385);
        btnNext.center = CGPointMake(640, 385);
        textFieldSearch.frame = CGRectMake(240, 55, 567, 25);
        _deptCarousel.frame = CGRectMake(50, 250, 600, 300);
        _spacePageControl.center = CGPointMake(346, 660);
        viewGesture.center = CGPointMake(347, 405);

    }
}

- ( IBAction )btnDepartmentClicked:(id)sender {
    int btnTag = [sender tag];
    ProductCategoriesViewController *productView = [[ProductCategoriesViewController alloc] initWithNibName:@"ProductCategoriesView" bundle:nil];
    if ( btnTag == 0 ) {
        [productView setStrTitle:NSLocalizedString(@"women", @"Women")];
    }else if ( btnTag == 1 ) {
        [productView setStrTitle:NSLocalizedString(@"men", @"Men")];
    } else {
        [productView setStrTitle:NSLocalizedString(@"sports", @"Sports")];
    }
    [self.navigationController pushViewController:productView animated:YES];

    [productView release];
}


- ( BOOL ) textFieldShouldReturn:( UITextField * )textField {
    [textField resignFirstResponder];
    [Flurry logEvent:@"Product searched" withParameters:[NSDictionary dictionaryWithObjectsAndKeys:textField.text,@"1", nil]];
    [self productSearch:textField.text isBar:NO isQR:NO];
    return YES;
}

- ( void ) productSearch:( NSString * )_searchText isBar:( BOOL )_isBar isQR:( BOOL )_isQr {
    if ([_searchText isEqualToString:@""]) {
        return;
    }

    NSMutableArray *ProductList = [[NSMutableArray alloc] init];
    [ProductList addObjectsFromArray:[[DBModel database] productSearch:_searchText isBar:_isBar isQR:_isQr]];
    if ( [ProductList count] == 0 ) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"product", @"")
                                                        message:NSLocalizedString(@"cannot_find_product", @"")
                                                       delegate:nil
                                              cancelButtonTitle:NSLocalizedString(@"ok", @"")
                                              otherButtonTitles:nil];
        [alert show];
        [alert release];        
    } else {
        GeneralProductListViewController *generalProductList = [[GeneralProductListViewController alloc] initWithNibName:IS_IPAD?@"GeneralProductListView~iPad": @"GeneralProductListView" bundle:nil];
        [generalProductList setMArProducts:ProductList];
        [self.navigationController pushViewController:generalProductList animated:YES];
        [generalProductList release];
    }
    [ProductList release];
  }


-(IBAction) spin:(id)sender {


    if([sender tag]==0)
    {

        [_deptCarousel scrollToItemAtIndex:[self.deptCarousel currentItemIndex]+1 animated:YES];
//         [_deptCarousel scrollByNumberOfItems:1 duration:2.0];
        }

    else{
     [_deptCarousel scrollToItemAtIndex:[self.deptCarousel currentItemIndex]-1 animated:YES];

    }

}

-(void)swipeNext:(UISwipeGestureRecognizer *)recognizer{

     [_deptCarousel scrollToItemAtIndex:[self.deptCarousel currentItemIndex]+1 animated:YES];

}

-(void)swipePrev:(UISwipeGestureRecognizer *)recognizer{

    [_deptCarousel scrollToItemAtIndex:[self.deptCarousel currentItemIndex]-1 animated:YES];

}

-(void) handleSingleTap:(UITapGestureRecognizer *)recognizer{

    if ([_categorymAr count] > 0) {

        ProductCategoriesViewController *prodCatView = [[ProductCategoriesViewController alloc] initWithNibName:IS_IPAD ?
                                                    @"ProductCategoriesView~iPad" : @"ProductCategoriesView" bundle:nil];
        Category *categoryObj = [_categorymAr objectAtIndex:[self.deptCarousel currentItemIndex]];
        [prodCatView setStrTitle:categoryObj.categoryName];
        [prodCatView setCategoryId:categoryObj.categoryId];
        [Flurry logEvent:@"Category List" withParameters:[NSDictionary dictionaryWithObjectsAndKeys:categoryObj.categoryName,[NSString stringWithFormat:@"%d",categoryObj.categoryId], nil]];
        [self.navigationController pushViewController:prodCatView animated:YES];
        [prodCatView release];
    }
}
//-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//    pageControl.currentPage = [self.deptCarousel currentItemIndex] ;
//}

#pragma mark
#pragma mark NavigationBarViewDelegate metho

- ( void ) navigationBackClicked {
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark -
#pragma mark iCarousel methods

- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
    return [_carouselItems count];
}

- (NSUInteger)numberOfVisibleItemsInCarousel:(iCarousel *)carousel
{
    //limit the number of items views loaded concurrently (for performance reasons)
    return NUMBER_OF_VISIBLE_ITEMS;
}

- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index
{
    Category *categoryObj = [_categorymAr objectAtIndex:index];
    //create a numbered view
    UIView *view = nil;
    NSString *imagePath = [[APP_CACHES_DIR stringByAppendingPathComponent:@"catalogues"] stringByAppendingString:[NSString stringWithFormat:@"/%d.jpg", categoryObj.categoryId]];
    if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        view = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:IS_IPAD?@"Gallery Placeholder.png":@"Gallery Placeholder.png"]] autorelease];
    } else {
        view = [[[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[[APP_CACHES_DIR stringByAppendingPathComponent:@"catalogues"] stringByAppendingString:[NSString stringWithFormat:@"/%d.jpg", categoryObj.categoryId]]]] autorelease];
    }



    if (IS_IPAD) {
        view.frame = CGRectMake(0, 0, 420, 420);
    } else {
        view.frame = CGRectMake(0, 0, 200, 200);
    }

//  UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(view.bounds.origin.x, view.bounds.origin.y+view.bounds.size.height, view.bounds.size.width, 44)] autorelease];
//  label.text = categoryObj.categoryName;
//    label.textColor = [UIColor blackColor];
//  label.backgroundColor = [UIColor clearColor];
//  label.textAlignment = UITextAlignmentCenter;
//    label.font = [UIFont fontWithName:@"Helvetica-Bold" size:IS_IPAD?26:14];
//  [view addSubview:label];


    return view;
}

- (NSUInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
    //note: placeholder views are only displayed on some carousels if wrapping is disabled
    return INCLUDE_PLACEHOLDERS? 2: 0;
}

- (UIView *)carousel:(iCarousel *)carousel placeholderViewAtIndex:(NSUInteger)index
{
    //create a placeholder view
    UIView *view = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@""]] autorelease];
    UILabel *label = [[[UILabel alloc] initWithFrame:view.bounds] autorelease];
    label.text = (index == 0)? @"[": @"]";
    label.backgroundColor = [UIColor clearColor];
    label.textAlignment = UITextAlignmentCenter;
    label.font = [label.font fontWithSize:50];

    _spacePageControl.currentPage = index;


//  [view addSubview:label];
    return view;
}

- (CGFloat)carouselItemWidth:(iCarousel *)carousel
{
    //usually this should be slightly wider than the item views
    return ITEM_SPACING;
}

- (CATransform3D)carousel:(iCarousel *)_carousel transformForItemView:(UIView *)view withOffset:(CGFloat)offset
{
    //implement 'flip3D' style carousel

    //set opacity based on distance from camera
    view.alpha = 1.0 - fminf(fmaxf(offset, 0.0), 1.0);

    //do 3d transform
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = _deptCarousel.perspective;
    transform = CATransform3DRotate(transform, M_PI / 8.0, 0, 1.0, 0);

    return CATransform3DTranslate(transform, 0.0, 0.0, offset * _deptCarousel.itemWidth);
}

- (BOOL)carouselShouldWrap:(iCarousel *)carousel
{
    //wrap all carousels
//    return NO;
    return wrap;
}

- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index {
    if (index == [self.deptCarousel currentItemIndex]) {
        ProductCategoriesViewController *prodCatView = [[ProductCategoriesViewController alloc] initWithNibName:IS_IPAD ? 
                                                        @"ProductCategoriesView~iPad" : @"ProductCategoriesView" bundle:nil];
        Category *categoryObj = [_categorymAr objectAtIndex:index];
        [prodCatView setStrTitle:categoryObj.categoryName];
        [prodCatView setCategoryId:categoryObj.categoryId];
        [Flurry logEvent:@"Category List" withParameters:[NSDictionary dictionaryWithObjectsAndKeys:categoryObj.categoryName,[NSString stringWithFormat:@"%d",categoryObj.categoryId], nil]];
        [self.navigationController pushViewController:prodCatView animated:YES];
        [prodCatView release];
    }
}

-(void) carouselDidScroll:(iCarousel *)carousel{

//    [_deptCarousel scrollToItemAtIndex:[self.deptCarousel currentItemIndex]+3 animated:YES];

//    [_deptCarousel scrollByNumberOfItems:1 duration:1];

}

- (void)carouselCurrentItemIndexUpdated:(iCarousel *)carousel{

    _spacePageControl.currentPage = [self.deptCarousel currentItemIndex];
}

- ( IBAction ) myCart {
    if ( [[DBModel database] isShoppingListEmpty] ) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"at_shopping_cart", @"") 
                                                        message:NSLocalizedString(@"amsg_shopping_cart_empty", @"")
                                                       delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"") otherButtonTitles:nil];
        [alert show];
        [alert release];
        return;
    }
    MyCartViewController *myCartView = [[MyCartViewController alloc] initWithNibName:IS_IPAD ? @"MyCartView~iPad" : @"MyCartView" bundle:nil];
    [self.navigationController pushViewController:myCartView animated:YES];
    [myCartView release];
}

3 ответа

Решение

Первый:

у вас есть возможная и видимая утечка, но я не уверен, что это та же самая утечка, которую вы обнаружили в приборах:

Эти две строки находятся в вашем методе viewDidLoad

_carouselItems      = [[NSMutableArray alloc] initWithCapacity:1];
_categorymAr        = [[NSMutableArray alloc] initWithCapacity:1];

Но: viewDidLoad: вызывается каждый раз, когда представление загружается его контроллером. Если контроллер очищает представление (например, после предупреждения памяти), во втором viewDidLoad ваш _carouselItems а также _categorymAr Переменные экземпляра потеряют ссылку на ранее созданный NSMutableArray, что приведет к утечке

Итак, измените эти строки и используйте синхронизированные сеттеры:

self.carouselItems      = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
self.categorymAr        = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

Синтезированный сеттер настроен на освобождение предыдущего объекта перед назначением нового.

Тем не менее: возможно, что у вас есть еще одна утечка. Если вы можете просто воспроизвести утечку (если я понимаю, что утечка просто перемещается из одного VC в другой), вы можете использовать функцию "heapshot" инструментов.

Предполагая, что ваша утечка движется от первого ВК ко второму и возвращается:

  • открыть инструменты с помощью инструмента выделения
  • перейти от первого ВК ко второму и вернуться.
  • нажмите "пометить кучу" слева. Линия появится.
  • Снова перейдите от первого ВК ко второму и вернитесь.
  • снова нажмите "heapshot"
  • сделать это несколько раз (9-10)

инструмент heapshot делает "снимок" живых объектов в тот момент, когда вы нажали кнопку, и показывает только разницу. Если появится 2-3 новых объекта, вы увидите это в списке.

Это хорошая отправная точка для расследования утечки. Посмотрите на прикрепленное изображение:

Анализ кучи

Учтите, что вы должны пометить кучу несколько раз и различить "ложное срабатывание", глядя на созданный объект, в моем примере вы можете увидеть возможную утечку (heapshot5, 1,66KB), но после просмотра содержимого это не -> это была фоновая задача, которая началась в тот момент.

Более того, задержки пула автоматического выпуска и кеша некоторых объектов UIKit могут показать что-то в heapshot, поэтому я и говорю попробовать несколько раз.

Сначала, как отмечалось ранее, используйте ARC. Вы не могли бы сделать ничего, что улучшило бы управление памятью.

Независимо от того, используете ли вы ARC или нет, вы всегда должны использовать средства доступа для доступа к своим иварам (кроме init а также dealloc). Как отмечает @LombaX, вы неправильно настраиваете свои ивары в viewDidLoad, Использование аксессоров поможет этому.

Вы должны запустить статический анализатор, который поможет вам найти другие ошибки памяти.

Я подозреваю, что у вас есть IBOutlet это настроено как retain и что вы не выпускаете в dealloc, Это наиболее вероятная причина утечек, которые я вижу на ваших скриншотах. ARC обычно устраняет такие проблемы автоматически.

Вполне возможно, что у вас есть цикл сохранения. Это обычно не будет отображаться как утечка. Вы должны использовать heapshot для расследования этого. Ваши утечки довольно маленькие; они не могут быть фактической причиной предупреждений памяти. Что вы хотите исследовать (с помощью инструмента Allocations), это то, что на самом деле значительно увеличивает использование вашей памяти.

Но сначала АРК. Потом аксессоры. Затем удалите все предупреждения о сборке. Затем удалите все предупреждения статического анализатора. Затем используйте инструмент Allocations.

Примечание: тот факт, что ответственной стороной является "UIKit", не означает, что это ошибка в UIKit. Это просто означает, что UIKit выделил память, которая была позже просочилась. Причиной утечки может быть в другом месте. (Тем не менее, в UIKit есть несколько небольших утечек. В общем, они не должны доставлять вам хлопот, но вы никогда не сможете избавиться от 100% мелких утечек в приложении для iOS.)

Один из простых способов определить, откуда происходят ваши утечки, - использовать расширенное представление инструментов.

Для этого нажмите "Вид"->"Расширенные сведения", и появится правильное меню с трассировкой стека "утечки". Там вы легко найдете код утечки для каждой утечки и, если они происходят из вашего приложения.

Другие вопросы по тегам