2

I'm building a spectrograph and would like to know how I can improve the performance of my UIView-based code. I know that I cannot update user interface for iPhone/iPad from a background thread, so I'm doing most of my processing using GCD. The issue that I'm running into is that my interface still updates way too slowly.

With the code below, I'm trying to take 32 stacked 4x4 pixel UIViews and change their background color (see the green squares on the attached image). The operation produces visible lag for other user interface.

Is there a way I can "prepare" these colors from some kind of background thread and then ask the main thread to refresh the interface all at once?

enter image description here

//create a color intensity map used to color pixels
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    colorMap = [[NSMutableDictionary alloc] initWithCapacity:128];

    for(int i = 0; i<128; i ++)
    {
        [colorMap setObject:[UIColor colorWithHue:0.2 saturation:1 brightness:i/128.0 alpha:1] forKey:[NSNumber numberWithInt:i]];
    }


});

-(void)updateLayoutFromMainThread:(id)sender
{
    for(UIView* tempView in self.markerViews)
    {
        tempView.backgroundColor =[colorMap objectForKey:[NSNumber numberWithInt:arc4random()%128]];
    }

}
//called from background, would do heavy processing and fourier transforms
-(void)updateLayout
{

    //update the interface from the main thread
    [self performSelectorOnMainThread:@selector(updateLayoutFromMainThread:) withObject:nil waitUntilDone:NO];


}

I ended up pre-calculating a dictionary of 256 colors and then asking the dictionary for the color based on the value that the circle is trying to display. Trying to allocate colors on the fly was the bottleneck.

jscs
  • 63,694
  • 13
  • 151
  • 195
Alex Stone
  • 46,408
  • 55
  • 231
  • 407
  • 6
    So you benchmarked your app and this turned out to be the bottleneck, right? –  May 11 '13 at 11:23
  • 1
    You indicate that your views are stacked. Do you mean that only one of them is visible? If yes, the solution is probably to defer all manipulations of non-visible views except for the next one (or the next few if your user can switch very quickly through the individual views). There is a video from WWDC 2012 that deals with building concurrent user interfaces. – Paul May 11 '13 at 13:36
  • Pretty much I have 256 columns each one having 32 UIViews (4px by 4px). By stacked I mean they occupy 128 pixels in 4 pixel increments. Each 200ms or so I want to do Fast Fourier Transform and use these views to color code the resulting frequencies ( simply changing their background color). – Alex Stone May 11 '13 at 15:53
  • this sounds like you try to emulate a plot with many many views... why don't just have 1 view and that one view really draws a real plot... you could look into coreplot – Daij-Djan May 11 '13 at 15:55
  • Core plot has abysmal performance and would not be able to update itself in 200 ms. I tried it on several other occasions. – Alex Stone May 11 '13 at 15:59
  • 3
    You are using too many views. A UIView is not a lightweight object and managing that many views on screen will kill performance. As other commenters have suggested, investigate drawing using core graphics, or using layers instead of views, for the sub-components of each bar at least. – jrturton May 11 '13 at 16:53

1 Answers1

1

, Yes, a couple of points.

While you shouldn't process UIView on the main thread, you can instantiate views on a background thread before using them. Not sure if that will help you at all. However beyond instantiating a view on a background thread, UIView's are really just a meta-data wrapper for CALayer objects and are optimised for flexibility rather than performance.

Your best bet is to draw to a layer object or an image object on a background thread (which is a slower process because drawing uses the CPU as well as the GPU), pass the layer object or image to the main thread, then draw the pre-rendered image to your view's layer (much faster because a simple call is made to get the Graphics Processor to blit the image to the UIView's backing store directly).

see this answer:

Render to bitmap then blit to screen

The code:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, rect, image);
}

executes far faster than if you were to execute other drawing operations, such as drawing bezier curves, in the same method.

Community
  • 1
  • 1
TheBasicMind
  • 3,585
  • 1
  • 18
  • 20
  • dont think this helps.. he is approaching the whole thing wrong IMO – Daij-Djan May 11 '13 at 15:56
  • Replacing unviews with view controllers managing CAlayers did speed up performance significantly, because I can do all processing in background and then just fill a bunch or rectangles with precomputed colors – Alex Stone May 15 '13 at 00:05