diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b8becd..526b9827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [5.7.4 - 5.7 Patch, on May 6th, 2020](https://github.com/SDWebImage/SDWebImage/releases/tag/5.7.4) +See [all tickets marked for the 5.7.4 release](https://github.com/SDWebImage/SDWebImage/milestone/71) + +### Fixes +- Fix the issue that NSOperation conforms to `SDWebImageOperation` check failed. Fix cancelling prefetcher hung up #2999 #2998 + ## [5.7.3 - 5.7 Patch, on Apr 21st, 2020](https://github.com/SDWebImage/SDWebImage/releases/tag/5.7.3) See [all tickets marked for the 5.7.3 release](https://github.com/SDWebImage/SDWebImage/milestone/70) diff --git a/SDWebImage.podspec b/SDWebImage.podspec index f84fb4a3..0c09dd1c 100644 --- a/SDWebImage.podspec +++ b/SDWebImage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SDWebImage' - s.version = '5.7.3' + s.version = '5.7.4' s.osx.deployment_target = '10.10' s.ios.deployment_target = '8.0' diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 8bb4678d..f5588bf7 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -115,6 +115,8 @@ 327054DC206CD8B3006EA328 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */; }; 3287E6D1244C0C1400007311 /* MKAnnotationView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */; }; 3287E6D2244C0C1400007311 /* MKAnnotationView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 327F2E83245AE1650075F846 /* SDWebImageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 327F2E82245AE1650075F846 /* SDWebImageOperation.m */; }; + 327F2E84245AE1650075F846 /* SDWebImageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 327F2E82245AE1650075F846 /* SDWebImageOperation.m */; }; 328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; }; 328BB6A42081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; }; @@ -441,6 +443,7 @@ 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageAPNGCoder.m; path = Core/SDImageAPNGCoder.m; sourceTree = ""; }; 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+WebCache.m"; sourceTree = ""; }; 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+WebCache.h"; sourceTree = ""; }; + 327F2E82245AE1650075F846 /* SDWebImageOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOperation.m; path = Core/SDWebImageOperation.m; sourceTree = ""; }; 328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheKeyFilter.h; path = Core/SDWebImageCacheKeyFilter.h; sourceTree = ""; }; 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCacheKeyFilter.m; path = Core/SDWebImageCacheKeyFilter.m; sourceTree = ""; }; 328BB6A82081FEE500760D6C /* SDWebImageCacheSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheSerializer.h; path = Core/SDWebImageCacheSerializer.h; sourceTree = ""; }; @@ -857,6 +860,7 @@ 320CAE132086F50500CFFC80 /* SDWebImageError.h */, 320CAE142086F50500CFFC80 /* SDWebImageError.m */, 530E49E71646388E002868E7 /* SDWebImageOperation.h */, + 327F2E82245AE1650075F846 /* SDWebImageOperation.m */, 324DF4B2200A14DC008A84CC /* SDWebImageDefine.h */, 324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */, 325312C6200F09910046BF1E /* SDWebImageTransition.h */, @@ -1208,6 +1212,7 @@ 4A2CAE201AB4BB6C00B6BC39 /* SDImageCache.m in Sources */, 4369C2801D9807EC007E863A /* UIView+WebCache.m in Sources */, 329A18611FFF5DFD008C9A2F /* UIImage+Metadata.m in Sources */, + 327F2E84245AE1650075F846 /* SDWebImageOperation.m in Sources */, 328BB6B22081FEE500760D6C /* SDWebImageCacheSerializer.m in Sources */, 325C4611223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */, ); @@ -1282,6 +1287,7 @@ ABBE71A818C43B4D00B75E91 /* UIImageView+HighlightedWebCache.m in Sources */, 4369C27E1D9807EC007E863A /* UIView+WebCache.m in Sources */, 329A185F1FFF5DFD008C9A2F /* UIImage+Metadata.m in Sources */, + 327F2E83245AE1650075F846 /* SDWebImageOperation.m in Sources */, 328BB6B02081FEE500760D6C /* SDWebImageCacheSerializer.m in Sources */, 325C4610223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */, ); diff --git a/SDWebImage/Core/SDWebImageOperation.m b/SDWebImage/Core/SDWebImageOperation.m new file mode 100644 index 00000000..0d6e880d --- /dev/null +++ b/SDWebImage/Core/SDWebImageOperation.m @@ -0,0 +1,14 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageOperation.h" + +/// NSOperation conform to `SDWebImageOperation`. +@implementation NSOperation (SDWebImageOperation) + +@end diff --git a/SDWebImage/Core/SDWebImagePrefetcher.m b/SDWebImage/Core/SDWebImagePrefetcher.m index efcb5c73..5c0f37f7 100644 --- a/SDWebImage/Core/SDWebImagePrefetcher.m +++ b/SDWebImage/Core/SDWebImagePrefetcher.m @@ -20,6 +20,10 @@ atomic_flag _isAllFinished; unsigned long _totalCount; + + // Used to ensure NSPointerArray thread safe + dispatch_semaphore_t _prefetchOperationsLock; + dispatch_semaphore_t _loadOperationsLock; } @property (nonatomic, copy, readwrite) NSArray *urls; @@ -106,7 +110,6 @@ } - (void)startPrefetchWithToken:(SDWebImagePrefetchToken * _Nonnull)token { - NSPointerArray *operations = token.loadOperations; for (NSURL *url in token.urls) { @autoreleasepool { @weakify(self); @@ -142,13 +145,13 @@ [asyncOperation complete]; }]; NSAssert(operation != nil, @"Operation should not be nil, [SDWebImageManager loadImageWithURL:options:context:progress:completed:] break prefetch logic"); - @synchronized (token) { - [operations addPointer:(__bridge void *)operation]; - } + SD_LOCK(token->_loadOperationsLock); + [token.loadOperations addPointer:(__bridge void *)operation]; + SD_UNLOCK(token->_loadOperationsLock); }]; - @synchronized (token) { - [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation]; - } + SD_LOCK(token->_prefetchOperationsLock); + [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation]; + SD_UNLOCK(token->_prefetchOperationsLock); [self.prefetchQueue addOperation:prefetchOperation]; } } @@ -262,24 +265,38 @@ @implementation SDWebImagePrefetchToken -- (void)cancel { - @synchronized (self) { - [self.prefetchOperations compact]; - for (id operation in self.prefetchOperations) { - if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { - [operation cancel]; - } - } - self.prefetchOperations.count = 0; - - [self.loadOperations compact]; - for (id operation in self.loadOperations) { - if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { - [operation cancel]; - } - } - self.loadOperations.count = 0; +- (instancetype)init { + self = [super init]; + if (self) { + _prefetchOperationsLock = dispatch_semaphore_create(1); + _loadOperationsLock = dispatch_semaphore_create(1); } + return self; +} + +- (void)cancel { + SD_LOCK(_prefetchOperationsLock); + [self.prefetchOperations compact]; + for (id operation in self.prefetchOperations) { + id strongOperation = operation; + if (strongOperation) { + [strongOperation cancel]; + } + } + self.prefetchOperations.count = 0; + SD_UNLOCK(_prefetchOperationsLock); + + SD_LOCK(_loadOperationsLock); + [self.loadOperations compact]; + for (id operation in self.loadOperations) { + id strongOperation = operation; + if (strongOperation) { + [strongOperation cancel]; + } + } + self.loadOperations.count = 0; + SD_UNLOCK(_loadOperationsLock); + self.completionBlock = nil; self.progressBlock = nil; [self.prefetcher removeRunningToken:self]; diff --git a/SDWebImage/Private/SDAsyncBlockOperation.m b/SDWebImage/Private/SDAsyncBlockOperation.m index 8862ef8e..0b2fefec 100644 --- a/SDWebImage/Private/SDAsyncBlockOperation.m +++ b/SDWebImage/Private/SDAsyncBlockOperation.m @@ -35,33 +35,58 @@ } - (void)start { - if (self.isCancelled) { - return; - } - - [self willChangeValueForKey:@"isExecuting"]; - self.executing = YES; - [self didChangeValueForKey:@"isExecuting"]; - - if (self.executionBlock) { - self.executionBlock(self); - } else { - [self complete]; + @synchronized (self) { + if (self.isCancelled) { + self.finished = YES; + return; + } + + self.finished = NO; + self.executing = YES; + + if (self.executionBlock) { + self.executionBlock(self); + } else { + self.executing = NO; + self.finished = YES; + } } } - (void)cancel { - [super cancel]; - [self complete]; + @synchronized (self) { + [super cancel]; + if (self.isExecuting) { + self.executing = NO; + self.finished = YES; + } + } } + - (void)complete { - [self willChangeValueForKey:@"isExecuting"]; + @synchronized (self) { + if (self.isExecuting) { + self.finished = YES; + self.executing = NO; + } + } + } + +- (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; - self.executing = NO; - self.finished = YES; - [self didChangeValueForKey:@"isExecuting"]; + _finished = finished; [self didChangeValueForKey:@"isFinished"]; } +- (void)setExecuting:(BOOL)executing { + [self willChangeValueForKey:@"isExecuting"]; + _executing = executing; + [self didChangeValueForKey:@"isExecuting"]; +} + +- (BOOL)isConcurrent { + return YES; +} + @end diff --git a/Tests/Tests/SDWebImageDownloaderTests.m b/Tests/Tests/SDWebImageDownloaderTests.m index 5d2c52ce..6c18314f 100644 --- a/Tests/Tests/SDWebImageDownloaderTests.m +++ b/Tests/Tests/SDWebImageDownloaderTests.m @@ -95,6 +95,10 @@ operation = token.downloadOperation; expect([operation class]).to.equal([SDWebImageTestDownloadOperation class]); + // Assert the NSOperation conforms to `SDWebImageOperation` + expect([NSOperation.class conformsToProtocol:@protocol(SDWebImageOperation)]).beTruthy(); + expect([operation conformsToProtocol:@protocol(SDWebImageOperation)]).beTruthy(); + // back to the original value downloader.config.operationClass = nil; token = [downloader downloadImageWithURL:imageURL3 options:0 progress:nil completed:nil]; diff --git a/WebImage/Info.plist b/WebImage/Info.plist index b311cc23..d880a0bf 100644 --- a/WebImage/Info.plist +++ b/WebImage/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.7.3 + 5.7.4 CFBundleSignature ???? CFBundleVersion - 5.7.3 + 5.7.4 NSPrincipalClass