Skip to content

Commit 89d22fb

Browse files
committed
Merge pull request #118 from jmah/thread-safety
Thread safety fixes
2 parents 6279387 + 50e10e6 commit 89d22fb

File tree

1 file changed

+48
-39
lines changed

1 file changed

+48
-39
lines changed

FastImageCache/FastImageCache/FastImageCache/FICImageCache.m

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ @interface FICImageCache () {
2525
NSMutableDictionary *_formats;
2626
NSMutableDictionary *_imageTables;
2727
NSMutableDictionary *_requests;
28-
__weak id <FICImageCacheDelegate> _delegate;
2928

3029
BOOL _delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock;
3130
BOOL _delegateImplementsShouldProcessAllFormatsInFamilyForEntity;
@@ -54,18 +53,15 @@ - (void)setDelegate:(id<FICImageCacheDelegate>)delegate {
5453
}
5554
}
5655

57-
static FICImageCache *__imageCache = nil;
58-
5956
#pragma mark - Object Lifecycle
6057

6158
+ (instancetype)sharedImageCache {
62-
if (__imageCache == nil) {
63-
static dispatch_once_t onceToken;
64-
dispatch_once(&onceToken, ^{
65-
__imageCache = [[[self class] alloc] init];
66-
});
67-
}
68-
59+
static dispatch_once_t onceToken;
60+
static FICImageCache *__imageCache = nil;
61+
dispatch_once(&onceToken, ^{
62+
__imageCache = [[[self class] alloc] init];
63+
});
64+
6965
return __imageCache;
7066
}
7167

@@ -204,13 +200,20 @@ - (BOOL)_retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString
204200

