Может ли вызов dawRect: в фоновом потоке вызвать сбой?

У меня есть несколько тяжелых операций рисования пользовательского интерфейса, поэтому я передал их фоновому потоку. Около 100% сбоев, о которых я сообщил, произошли во время этой операции. Когда отрисовка выполнялась в основном потоке, таких проблем не возникало, код просто не изменялся.

Есть ли риск рисования на заднем плане?

(Я заполняю содержимое UIScrollView, может быть там проблема?)


person Geri Borbás    schedule 18.02.2012    source источник


Ответы (1)


Во-первых, вы не должны сами звонить drawRect:, UIKit сделает это за вас. Вы должны позвонить setNeedsDisplay. Во-вторых, UIKit не является потокобезопасным, поэтому вызов любых операций рисования UIKit в любом потоке, кроме основного, может привести к сбою вашего приложения, как вы испытали.

Однако CoreGraphics является потокобезопасным, если вы создаете контекст для рисования в себе, а затем используете только вызовы CoreGraphics. Итак, что вы можете сделать, так это выполнить трудоемкое рисование в фоновом потоке с помощью CoreGraphics, где вы рисуете в контексте изображения и сохраняете изображение в переменной экземпляра. Затем вызовите setNeedsDisplay в основном потоке и просто отобразите визуализированное изображение в своем drawRect: методе.

Итак, в псевдокоде (версия Core Graphics):

- (void)redraw
{
    [self performSelectorInBackground:@selector(redrawInBackground) withObject:nil];
}

- (void)redrawInBackground
{
    CGImageRef image;
    CGContextRef context;

    context = CGBitmapContextCreate(..., self.bounds.size.width, self.bounds.size.height, ...);

    // Do the drawing here

    image = CGBitmapContextCreateImage(context);

    // This must be an atomic property.
    self.renderedImage:[UIImage imageWithCGImage:image]];

    CGContextRelease(context);
    CGRelease(image);

    [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
}

- (void)drawRect:(CGRect)rect
{
    [self.renderedImage drawAtPoint:CGPointMake(0,0)];
}

Версия UIKit будет:

- (void)redrawInBackground
{
    UIGraphicsBeginImageContext(self.bounds.size);

    // Do the drawing here.

    self.renderedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
}
person DarkDust    schedule 18.02.2012
comment
Что нового в iOS: iOS 4.0: Улучшения UIKit Framework: «Рисование в графическом контексте в UIKit теперь является потокобезопасным». - person rob mayoff; 19.02.2012
comment
Однако передача графического контекста, предоставляемого UIKit, между потоками по-прежнему небезопасна. - person rob mayoff; 19.02.2012
comment
Спасибо за разъяснение, я это пропустил. - person DarkDust; 19.02.2012
comment
Спасибо, я не звоню drawRect: себе, а там занимаюсь тяжелым лифтингом. - person Geri Borbás; 19.02.2012
comment
Я сделаю все в основном потоке, касающемся вызовов UIKit, единственное, что я могу передать в фоновый режим (если я вас понял), чтобы выполнять настраиваемое рисование в отдельных контекстах, а затем передать результат UIImage в основной поток (он же заполнить элементы UIKit ). - person Geri Borbás; 19.02.2012
comment
Пожалуйста, подтвердите, кажется ли этот подход потокобезопасным, и еще раз спасибо. - person Geri Borbás; 19.02.2012
comment
См. Мой отредактированный ответ. Я добавил псевдокод, чтобы проиллюстрировать описываемый мной метод. - person DarkDust; 19.02.2012
comment
setNeedsDisplay потокобезопасен? вы вызываете его в фоновом потоке - person Bryan Chen; 25.02.2013
comment
@ xlc0212 Вы правы, я исправил код. По моему опыту, и на Mac, и на iOS не проблема вызвать _1 _ / _ 2_ в другом потоке ... он просто ничего не делает (то есть представление не перерисовывается). - person DarkDust; 25.02.2013