From 3994006cb46eb5e20517d66bb59c673769ec7e23 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 11 Aug 2022 14:51:02 +0800 Subject: [PATCH] Try to workaround iOS 15+ crash that CGImage retain the CGImageSource during animation image playing This used to work on iOS 14 --- CHANGELOG.md | 2 +- SDWebImage/Core/SDImageIOAnimatedCoder.m | 31 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b0d322..5c42ae40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## [5.13.2 - 5.13 Patch, on Jul 23th, 2022](https://github.com/rs/SDWebImage/releases/tag/5.13.2) -See [all tickets marked for the 5.13.1 release](https://github.com/SDWebImage/SDWebImage/milestone/99) +See [all tickets marked for the 5.13.2 release](https://github.com/SDWebImage/SDWebImage/milestone/99) ### Fixes - Fix the rare case when cancel an async disk cache query may cause twice callback #3380 #3374 diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index 922d0186..17e2d511 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -627,15 +627,36 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination if (index >= _frameCount) { return nil; } - // Animated Image should not use the CGContext solution to force decode. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961 - NSDictionary *options = @{ - (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES), - (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage - }; + NSDictionary *options; + BOOL isDecoded = YES; + if (@available(iOS 15, tvOS 15, *)) { + // iOS 15+, CGImageRef now retains CGImageSourceRef internally. To workaround its thread-safe issue, we have to strip CGImageSourceRef, using Force-Decode (or have to use SPI `CGImageSetImageSource`), See: https://github.com/SDWebImage/SDWebImage/issues/3273 + isDecoded = NO; + options = @{ + (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO), + (__bridge NSString *)kCGImageSourceShouldCache : @(NO) + }; + } else { + // Animated Image should not use the CGContext solution to force decode on lower firmware. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961 + isDecoded = YES; + options = @{ + (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES), + (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage + }; + } UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:options]; if (!image) { return nil; } + if (!isDecoded) { + image = [SDImageCoderHelper decodedImageWithImage:image]; +#if DEBUG + // Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+) + // If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check + extern CGImageSourceRef CGImageGetImageSource(CGImageRef); + NSCAssert(!CGImageGetImageSource(image.CGImage), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock"); +#endif + } image.sd_imageFormat = self.class.imageFormat; image.sd_isDecoded = YES; return image;