Должен ли drawInRect: для отдельного контекста выполняться в основном потоке? - PullRequest
1 голос
/ 09 июля 2010

[обновление: эта проблема была решена;проблема была не в drawInRect:, а в UIGraphicsBeginImageContext()]

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

Обратите внимание, что я делаю это в отдельном контексте изображения - это , а не о перерисовке UIView на экране.

Этот коддовольно интенсивно, поэтому я запускаю его в отдельном потоке.Фактическое масштабирование выглядит следующим образом и является реализацией категории поверх UIImage:

- (UIImage *) scaledImageWithWidth:(CGFloat)width andHeight:(CGFloat)height
{
    CGRect rect = CGRectMake(0.0, 0.0, width, height);
    UIGraphicsBeginImageContext(rect.size);
    [self drawInRect:rect]; // <-- crashing on this line
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

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

UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];

Это все работает большую часть времени, но иногда я получаю EXC_BAD_ACCESS.

Backtrace:

#0  0x330d678c in ripc_RenderImage ()
#1  0x330dd5aa in ripc_DrawImage ()
#2  0x300e3276 in CGContextDelegateDrawImage ()
#3  0x300e321a in CGContextDrawImage ()
#4  0x315164c8 in -[UIImage drawInRect:blendMode:alpha:] ()
#5  0x31516098 in -[UIImage drawInRect:] ()
#6  0x0000d6e4 in -[UIImage(Scaling) scaledImageWithWidth:andHeight:] (self=0x169320, _cmd=0x30e6e, width=48, height=64) at /Users/me/Documents/svn/app/trunk/Classes/UIImage+Scaling.m:20
#7  0x00027df0 in -[mgMinimap loadThumbnails] (self=0x13df00, _cmd=0x30d05) at /Users/me/Documents/svn/app/trunk/Classes/mgMinimap.m:167
#8  0x32b15bd0 in -[NSThread main] ()
#9  0x32b81cfe in __NSThread__main__ ()
#10 0x30c8f78c in _pthread_start ()
#11 0x30c85078 in thread_start ()

[обновление 4] Когда я запускаю это в симуляторе, и эта проблема возникает, консоль дополнительно показывает следующее:

// the below is before loading the first thumbnail
<Error>: CGContextSaveGState: invalid context
<Error>: CGContextSetBlendMode: invalid context
<Error>: CGContextSetAlpha: invalid context
<Error>: CGContextTranslateCTM: invalid context
<Error>: CGContextScaleCTM: invalid context
<Error>: CGContextDrawImage: invalid context
<Error>: CGContextRestoreGState: invalid context
<Error>: CGBitmapContextCreateImage: invalid context
// here, the first thumbnail has finished loading and the second one
// is about to be generated
<Error>: CGContextSetStrokeColorWithColor: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context

Мне кажется, что я иногда пытаюсь drawInRect: в то время как ОС также пытается что-то нарисовать, что иногда приводит к сбою.Я всегда предполагал, что пока вы не рисуете на реальном экране, это приемлемо - разве это не так?Или, если это так, есть идеи, что может быть причиной этого?

Обновление (r2): я забыл упомянуть, что это приложение работает с довольно жесткими ограничениями памяти (у меня загружено много изображенийв любой момент времени, и они поменялись местами в / из), так что это может быть случай нехватки памяти (читать дальше - это не так).Я не уверен, как это проверить, поэтому мысли об этом тоже будут приветствоваться.Я проверил это, строго сократив количество загружаемых изображений и добавив проверку, чтобы убедиться, что они правильно освобождены (они есть, и сбой все еще происходит).

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

Код (после того, как я разместил этот вопрос) иногда начинал выходить с кодом выхода 0, а иногда и с кодом выхода 10 (SIGBUS).0 означает «без ошибок», так что это было очень странно.10, кажется, означает всего понемногу, так что это тоже было бесполезно.Вызов drawInRect: был большой подсказкой, однако, когда там произошел сбой.

Цикл для получения миниатюр генерировал много автоматически выпущенных изображений.У меня был пул авто-релиза, но он оборачивал весь цикл for.Я добавил второй пул автоматического выпуска в цикле for:

- (void)loadThumbnails
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    for (...) {
        NSAutoreleasePool *cyclePool = 
           [[NSAutoreleasePool alloc] init]; // <-- here
        UIImage *bigger = ...;
        UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f];
        UIImage *bloated = [i scaledImageWithWidth:48.f andHeight:64.f];
        [cyclePool release]; // <-- ending here
    }
    [pool release];
}

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

Ответы [ 3 ]

2 голосов
/ 09 июля 2010

Вы смотрели на последний выпуск Мэтта Джеммелла, MGImageUtilities ? Я извлек это из его источника на github:

// Create appropriately modified image.
UIImage *image;
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

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

1 голос
/ 09 июля 2010

Оказывается, есть два ответа:

"Должны ли drawInRect: для отдельного контекста выполняться в главном потоке?"Ответ - нет, это не так.

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

Решением было прекратить использование UIGraphicsContext и вместо этого использовать CGBitmapContext, что является потокобезопасным.

0 голосов
/ 09 июля 2010

У меня такое ощущение, что вам не следует звонить drawInRect напрямую, так как это может быть небезопасно. Вы должны позвонить setNeedsDisplay в представлении, которое отправит сообщение на drawInRect, когда это будет безопасно.

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