From d957badc1b3a4fb3ed37ba8a79cb9df957d6611c Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Sun, 11 Nov 2012 12:59:40 -0800 Subject: [PATCH 1/5] Properties on views that are drawn on the background should be atomic --- .../MSAsyncDrawingCell.m | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m index 0d59f80..484646a 100644 --- a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m +++ b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m @@ -17,12 +17,14 @@ */ @interface MSCustomDrawnViewImageView : UIImageView -@property (nonatomic, strong) UIColor *circleColor; +@property (atomic, strong) UIColor *circleColor; @end @implementation MSCustomDrawnViewImageView +@synthesize circleColor = _circleColor; + - (void)setFrame:(CGRect)frame { const BOOL sizeHasChanged = !CGSizeEqualToSize(frame.size, self.frame.size); @@ -37,11 +39,22 @@ - (void)setFrame:(CGRect)frame - (void)setCircleColor:(UIColor *)circleColor { - if (circleColor != _circleColor) + @synchronized(self) { - _circleColor = circleColor; + if (circleColor != _circleColor) + { + _circleColor = circleColor; - [self setNeedsImageReload]; + [self setNeedsImageReload]; + } + } +} + +- (UIColor *)circleColor +{ + @synchronized(self) + { + return _circleColor; } } From a46c8e6e5f156f0483b1cc7c7885e6a193158919 Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Sun, 11 Nov 2012 13:07:27 -0800 Subject: [PATCH 2/5] Alternative: using NSOperationQueue instead of straight GCD to give the option to the clients of canceling the drawing operations. --- .../MSAsyncDrawingCell.m | 50 +++---- MSCachedAsyncViewDrawing.h | 12 +- MSCachedAsyncViewDrawing.m | 133 ++++++++++++------ 3 files changed, 126 insertions(+), 69 deletions(-) diff --git a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m index 484646a..d3eb38e 100644 --- a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m +++ b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m @@ -21,6 +21,12 @@ @interface MSCustomDrawnViewImageView : UIImageView @end +@interface MSCustomDrawnViewImageView () + +@property (nonatomic, strong) NSOperation *imageLoadOperation; + +@end + @implementation MSCustomDrawnViewImageView @synthesize circleColor = _circleColor; @@ -85,30 +91,26 @@ - (void)loadImage return; } - UIColor *currentCircleColor = self.circleColor; - CGSize currentSize = self.frame.size; - - NSString *cacheKey = [NSString stringWithFormat:@"com.mindsnacks.circle.%@.%@", currentCircleColor, NSStringFromCGSize(self.frame.size)]; - [[MSCachedAsyncViewDrawing sharedInstance] drawViewAsyncWithCacheKey:cacheKey - size:self.frame.size - backgroundColor:self.backgroundColor - drawBlock:^(CGRect frame) - { - MSCustomDrawnView *view = [[MSCustomDrawnView alloc] initWithFrame:frame - circleColor:currentCircleColor]; - - [view drawRect:frame]; - } - completionBlock:^(UIImage *drawnImage) - { - // Prevent race conditions: - BOOL receivedImageHasCurrentConfiguration = ([currentCircleColor isEqual:self.circleColor] && CGSizeEqualToSize(currentSize, self.frame.size)); - - if (receivedImageHasCurrentConfiguration) - { - self.image = drawnImage; - } - }]; + if (self.imageLoadOperation) + { + [self.imageLoadOperation cancel]; + } + + NSString *cacheKey = [NSString stringWithFormat:@"com.mindsnacks.circle.%@.%@", self.circleColor, NSStringFromCGSize(self.frame.size)]; + self.imageLoadOperation = [[MSCachedAsyncViewDrawing sharedInstance] drawViewAsyncWithCacheKey:cacheKey + size:self.frame.size + backgroundColor:self.backgroundColor + drawBlock:^(CGRect frame) + { + MSCustomDrawnView *view = [[MSCustomDrawnView alloc] initWithFrame:frame + circleColor:self.circleColor]; + + [view drawRect:frame]; + } + completionBlock:^(UIImage *drawnImage) + { + self.image = drawnImage; + }]; } @end diff --git a/MSCachedAsyncViewDrawing.h b/MSCachedAsyncViewDrawing.h index 21e02e1..efd9489 100644 --- a/MSCachedAsyncViewDrawing.h +++ b/MSCachedAsyncViewDrawing.h @@ -30,12 +30,14 @@ typedef void (^MSCachedAsyncViewDrawingCompletionBlock)(UIImage *drawnImage); * To generate an opaque image, pass a color with alpha = 1. * @param drawBlock this method is called from a background thread, so you must pay special attention to the thread safety of * anything you do in it. It's safe to use UIKit methods like -[UIImage drawInRect:] or -[NSString drawInRect:]. + * @return NSOperation associated with the drawing. If you're enqueuing a lot of drawing, you may want to cancel the operation + * before it finishes if the result is not needed anymore to save resources. */ -- (void)drawViewAsyncWithCacheKey:(NSString *)cacheKey - size:(CGSize)imageSize - backgroundColor:(UIColor *)backgroundColor - drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock - completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock; +- (NSOperation *)drawViewAsyncWithCacheKey:(NSString *)cacheKey + size:(CGSize)imageSize + backgroundColor:(UIColor *)backgroundColor + drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock + completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock; /** * @discussion this is the synchronous version of the other method. diff --git a/MSCachedAsyncViewDrawing.m b/MSCachedAsyncViewDrawing.m index 1252b9c..a20ec84 100644 --- a/MSCachedAsyncViewDrawing.m +++ b/MSCachedAsyncViewDrawing.m @@ -12,7 +12,33 @@ @interface MSCachedAsyncViewDrawing () @property (nonatomic, strong) NSCache *cache; -@property (nonatomic, strong) dispatch_queue_t dispatchQueue; +@property (nonatomic, strong) NSOperationQueue *operationQueue; + +@end + +@interface _MSViewDrawingOperation : NSBlockOperation + +@property (nonatomic, strong) UIImage *resultImage; + ++ (_MSViewDrawingOperation *)viewDrawingBlockOperationWithBlock:(void (^)(_MSViewDrawingOperation *))block; + +@end + +@implementation _MSViewDrawingOperation + ++ (_MSViewDrawingOperation *)viewDrawingBlockOperationWithBlock:(void (^)(_MSViewDrawingOperation *))block +{ + UIImage * (^heapBlock)(_MSViewDrawingOperation *) = [block copy]; + _MSViewDrawingOperation *operation = [[self alloc] init]; + + __weak _MSViewDrawingOperation *weakOperation = operation; + + [operation addExecutionBlock:^{ + heapBlock(weakOperation); + }]; + + return operation; +} @end @@ -36,7 +62,8 @@ - (id)init { self.cache = [[NSCache alloc] init]; self.cache.name = @"com.mindsnacks.view_drawing.cache"; - self.dispatchQueue = dispatch_queue_create("com.mindsnacks.view_drawing.queue", DISPATCH_QUEUE_CONCURRENT); + self.operationQueue = [[NSOperationQueue alloc] init]; + self.operationQueue.name = @"com.mindsnacks.view_drawing.queue"; } return self; @@ -44,29 +71,40 @@ - (id)init #pragma mark - Private -- (void)drawViewWithCacheKey:(NSString *)cacheKey - size:(CGSize)imageSize - backgroundColor:(UIColor *)backgroundColor - drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock - completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock - waitUntilDone:(BOOL)waitUntilDone +- (NSOperation *)drawViewWithCacheKey:(NSString *)cacheKey + size:(CGSize)imageSize + backgroundColor:(UIColor *)backgroundColor + drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock + completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock + waitUntilDone:(BOOL)waitUntilDone { UIImage *cachedImage = [self.cache objectForKey:cacheKey]; if (cachedImage) { completionBlock(cachedImage); - return; + return nil; } - MSCachedAsyncViewDrawingDrawBlock _drawBlock = [drawBlock copy]; - MSCachedAsyncViewDrawingCompletionBlock _completionBlock = [completionBlock copy]; - - dispatch_block_t loadImageBlock = ^{ + MSCachedAsyncViewDrawingDrawBlock heapDrawBlock = [drawBlock copy]; + MSCachedAsyncViewDrawingCompletionBlock heapCompletionBlock = [completionBlock copy]; + + _MSViewDrawingOperation *operation = [_MSViewDrawingOperation viewDrawingBlockOperationWithBlock:^(_MSViewDrawingOperation *operation) { + if (operation.isCancelled) + { + return; + } + BOOL opaque = [self colorIsOpaque:backgroundColor]; UIGraphicsBeginImageContextWithOptions(imageSize, opaque, 0); + if (operation.isCancelled) + { + UIGraphicsEndImageContext(); + return; + } + CGContextRef context = UIGraphicsGetCurrentContext(); CGRect rectToDraw = (CGRect){.origin = CGPointZero, .size = imageSize}; @@ -83,50 +121,65 @@ - (void)drawViewWithCacheKey:(NSString *)cacheKey CGContextRestoreGState(context); } - _drawBlock(rectToDraw); + heapDrawBlock(rectToDraw); - UIImage *imageResult = UIGraphicsGetImageFromCurrentImageContext(); + if (operation.isCancelled) + { + UIGraphicsEndImageContext(); + return; + } - UIGraphicsEndImageContext(); + UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); - [self.cache setObject:imageResult forKey:cacheKey]; + UIGraphicsEndImageContext(); - if (waitUntilDone) + if (operation.isCancelled) { - _completionBlock(imageResult); - } - else - { - dispatch_async(dispatch_get_main_queue(), ^{ - _completionBlock(imageResult); - }); + UIGraphicsEndImageContext(); + return; } + + [self.cache setObject:resultImage forKey:cacheKey]; + + operation.resultImage = resultImage; + }]; + + __strong __block _MSViewDrawingOperation *_operation = operation; + + operation.completionBlock = ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + heapCompletionBlock(_operation.resultImage); + _operation = nil; + }); }; + [self.operationQueue addOperation:operation]; + if (waitUntilDone) { - loadImageBlock(); + [operation waitUntilFinished]; + return nil; } else { - dispatch_async(self.dispatchQueue, loadImageBlock); + return operation; } } #pragma mark - Public -- (void)drawViewAsyncWithCacheKey:(NSString *)cacheKey - size:(CGSize)imageSize - backgroundColor:(UIColor *)backgroundColor - drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock - completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock +- (NSOperation *)drawViewAsyncWithCacheKey:(NSString *)cacheKey + size:(CGSize)imageSize + backgroundColor:(UIColor *)backgroundColor + drawBlock:(MSCachedAsyncViewDrawingDrawBlock)drawBlock + completionBlock:(MSCachedAsyncViewDrawingCompletionBlock)completionBlock { - [self drawViewWithCacheKey:cacheKey - size:imageSize - backgroundColor:backgroundColor - drawBlock:drawBlock - completionBlock:completionBlock - waitUntilDone:NO]; + return [self drawViewWithCacheKey:cacheKey + size:imageSize + backgroundColor:backgroundColor + drawBlock:drawBlock + completionBlock:completionBlock + waitUntilDone:NO]; } - (UIImage *)drawViewSyncWithCacheKey:(NSString *)cacheKey @@ -160,8 +213,8 @@ - (BOOL)colorIsOpaque:(UIColor *)color { [color getWhite:NULL alpha:&alpha]; } - + return (alpha == 1.0f); } -@end +@end \ No newline at end of file From 48fc60764f810bafdf6b83b1d53cefb920ba6fcf Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Sun, 11 Nov 2012 13:14:37 -0800 Subject: [PATCH 3/5] Clean up --- MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m index d3eb38e..051b42b 100644 --- a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m +++ b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m @@ -91,10 +91,7 @@ - (void)loadImage return; } - if (self.imageLoadOperation) - { - [self.imageLoadOperation cancel]; - } + [self.imageLoadOperation cancel]; NSString *cacheKey = [NSString stringWithFormat:@"com.mindsnacks.circle.%@.%@", self.circleColor, NSStringFromCGSize(self.frame.size)]; self.imageLoadOperation = [[MSCachedAsyncViewDrawing sharedInstance] drawViewAsyncWithCacheKey:cacheKey From f6f67e66b66c6e2981b0802e442ac4e171b03159 Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Sun, 11 Nov 2012 13:38:49 -0800 Subject: [PATCH 4/5] Making the NSOperationQueue static --- MSCachedAsyncViewDrawing.m | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MSCachedAsyncViewDrawing.m b/MSCachedAsyncViewDrawing.m index a20ec84..b485773 100644 --- a/MSCachedAsyncViewDrawing.m +++ b/MSCachedAsyncViewDrawing.m @@ -44,6 +44,17 @@ + (_MSViewDrawingOperation *)viewDrawingBlockOperationWithBlock:(void (^)(_MSVie @implementation MSCachedAsyncViewDrawing +static NSOperationQueue *_sharedOperationQueue = nil; + ++ (void)initialize +{ + if ([self class] == [MSCachedAsyncViewDrawing class]) + { + _sharedOperationQueue = [[NSOperationQueue alloc] init]; + _sharedOperationQueue.name = @"com.mindsnacks.view_drawing.queue"; + } +} + + (MSCachedAsyncViewDrawing *)sharedInstance { static MSCachedAsyncViewDrawing *sharedInstance = nil; @@ -62,8 +73,7 @@ - (id)init { self.cache = [[NSCache alloc] init]; self.cache.name = @"com.mindsnacks.view_drawing.cache"; - self.operationQueue = [[NSOperationQueue alloc] init]; - self.operationQueue.name = @"com.mindsnacks.view_drawing.queue"; + self.operationQueue = _sharedOperationQueue; } return self; From 242a60ffaef35c45ed75920282845dda03edc6ee Mon Sep 17 00:00:00 2001 From: Javier Soto Date: Mon, 12 Nov 2012 10:28:14 -0800 Subject: [PATCH 5/5] Fixed bad access crash --- MSCachedAsyncViewDrawing.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/MSCachedAsyncViewDrawing.m b/MSCachedAsyncViewDrawing.m index b485773..3b7950b 100644 --- a/MSCachedAsyncViewDrawing.m +++ b/MSCachedAsyncViewDrawing.m @@ -28,13 +28,12 @@ @implementation _MSViewDrawingOperation + (_MSViewDrawingOperation *)viewDrawingBlockOperationWithBlock:(void (^)(_MSViewDrawingOperation *))block { - UIImage * (^heapBlock)(_MSViewDrawingOperation *) = [block copy]; _MSViewDrawingOperation *operation = [[self alloc] init]; __weak _MSViewDrawingOperation *weakOperation = operation; [operation addExecutionBlock:^{ - heapBlock(weakOperation); + block(weakOperation); }]; return operation; @@ -99,7 +98,7 @@ - (NSOperation *)drawViewWithCacheKey:(NSString *)cacheKey MSCachedAsyncViewDrawingDrawBlock heapDrawBlock = [drawBlock copy]; MSCachedAsyncViewDrawingCompletionBlock heapCompletionBlock = [completionBlock copy]; - _MSViewDrawingOperation *operation = [_MSViewDrawingOperation viewDrawingBlockOperationWithBlock:^(_MSViewDrawingOperation *operation) { + _MSViewDrawingOperation *operation = [_MSViewDrawingOperation viewDrawingBlockOperationWithBlock:[^(_MSViewDrawingOperation *operation) { if (operation.isCancelled) { return; @@ -152,7 +151,7 @@ - (NSOperation *)drawViewWithCacheKey:(NSString *)cacheKey [self.cache setObject:resultImage forKey:cacheKey]; operation.resultImage = resultImage; - }]; + } copy]]; __strong __block _MSViewDrawingOperation *_operation = operation;