Сбой памяти iOS UIScrollview с асинхронной загрузкой изображения

Я представляю прокрутку изображений с двумя столбцами, похожую на Pinterest, с использованием PSCollectionView https://github.com/ptshih/PSCollectionView и загружая изображения асинхронно с помощью https://github.com/rs/SDWebImage

На iPhone 5 оно работает непрерывно без сбоев, но на iPhone 4 я сталкиваюсь с предупреждениями о памяти, и при постоянной прокрутке приложение в конечном итоге падает.

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

Я запустил профилирование памяти, но не вижу ничего очевидного — наиболее интенсивное использование памяти происходит при SDWebImage асинхронной загрузке изображений. Похоже, что метод objectForKey для получения данных из моего PFObjects (с parse.com) также занимает хороший кусок памяти.

Я прикрепил ниже, в частности, фрагмент кода, который устанавливает содержимое каждой PSCollectionView ячейки.

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

- (UIView *)collectionView:(PSCollectionView *)collectionView cellForRowAtIndex:(NSInteger)index withRect:(CGRect) recttouse {


    FPCollectionViewCell *thefpcell = (FPCollectionViewCell *)
    [collectionView dequeueReusableViewForClass:[FPCollectionViewCell class]];

    thefpcell.frame = recttouse;

    if (thefpcell == nil) {


        thefpcell = [[FPCollectionViewCell alloc] initWithFrame:recttouse];

        //NSLog(@"creating this cell: %i", index);

        UILabel *cellText = [[UILabel alloc] initWithFrame:CGRectMake(2,0,143,20)];

        thefpcell.cellText = cellText;
        thefpcell.cellText.textAlignment = NSTextAlignmentCenter;


        UIImageView *cellImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,20,recttouse.size.width, recttouse.size.height-20)];

        thefpcell.cellImage = cellImage;

         thefpcell.backgroundColor = [UIColor whiteColor];
        thefpcell.cellText.font = [UIFont fontWithName:@"HelveticaNeue-LightItalic" size:9];

        thefpcell.cellText.backgroundColor = [UIColor clearColor];


        [thefpcell addSubview:cellText];
        [thefpcell addSubview:cellImage];

        }

    //@Brian note--consider moving more of this to other blocks to try and improve performance
    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{

            PFObject *thisobj = [self.contentObjectsArray objectAtIndex:index];

         thefpcell.cellText.frame = CGRectMake(2,0,143,20);


        thefpcell.cellImage.frame = CGRectMake(0,20,recttouse.size.width, recttouse.size.height-20);


        thefpcell.cellText.text = [thisobj objectForKey:@"Caption"];

        NSString *imglink = [thisobj objectForKey:@"imgLink"];
        NSString *imgurl;
        if(imglink.length<2)
        {
            PFFile *mydata = [thisobj objectForKey:@"imageFile"];
            imgurl = mydata.url;

        }
        else
        {
            imgurl =imglink;
        }

              UIImage *cellplaceholder = [UIImage imageWithContentsOfFile:@"placeholder.png"];
        [thefpcell.cellImage setImageWithURL:[NSURL URLWithString:imgurl] placeholderImage:cellplaceholder];


        thefpcell.layer.cornerRadius = 9.0;
        thefpcell.layer.masksToBounds = YES;

        }]];


    //NSLog(@"got it: %i", index);

    return thefpcell;
}

person BrianAllenToronto    schedule 24.11.2013    source источник


Ответы (2)


Хм. Здесь нет ничего очевидного.

SDWebImage регистрируется для UIApplicationDidReceiveMemoryWarningNotification (что важно, потому что в iOS7 NSCache не реагирует на нехватку памяти, как раньше). Вы можете подтвердить это, поместив точку останова в clearMemory в SDImageCache и запустив приложение, имитируя/воспроизводя предупреждение памяти и подтверждая, и подтверждая, что это вызывается. Но я думаю, вы обнаружите, что это так. Я не могу говорить о том, как parse.com обрабатывает тот факт, что NSCache больше не очищается автоматически, как это было раньше.

