@@ -313,11 +313,20 @@ - (void)dealloc
313313
314314- (void )didReceiveMemoryWarning : (NSNotification *)notification
315315{
316+ // Incremental decoding may concurrently read/write _imageSource from the
317+ // frame fetch queue and updateIncrementalData:; hold the same lock to
318+ // prevent races with CGImageSourceRemoveCacheAtIndex.
319+ if (_incremental) {
320+ SD_LOCK (_lock);
321+ }
316322 if (_imageSource) {
317323 for (size_t i = 0 ; i < _frameCount; i++) {
318324 CGImageSourceRemoveCacheAtIndex (_imageSource, i);
319325 }
320326 }
327+ if (_incremental) {
328+ SD_UNLOCK (_lock);
329+ }
321330}
322331
323332#pragma mark - Subclass Override
@@ -781,15 +790,20 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
781790 if (_finished) {
782791 return ;
783792 }
784- _imageData = data;
785- _finished = finished;
786-
787793 // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
788794 // Thanks to the author @Nyx0uf
789-
795+
796+ // Lock before updating state and the image source to prevent concurrent access from the frame fetch queue.
797+ // CGImageSource is not thread-safe for simultaneous read+write.
798+ // _imageData and _finished must be set inside the lock so readers cannot observe
799+ // _finished == YES while _imageSource still has old data.
800+ SD_LOCK (_lock);
801+ _imageData = data;
802+ _finished = finished;
803+
790804 // Update the data source, we must pass ALL the data, not just the new bytes
791805 CGImageSourceUpdateData (_imageSource, (__bridge CFDataRef)data, finished);
792-
806+
793807 if (_width + _height == 0 ) {
794808 CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex (_imageSource, 0 , NULL );
795809 if (properties) {
@@ -800,12 +814,10 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
800814 CFRelease (properties);
801815 }
802816 }
803-
804- SD_LOCK (_lock);
817+
805818 // For animated image progressive decoding because the frame count and duration may be changed.
806819 [self scanAndCheckFramesValidWithImageSource: _imageSource];
807- SD_UNLOCK (_lock);
808-
820+
809821 // Scale down to limit bytes if need
810822 if (_limitBytes > 0 ) {
811823 // Hack since ImageIO public API (not CGImageDecompressor/CMPhoto) always return back RGBA8888 CGImage
@@ -815,23 +827,27 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
815827 _thumbnailSize = framePixelSize;
816828 _preserveAspectRatio = YES ;
817829 }
830+
831+ SD_UNLOCK (_lock);
818832}
819833
820834- (UIImage *)incrementalDecodedImageWithOptions : (SDImageCoderOptions *)options {
821835 NSCParameterAssert (_incremental);
822836 UIImage *image;
823837
838+ // Create the image
839+ CGFloat scale = _scale;
840+ NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
841+ if (scaleFactor != nil ) {
842+ scale = MAX ([scaleFactor doubleValue ], 1 );
843+ }
844+ SD_LOCK (_lock);
824845 if (_width + _height > 0 ) {
825- // Create the image
826- CGFloat scale = _scale;
827- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
828- if (scaleFactor != nil ) {
829- scale = MAX ([scaleFactor doubleValue ], 1 );
830- }
831846 image = [self .class createFrameAtIndex: 0 source: _imageSource scale: scale preserveAspectRatio: _preserveAspectRatio thumbnailSize: _thumbnailSize lazyDecode: _lazyDecode animatedImage: NO decodeToHDR: _finished ? _decodeToHDR : NO ];
832- if (image) {
833- image.sd_imageFormat = self.class .imageFormat ;
834- }
847+ }
848+ SD_UNLOCK (_lock);
849+ if (image) {
850+ image.sd_imageFormat = self.class .imageFormat ;
835851 }
836852
837853 return image;
0 commit comments