205201
if (sourceImageURL != nil) {
206202
// We check to see if this image is already being fetched.
207-
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
208-
if (requestDictionary == nil) {
209-
// If we're here, then we aren't currently fetching this image.
210-
NSMutableDictionary *requestDictionary = [NSMutableDictionary dictionary];
211-
[_requests setObject:requestDictionary forKey:sourceImageURL];
203+
BOOL needsToFetch = NO;
204+
@synchronized (_requests) {
205+
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
206+
if (requestDictionary == nil) {
207+
// If we're here, then we aren't currently fetching this image.
208+
requestDictionary = [NSMutableDictionary dictionary];
209+
[_requests setObject:requestDictionary forKey:sourceImageURL];
210+
needsToFetch = YES;
211+
}
212212

213213
_FICAddCompletionBlockForEntity(formatName, requestDictionary, entity, completionBlock);
214+
}
215+
216+
if (needsToFetch) {
214217
UIImage *image;
215218
if ([entity respondsToSelector:@selector(imageForFormat:)]){
216219
FICImageFormat *format = [self formatWithName:formatName];
@@ -221,12 +224,9 @@ - (BOOL)_retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString
221224
[self _imageDidLoad:image forURL:sourceImageURL];
222225
} else if (_delegateImplementsWantsSourceImageForEntityWithFormatNameCompletionBlock){
223226
[_delegate imageCache:self wantsSourceImageForEntity:entity withFormatName:formatName completionBlock:^(UIImage *sourceImage) {
224-
[self _imageDidLoad:sourceImage forURL:sourceImageURL];
227+
[self _imageDidLoad:sourceImage forURL:sourceImageURL];
225228
}];
226229
}
227-
} else {
228-
// We have an existing request dictionary, which means this URL is currently being fetched.
229-
_FICAddCompletionBlockForEntity(formatName, requestDictionary, entity, completionBlock);
230230
}
231231
} else {
232232
NSString *message = [NSString stringWithFormat:@"*** FIC Error: %s entity %@ returned a nil source image URL for image format %@.", __PRETTY_FUNCTION__, entity, formatName];
@@ -243,7 +243,13 @@ - (BOOL)_retrieveImageForEntity:(id <FICEntity>)entity withFormatName:(NSString
243243
}
244244

245245
- (void)_imageDidLoad:(UIImage *)image forURL:(NSURL *)URL {
246-
NSDictionary *requestDictionary = [_requests objectForKey:URL];
246+
NSDictionary *requestDictionary;
247+
@synchronized (_requests) {
248+
requestDictionary = [_requests objectForKey:URL];
249+
[_requests removeObjectForKey:URL];
250+
// Now safe to use requestsDictionary outside the lock, because we've taken ownership from _requests
251+
}
252+
247253
if (requestDictionary != nil) {
248254
for (NSMutableDictionary *entityDictionary in [requestDictionary allValues]) {
249255
id <FICEntity> entity = [entityDictionary objectForKey:FICImageCacheEntityKey];
@@ -263,8 +269,6 @@ - (void)_imageDidLoad:(UIImage *)image forURL:(NSURL *)URL {
263269
}
264270
}
265271
}
266-
267-
[_requests removeObjectForKey:URL];
268272
}
269273

270274
static void _FICAddCompletionBlockForEntity(NSString *formatName, NSMutableDictionary *entityRequestsDictionary, id <FICEntity> entity, FICImageCacheCompletionBlock completionBlock) {
@@ -439,27 +443,32 @@ - (void)deleteImageForEntity:(id <FICEntity>)entity withFormatName:(NSString *)f
439443

440444
- (void)cancelImageRetrievalForEntity:(id <FICEntity>)entity withFormatName:(NSString *)formatName {
441445
NSURL *sourceImageURL = [entity sourceImageURLWithFormatName:formatName];
442-
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
443-
if (requestDictionary) {
444-
NSString *entityUUID = [entity UUID];
445-
NSMutableDictionary *entityRequestsDictionary = [requestDictionary objectForKey:entityUUID];
446-
if (entityRequestsDictionary) {
447-
NSMutableDictionary *completionBlocksDictionary = [entityRequestsDictionary objectForKey:FICImageCacheCompletionBlocksKey];
448-
[completionBlocksDictionary removeObjectForKey:formatName];
449-
450-
if ([completionBlocksDictionary count] == 0) {
451-
[requestDictionary removeObjectForKey:entityUUID];
452-
}
453-
454-
if ([requestDictionary count] == 0) {
455-
[_requests removeObjectForKey:sourceImageURL];
456-
457-
if (_delegateImplementsCancelImageLoadingForEntityWithFormatName) {
458-
[_delegate imageCache:self cancelImageLoadingForEntity:entity withFormatName:formatName];
446+
NSString *entityUUID = [entity UUID];
447+
448+
BOOL cancelImageLoadingForEntity = NO;
449+
@synchronized (_requests) {
450+
NSMutableDictionary *requestDictionary = [_requests objectForKey:sourceImageURL];
451+
if (requestDictionary) {
452+
NSMutableDictionary *entityRequestsDictionary = [requestDictionary objectForKey:entityUUID];
453+
if (entityRequestsDictionary) {
454+
NSMutableDictionary *completionBlocksDictionary = [entityRequestsDictionary objectForKey:FICImageCacheCompletionBlocksKey];
455+
[completionBlocksDictionary removeObjectForKey:formatName];
456+
457+
if ([completionBlocksDictionary count] == 0) {
458+
[requestDictionary removeObjectForKey:entityUUID];
459+
}
460+
461+
if ([requestDictionary count] == 0) {
462+
[_requests removeObjectForKey:sourceImageURL];
463+
cancelImageLoadingForEntity = YES;
459464
}
460465
}
461466
}
462467
}
468+
469+
if (cancelImageLoadingForEntity && _delegateImplementsCancelImageLoadingForEntityWithFormatName) {
470+
[_delegate imageCache:self cancelImageLoadingForEntity:entity withFormatName:formatName];
471+
}
463472
}
464473

465474
- (void)reset {

0 commit comments

Comments
 (0)