Вы должны сделать несколько снимков/генераций в Instruments, как показано в видео WWDC 2012 Производительность приложений iOS : Память. Итак, запустите приложение, доведите его до точки покоя, смоделируйте предупреждение памяти, отметьте heapshot/генерацию, немного используйте приложение, снова смоделируйте предупреждение памяти и снова отметьте heapshot/генерацию. Затем посмотрите на объекты, выделенные и все еще живущие между двумя поколениями, и вы будете на пути к определению того, что не высвобождается на вас. Вы, вероятно, сможете определить, есть ли что-то, что parse.com не очищает, или это находится в вашем коде или в SDWebImage.


Пара несвязанных наблюдений:

  1. Это может показаться любопытным советом, учитывая, что вы пытаетесь уменьшить нагрузку на память, но cellplaceholder, вероятно, является единственной ситуацией, когда вы действительно хотите использовать imageNamed, а не imageWithContentsOfFile. Предполагая, что этот заполнитель будет часто нужен, он (а) будет быстрее; и (б) может на самом деле незначительно снизить вашу максимальную отметку, если заполнитель временно используется часто (вместо того, чтобы иметь кучу недолговечных экземпляров одного и того же заполнителя).

  2. Это крайне маловероятно, но если вы собираетесь обновлять ячейку асинхронно самостоятельно, вам действительно следует повторно получить ячейку представления коллекции, чтобы убедиться, что эта ячейка не прокручивается за пределы экрана (вызов [collectionView cellForItemAtIndexPath:indexPath], не путать с метод с таким же названием UICollectionViewDataSource) и убедитесь, что это не nil).

    Честно говоря, я не уверен, почему вы все равно отправляете это обратно в основную очередь. SDWebImage уже будет извлекать изображения асинхронно, поэтому вам обычно не нужно выполнять какую-либо асинхронную обработку самостоятельно, и если вам действительно нужна какая-то асинхронная обработка, вы должны отправить ее в какую-то фоновую очередь (а не обратно в основную очередь).

    Независимо от этой конструкции:

    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
        // ...
    }]];
    

    можно упростить до

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // ...
    }];
    
person Rob    schedule 24.11.2013
comment
Большое спасибо за ваш ответ. Я обязательно воспользуюсь вашим советом по поводу изображения-заполнителя. Я запущу генерацию кучи и добавлю к ней профиль, чтобы было больше информации. Есть ли что-нибудь, что, по вашему мнению, я должен сделать с пулами авторелиза и UIImages, загружаемыми SDWebImage? - person BrianAllenToronto; 25.11.2013
comment
Я запустил профилировщик, и для диапазона времени, когда я открываю прокрутку, 7,18 МБ или 59,2% используемых байтов используются CAKeyframeanimation CA_preparerendervalue. 30% используемых байтов предназначены для SDImageCache DiskImageForKey. На этот раз, запустив профилирование, я достиг большего предела памяти, чем когда-либо раньше (примерно 8 мегабайт живых байтов), но он никогда не получал предупреждения о памяти. Я буду тестировать дальше, но единственным изменением было изменение размера изображений перед загрузкой из этого потока (stackoverflow.com/questions/12928103/) - person BrianAllenToronto; 25.11.2013
comment
@BrianAllenToronto Нет, я не думаю, что вам следует что-то делать с пулом автоматического освобождения (это полезно при настройке максимальной отметки, а не для уменьшения общего объема памяти, используемой при выполнении). Но изменение размера изображений до соответствующего размера для вашего пользовательского интерфейса будет иметь огромное преимущество. Это определенно хорошая линия для продолжения. Если это полезно, это процедуры изменения размера изображения, которые я использую. - person Rob; 25.11.2013
comment
очень хороший класс вы написали там! Я использовал что-то похожее, но немного менее организованное, чем ваш класс. Вы были правы, изменение размера имело огромное значение! - person BrianAllenToronto; 25.11.2013

Похоже, что это простое изменение размера изображений перед их загрузкой из кеша sdwebimage решило мою проблему. Образы процесса SDWebImage перед кэшированием Я, должно быть, столкнулся с каким-то лимитом пакетов iOS на мой iPhone 4 с точки зрения того, сколько изображений большого размера можно быстро загрузить одновременно.

Мой последний профиль памяти показывает, что кеш, кажется, сбрасывается правильно после достижения высокого порога памяти, и кажется, что он приближается к уровню сбоя только тогда, когда попадает что-то вроде 4 или 5 GIF-файлов подряд.

Большое спасибо за помощь!

person BrianAllenToronto    schedule 25.11.2013