diff --git a/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m b/MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m index 484646a..051b42b 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,23 @@ - (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; - } - }]; + [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..3b7950b 100644 --- a/MSCachedAsyncViewDrawing.m +++ b/MSCachedAsyncViewDrawing.m @@ -12,12 +12,48 @@ @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 +{ + _MSViewDrawingOperation *operation = [[self alloc] init]; + + __weak _MSViewDrawingOperation *weakOperation = operation; + + [operation addExecutionBlock:^{ + block(weakOperation); + }]; + + return operation; +} @end @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; @@ -36,7 +72,7 @@ - (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 = _sharedOperationQueue; } return self; @@ -44,29 +80,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 +130,65 @@ - (void)drawViewWithCacheKey:(NSString *)cacheKey CGContextRestoreGState(context); } - _drawBlock(rectToDraw); + heapDrawBlock(rectToDraw); + + if (operation.isCancelled) + { + UIGraphicsEndImageContext(); + return; + } - UIImage *imageResult = UIGraphicsGetImageFromCurrentImageContext(); + UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - [self.cache setObject:imageResult forKey:cacheKey]; - - if (waitUntilDone) - { - _completionBlock(imageResult); - } - else + if (operation.isCancelled) { - dispatch_async(dispatch_get_main_queue(), ^{ - _completionBlock(imageResult); - }); + UIGraphicsEndImageContext(); + return; } + + [self.cache setObject:resultImage forKey:cacheKey]; + + operation.resultImage = resultImage; + } copy]]; + + __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 +222,8 @@ - (BOOL)colorIsOpaque:(UIColor *)color { [color getWhite:NULL alpha:&alpha]; } - + return (alpha == 1.0f); } -@end +@end \ No newline at end of file