2

I have custom UICollectionViewCell subclass where I draw with clipping, stroking and transparency. It works pretty well on Simulator and iPhone 5, but on older devices there is noticeable performance problems.

So I want to move time-consuming drawing to background thread. Since -drawRect method is always called on the main thread, I ended up saving drawn context to CGImage (original question contained code with using CGLayer, but it is sort of obsolete as Matt Long pointed out).

Here is my implementation of drawRect method inside this class:

-(void)drawRect:(CGRect)rect {

    CGContextRef ctx = UIGraphicsGetCurrentContext();
    if (self.renderedSymbol != nil) {
        CGContextDrawImage(ctx, self.bounds, self.renderedSymbol);
    }
}

Rendering method that defines this renderedSymbol property:

- (void) renderCurrentSymbol {

    [self.queue addOperationWithBlock:^{

// creating custom context to draw there (contexts are not thread safe)
        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
        CGContextRef ctx = CGBitmapContextCreate(nil, self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width * (CGColorSpaceGetNumberOfComponents(space) + 1), space, kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(space);

// custom drawing goes here using 'ctx' context

// then saving context as CGImageRef to property that will be used in drawRect
        self.renderedSymbol = CGBitmapContextCreateImage(ctx);

// asking main thread to update UI    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self setNeedsDisplayInRect:self.bounds];
        }];

        CGContextRelease(ctx);

    }];
}

This setup works perfectly on main thread, but when I wrap it with NSOperationQueue or GCD, I'm getting lots of different "invalid context 0x0" errors. App doesn't crash itself, but drawing doesn't happen. I suppose there is a problem with releasing custom created CGContextRef, but I don't know what to do about it.

Here's my property declarations. (I tried using atomic versions, but that didn't help)

@property (nonatomic) CGImageRef renderedSymbol;
@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, strong) NSString *symbol; // used in custom drawing

Custom setters / getters for properties:

-(NSOperationQueue *)queue {
    if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.name = @"Background Rendering";
    }
    return _queue;
}   
-(void)setSymbol:(NSString *)symbol {
    _symbol = symbol;
    self.renderedSymbol = nil;
    [self setNeedsDisplayInRect:self.bounds];
}

-(CGImageRef) renderedSymbol {
    if (_renderedSymbol == nil) {
        [self renderCurrentSymbol];
    }
    return _renderedSymbol;
}

What can I do?

Rinat Khanov
  • 1,566
  • 10
  • 32
  • 2
    your first `drawInRect` will end up drawing a NULL-`CGLayer`, as you calling `renderCurrentSymbol` asynchronous in `renderedSymbol` but returning `_renderedSymbol` without delay. – Jonathan Cichon May 14 '13 at 14:11
  • Have you tried not doing CGContextRelease(ctx) until you also release the CGLayer (presumably in dealloc?) – Mike Pollard May 14 '13 at 14:12
  • @MikePollard Releasing the context is correct, if you do not release the context you will have a Memoryleak. – Jonathan Cichon May 14 '13 at 14:15
  • Yes, I'm not suggesting not releasing it, but releasing it in dealloc, which presumably is where the CGLayer is being released, otherwise that is leaking at the moment. – Mike Pollard May 14 '13 at 14:18
  • 1
    **invalid context 0x0** normaly means that either your `CGContext` is nil or its size is Zero. You should debug your program for this two things. – Jonathan Cichon May 14 '13 at 14:25
  • I think the first comment by @JonathanCichon is a good spot. – Mike Pollard May 14 '13 at 14:26
  • Jonathan, as far as I know if I'll wait `renderCurrentSymbol` completion, this will pause my current thread (i.e. main thread). Not big advance in using multithreading. – Rinat Khanov May 14 '13 at 14:27
  • No, but you should test `self.renderedSymbol` for not being nil in your `drawRect`. After your background-drawing is complete, `drawRect` will be called again with `self.renderedSymbol` set. – Jonathan Cichon May 14 '13 at 14:42
  • I updated code that uses CGImage instead of CGLayer and checks renderedSymbol in drawRect method (see new code in question). Sadly, it didn't help, I am still getting this **invalid context 0x0** errors. But that was good catch, thanks! If you have any thoughts how to fix this, please share. – Rinat Khanov May 14 '13 at 15:13
  • @RinatKhanov: What are you “wrap[ping] with NSOperationQueue or GCD”? And where are you getting the “invalid context” error: in `drawRect:`, or in `renderCurrentSymbol`? – Peter Hosey May 14 '13 at 17:41

2 Answers2

1

Did you notice the document on CGLayer you're referencing hasn't been updated since 2006? The assumption you've made that CGLayer is the right solution is incorrect. Apple has all but abandoned this technology and you probably should too: http://iosptl.com/posts/cglayer-no-longer-recommended/ Use Core Animation.

Matt Long
  • 24,438
  • 4
  • 73
  • 99
  • Whoa. Thanks! Before CGLayer I tried to do the same with saving context to CGImageRef (like explained in this question on [Stack Overflow](http://stackoverflow.com/questions/14606943/asynchronous-drawing-and-touches)), but it didn't work either. Still getting this **0x0 invalid context** errors – Rinat Khanov May 14 '13 at 14:35
  • I would not say CGLayer is not the right solution, it 'might' not be, but it can be. – Jonathan Cichon May 14 '13 at 14:44
  • @RinatKhanov I suggest you ask your question from a higher level and see what people think is the best approach. From your question it sounds to me like you could just composite multiple Core Animation layers and let it do all the hard work for you. I've created many custom collection view cells and they've used things like clipping stroking and opacity. You might want to look at CALayer's shouldRasterize property. It will improve drawing performance. You might even consider using images instead of drawing programmatically as this can improve performance as well. – Matt Long May 14 '13 at 14:57
  • @MattLong In collection view I need thousand rounded rects (with stroking and transparent corners) with text symbol or image on top of it. I need that symbol in other parts of application as text representation, so using images isn't solution for my particular situation. I tried to turn off that rounded rect (used as background), but surprisingly app feels slow on old devices while scrolling anyway. That's why I try to handle heavy rendering to background thread. I'll take a look at CALayer, thanks. – Rinat Khanov May 14 '13 at 15:26
1

Issue solved by using amazing third party library by Mind Snacks — MSCachedAsyncViewDrawing.

Rinat Khanov
  • 1,566
  • 10
  • 32