6

I have a view with some very complex drawing logic (it's a map view that draws from GIS data). Doing this drawing on the main thread locks up the UI and makes the app unresponsive. I want to move away the drawing to a background thread with for example an NSOperation.

What is the best way to structure this?

I am currently drawing to an off memory CGContext and then convert that to a CGImageRef which I send to the view to blit on the main thread. Unfortunately this uses up a lot of memory and it seems that the GPU acceleration is no longer used as it is quite a bit slower. Is there some way of drawing directly to the view from a background thread? I know that UIKit isn't multi-thread safe but maybe there is some way of locking the view while I'm doing the drawing?

Jon Tirsen
  • 4,750
  • 4
  • 29
  • 27

2 Answers2

8

Beyond iOS 4.0, drawing is thread safe. You might need to create a CGContext yourself, but there is no reason you cannot draw on a background thread.

That said, most UIKit operations are not. If you need to do those, you can always prepare in a background thread, and then use performOnMainThread when needed. There's even waitUntilDone. That said, you should use NSOperation and NSOperationQueue, apparently.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 2
    There seems to be a lot of confusion about this but this is the correct answer, as of iOS 4.0 drawing with UIKit is thread safe. Here is the official announcement of this: http://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html (search for "thread"). – Jon Tirsen Oct 31 '11 at 11:20
0

I have never came across such situation on iPhone but on Mac I had similar problem once.

  1. Use CGLayer to delegate drawing activity of offline contexts.
  2. Try add timer for current NSRunLoop and on timed interval execute your graphic commands. It should look something like this...

...

kRenderFPS 25.0 //This is Maximum value

renderTimer = [[NSTimer timerWithTimeInterval:(1.0 / (NSTimeInterval)kRenderFPS) target:viewobject selector:@selector(RenderUI:) userInfo:nil repeats:YES] retain];


[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSDefaultRunLoopMode];


[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSModalPanelRunLoopMode];


[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSEventTrackingRunLoopMode];

//In view class
-(void)RenderUI:(id)param
{
    [self setNeedsDisplayInRect:[self bounds]];
}

This should do the trick.

Also try to sample your process and check who is consuming the CPU. This will make UI responsive and very fast.

The performance problem you mentioned is can be due to something else. Try to cpu sample process. It will give you insight on who is actually taking CPU.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
RLT
  • 4,219
  • 4
  • 37
  • 91
  • 1
    No. CGLayer. http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGLayer/Reference/reference.html – RLT Oct 14 '11 at 08:45
  • So I'm trying this approach now but even if I run with an NSTimer and run only 50% of the time (run on 0.2s intervals for max 0.1s per drawing cycle) it seems it's still ignoring all UI events. Is there a trick to this? – Jon Tirsen Nov 09 '11 at 09:30
  • Which style did you tried? CGLayer or NSRunLoop or both? Use CGLayer to delegate offscreen drawing and NSRunloop to execute drawing commands on timed interval. – RLT Nov 09 '11 at 09:54
  • I notice someone downvoted my answer but did not put any explanation why -1. Its not Cool. – RLT Nov 09 '11 at 09:55
  • I used both CGLayer and NSRunLoop. Now I'm trying CGLayer with NSOperation (i.e. on a background thread). I'm getting pretty horrible performance with the background thread though. (Although the UI is now responsive.) – Jon Tirsen Nov 09 '11 at 14:59
  • I will add more details to my answer. – RLT Nov 09 '11 at 15:01
  • What is the benefit of using CGLayer over a CGBitmapContext? When you create your CGLayer do you use the UIGraphicsCurrentContext from the drawRect or do you create a CGBitmapContext and then create the CGLayer based on that? – Jon Tirsen Nov 09 '11 at 15:15
  • Use CGBitmapContext only when you need to create a bitmap out of it. If its offline context use CGLayer. CGLayer objects are useful for offscreen drawing and can be used in much the same way that a bitmap context can be used. In fact, a CGLayer object is a much better representation than a bitmap context. – RLT Nov 09 '11 at 15:21
  • Using CGLayer objects can improve performance, particularly when you need to capture a piece of drawing that you stamp repeatedly (using the same scale factor and orientation). Quartz can cache CGLayer objects to the video card, making drawing a CGLayer to a destination much faster than rendering the equivalent image constructed from a bitmap context. – RLT Nov 09 '11 at 15:22
  • Ok, it was my fault, I was calling setNeedsDisplay on the background thread. Maybe someone with enough privileges can clean up these comments. :-) – Jon Tirsen Nov 09 '11 at 15:55
  • @Rahul I removed my -1 vote since you edited your answer. At the time it was not even an answer. – Dan Rosenstark Nov 14 '11 at 18:07
  • @Yar- I don't mind down vote. Reason for down vote would be nice so that answer can be updated if anything is not clear. – RLT Nov 14 '11 at 18:33