Skip to content

Commit c3ad5e1

Browse files
authored
Merge pull request #3876 from chrisngabp/master
Fix locking mechanism to avoid iOS 26 crash with progressiveLoad concurrency
2 parents 4bb1649 + bb4ba99 commit c3ad5e1

1 file changed

Lines changed: 34 additions & 18 deletions

File tree

‎SDWebImage/Core/SDImageIOAnimatedCoder.m‎

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)