UIImageJPEGRepresentation получил предупреждение о нехватке памяти

Я получаю предупреждение о памяти при использовании UIImageJPEGRepresentation, есть ли способ избежать этого? Это не приводит к сбою приложения, но я хотел бы избежать этого, если это возможно. Периодически не запускается [[UIApplication sharedApplication] openURL:url];

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
    NSData *imageToUpload = UIImageJPEGRepresentation(image, 1.0);

    // code that sends the image to a web service (omitted)
    // on success from the service
    // this sometime does not get run, I assume it has to do with the memory warning?
    [[UIApplication sharedApplication] openURL:url];
}

person Sam Luther    schedule 11.08.2014    source источник
comment
Используете подход ARC или не-ARC?   -  person Oleg Gordiichuk    schedule 11.08.2014
comment
Проблема вряд ли заключается в этом отдельном изображении, а скорее в сочетании со всем остальным, что вы можете делать (например, если вы одновременно держите в памяти много этих изображений).   -  person Rob    schedule 11.08.2014
comment
@Rob Я не знаю об этом, это довольно простое приложение, и я сузил его до той конкретной строки кода, которая увеличивает память с 6 МБ до более чем 50 МБ, а затем обратно до 6 МБ. Я также запускал остальную часть кода без этой конкретной строки и без предупреждения о памяти.   -  person Sam Luther    schedule 11.08.2014
comment
ХОРОШО. Использование 50 МБ не является возмутительным объемом памяти и, как правило, не должно приводить к фатальным ошибкам, хотя я уверен, что вы все равно сможете уменьшить пиковое использование памяти приложением. Кстати, вы говорите, что openURL не всегда запускается, но успешно ли завершается загрузка изображения? Думаю, я спрашиваю, определили ли вы проблему как openURL или, возможно, что-то до этого...   -  person Rob    schedule 12.08.2014
comment
@Rob извините за задержку ответа. Да, загрузка всегда завершается, но openURL нет.   -  person Sam Luther    schedule 13.08.2014


Ответы (3)


Использование UIImageJPEGRepresentation (в котором вы выполняете циклический обход ресурса через UIImage) может быть проблематичным, поскольку при использовании compressionQuality, равного 1,0, результирующий NSData может быть значительно больше, чем исходный файл. (Кроме того, у вас есть вторая копия изображения в UIImage.)

Например, я просто выбрал случайное изображение из фотобиблиотеки своего iPhone, исходный актив весил 1,5 МБ, но для NSData, созданного UIImageJPEGRepresentation с compressionQuality 1,0, требовалось 6,2 МБ. И само хранение изображения в UIImage может занять еще больше памяти (потому что в несжатом виде может потребоваться, например, четыре байта на пиксель).

Вместо этого вы можете получить исходный актив, используя метод getBytes:

static NSInteger kBufferSize = 1024 * 10;

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSURL *url = info[UIImagePickerControllerReferenceURL];

    [self.library assetForURL:url resultBlock:^(ALAsset *asset) {
        ALAssetRepresentation *representation = [asset defaultRepresentation];
        long long remaining = representation.size;
        NSString *filename  = representation.filename;

        long long representationOffset = 0ll;
        NSError *error;
        NSMutableData *data = [NSMutableData data];

        uint8_t buffer[kBufferSize];

        while (remaining > 0ll) {
            NSInteger bytesRetrieved = [representation getBytes:buffer fromOffset:representationOffset length:sizeof(buffer) error:&error];
            if (bytesRetrieved <= 0) {
                NSLog(@"failed getBytes: %@", error);
                return;
            } else {
                remaining -= bytesRetrieved;
                representationOffset += bytesRetrieved;
                [data appendBytes:buffer length:bytesRetrieved];
            }
        }

        // you can now use the `NSData`

    } failureBlock:^(NSError *error) {
        NSLog(@"assetForURL error = %@", error);
    }];
}

Это позволяет избежать помещения изображения в UIImage, а результирующий NSData может быть (во всяком случае, для фотографий) значительно меньше. Обратите внимание, что это также имеет то преимущество, что оно также сохраняет метаданные, связанные с изображением.

Между прочим, в то время как приведенное выше представляет собой значительное улучшение памяти, вы, вероятно, можете увидеть более существенную возможность сокращения памяти: в частности, вместо того, чтобы загружать весь ресурс в NSData за один раз, теперь вы можете передавать ресурс (подкласс NSInputStream для использования эта процедура getBytes для выборки байтов по мере необходимости, а не для загрузки всего в память за один раз). С этим процессом связаны некоторые неудобства (см. статью Б. Дж. Гомера на эту тему), но если вы ищете резкое сокращение объема памяти, то это именно то, что вам нужно. Здесь есть несколько подходов (BJ, использование некоторого промежуточного файла и потоковой передачи из него и т. д.), но ключ в том, что потоковая передача может значительно уменьшить объем памяти.

Но избегая UIImage в UIImageJPEGRepresentation (что позволяет избежать памяти, занимаемой изображением, а также большего NSData, которое дает UIImageJPEGRepresentation), вы можете значительно продвинуться вперед. Кроме того, вы можете захотеть убедиться, что у вас нет избыточных копий данных этого изображения в памяти одновременно (например, не загружайте данные изображения в NSData, а затем создайте второй NSData для HTTPBody... посмотрите, сможете ли вы сделать это одним махом). И если случится худшее, вы можете использовать потоковую передачу.

person Rob    schedule 11.08.2014
comment
большое спасибо за подробное объяснение, попробую - person Sam Luther; 13.08.2014
comment
Кстати, с новым Photos Framework вы теперь можете использовать requestImageDataForAsset для получения исходного NSData объекта. См. stackoverflow.com/a/27709329/1271826. - person Rob; 12.08.2015

в ARC: просто поместите свой код в небольшой блок @autoreleasepool

 @autoreleasepool {
     NSData *data = UIImageJPEGRepresentation(img, 0.5);
     // something with data
}
person user3684669    schedule 29.07.2016

Представлено как ответ на форматирование и изображения.

Используйте инструменты для проверки утечек и потери памяти из-за сохранения, но не утечки памяти. Последняя представляет собой неиспользуемую память, на которую все еще указывают. Используйте генерацию меток (Heapshot) в инструменте «Распределения» на «Инструменты».

Чтобы узнать, как использовать Heapshot для обнаружения утечки памяти, см.: ббум-блог

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

Чтобы выяснить результаты, откройте, чтобы увидеть отдельные распределения.

Если вам нужно увидеть, где происходит сохранение, освобождение и автоматическое освобождение объекта, используйте инструменты:

Запустите инструменты, в разделе «Распределения» установите «Записывать счетчики ссылок» (для Xcode 5 и ниже вы должны остановить запись, чтобы установить эту опцию). Запустите приложение, остановите запись, разверните детализацию, и вы сможете увидеть, где произошли все сохранения, выпуски и автоматические выпуски.

person zaph    schedule 11.08.2014
comment
спасибо за ответ, Заф, надо будет почитать. - person Sam Luther; 11.08.2014