Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 23 additions & 24 deletions MSCachedAsyncViewDrawing-SampleProject/MSAsyncDrawingCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ @interface MSCustomDrawnViewImageView : UIImageView

@end

@interface MSCustomDrawnViewImageView ()

@property (nonatomic, strong) NSOperation *imageLoadOperation;

@end

@implementation MSCustomDrawnViewImageView

@synthesize circleColor = _circleColor;
Expand Down Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions MSCachedAsyncViewDrawing.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
142 changes: 102 additions & 40 deletions MSCachedAsyncViewDrawing.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,37 +72,48 @@ - (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;
}

#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};
Expand All @@ -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
Expand Down Expand Up @@ -160,8 +222,8 @@ - (BOOL)colorIsOpaque:(UIColor *)color
{
[color getWhite:NULL alpha:&alpha];
}

return (alpha == 1.0f);
}

@end
@end