SDWebImage: SDWebImage 5.17.0 iOS 16 SDImageIOAnimatedCoder createFrameAtIndex: still crashes EXC_BAD_ACCESS

New Issue Checklist

Issue Info

Info Value
Platform Name iOS
Platform Version 16.5.1
SDWebImage Version 5.17.0
Integration Method SwiftPM
Xcode Version Xcode 15
Repro rate sometimes (found in Xcode Organizer crashlogs)
Repro with our demo prj sorry haven’t tried
Demo project link -

Issue Description and Steps

Hi we have an iOS app that uses SDWebImage for animated images. We use SDAnimatedImageView + SDAnimatedImage to display. Project is written in Swift and uses a mix of SwiftUI and UIKit. For loading, .scaleDownLargeImages is always passed. But context option “SDImageCoderDecodeUseLazyDecoding” is not passed. I find that setting SDImageCoderDecodeUseLazyDecoding = true causes noticeable lag when scrolling, so I prefer to not set it to true. However, in Xcode Organizer, we see several crash logs like so:

0   libsystem_platform.dylib      	0x000000020a0fbe14 _platform_memmove + 84
1   ImageIO                       	0x00000001c51591e0 GIFBufferInfo::GIFBufferInfo(unsigned char*, bool, unsigned int, unsigned int, unsigned int) + 88 (GIF_common.cpp:27)
2   ImageIO                       	0x00000001c5169f50 std::__1::__shared_ptr_emplace<GIFBufferInfo, std::__1::allocator<GIFBufferInfo> >::__shared_ptr_emplace[abi:v15006]<unsigned char*&, bool, unsigned int&, unsigned int&, unsigned int>(std::__1::all... + 56 (shared_ptr.h:294)
3   ImageIO                       	0x00000001c5169ee4 std::__1::shared_ptr<GIFBufferInfo> std::__1::allocate_shared[abi:v15006]<GIFBufferInfo, std::__1::allocator<GIFBufferInfo>, unsigned char*&, bool, unsigned int&, unsigned int&, unsigned int, void>... + 84 (shared_ptr.h:953)
4   ImageIO                       	0x00000001c51696c0 GIFReadPlugin::copyImageBlockSet(InfoRec*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 872 (GIF_readPlugin.cpp:1520)
5   ImageIO                       	0x00000001c4fa4b70 IIO_Reader::CopyImageBlockSetProc(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 228 (IIOReader.cpp:1454)
6   ImageIO                       	0x00000001c4fa08d4 IIOImageProviderInfo::copyImageBlockSetWithOptions(CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 740 (CGImagePlus.cpp:2326)
7   ImageIO                       	0x00000001c4fa8430 IIOImageProviderInfo::CopyImageBlockSetWithOptions(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 840 (CGImagePlus.cpp:2638)
8   CoreGraphics                  	0x00000001c1caafe0 imageProvider_retain_data + 88 (CGDataProviderImageProvider.c:91)
9   CoreGraphics                  	0x00000001c1cc8530 CGDataProviderRetainData + 84 (CGDataProvider.c:922)
10  CoreGraphics                  	0x00000001c1ce4b98 provider_for_destination_retain_data + 24 (CGDataProviderForDestination.c:705)
11  CoreGraphics                  	0x00000001c1cc8530 CGDataProviderRetainData + 84 (CGDataProvider.c:922)
12  CoreGraphics                  	0x00000001c1ca7ed8 CGAccessSessionCreate + 104 (CGAccessSession.c:71)
13  CoreGraphics                  	0x00000001c1ca28d0 get_access_session + 48 (CGSImage.c:488)
14  CoreGraphics                  	0x00000001c1cc9bf4 img_raw_read + 944 (CGSImage.c:627)
15  CoreGraphics                  	0x00000001c1cb841c img_interpolate_read + 588 (CGSImage.c:2547)
16  CoreGraphics                  	0x00000001c1c76a80 img_data_lock + 8748 (CGSImage.c:5553)
17  CoreGraphics                  	0x00000001c1cb99ec CGSImageDataLock + 1324 (CGSImage.c:5847)
18  CoreGraphics                  	0x00000001c1c8d12c ripc_AcquireRIPImageData + 708 (RIPImage.c:337)
19  CoreGraphics                  	0x00000001c1caa230 ripc_DrawImage + 820 (RIPContext.c:1432)
20  CoreGraphics                  	0x00000001c1c811dc CGContextDrawImageWithOptions + 1112 (CGContextImage.c:303)
21  ImageIO                       	0x00000001c4fbfff0 CGImageCreateCopyWithParametersNew(CGImage*, CGColor*, CGAffineTransform, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, CGColorSpace*, unsigned int, bool, CGColorRender... + 1640 (CGImageCopy.cpp:784)
22  ImageIO                       	0x00000001c4fac560 IIOImageSource::createThumbnailAtIndex(unsigned long, IIODictionary*) + 2692 (CGImageSource.cpp:2192)
23  ImageIO                       	0x00000001c4f98844 CGImageSourceCreateThumbnailAtIndex + 388 (CGImageSource.cpp:4269)
24  <App Name>                    	0x0000000102973428 +[SDImageIOAnimatedCoder createFrameAtIndex:source:scale:preserveAspectRatio:thumbnailSize:lazyDecode:animatedImage:] + 820 (SDImageIOAnimatedCoder.m:269)
25  <App Name>                    	0x0000000102973a9c -[SDImageIOAnimatedCoder decodedImageWithData:options:] + 1032 (SDImageIOAnimatedCoder.m:449)
26  <App Name>                    	0x0000000102972004 -[SDImageCodersManager decodedImageWithData:options:] + 276 (SDImageCodersManager.m:109)
27  <App Name>                    	0x000000010296ce4c SDImageCacheDecodeImageData + 396 (SDImageCacheDefine.m:123)
28  <App Name>                    	0x000000010296a2ac -[SDImageCache diskImageForKey:data:options:context:] + 112 (SDImageCache.m:524)
29  <App Name>                    	0x000000010296abe0 __73-[SDImageCache queryCacheOperationForKey:options:context:cacheType:done:]_block_invoke.161 + 420 (SDImageCache.m:677)
30  <App Name>                    	0x000000010296ae74 __73-[SDImageCache queryCacheOperationForKey:options:context:cacheType:done:]_block_invoke.164 + 68 (SDImageCache.m:701)

There are not many threads created, only 9 threads are found in the crash log. This crash is EXC_BAD_ACCESS (SIGSEGV), and after looking around for similar issues, this looks like some kind of out of memory crash? My question is what is the best approach right now to avoid this kind of problem, besides trying SDImageCoderDecodeUseLazyDecoding = true ? We are using a fairly new version of SDWebImage as well (5.17.0), and I see several issues in the past attempting to address or fix similar crash issues, yet it still seems to happen. The images are loaded in either a SwiftUI List or UIKit UICollectionView, it is common for several animated images to appear together (but they are not the same image), these animated images are user uploaded, so we have no size guarantee. My guess is that some of the animated images are very big, causing out of memory when decoding. Is there some approach I can take to better limit the image size, in order to avoid crashes like this? Thanks in advance!

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Comments: 22 (12 by maintainers)

Most upvoted comments

with a limit of maybe 1000 x 1000, it should help with “Big images” i think

Yes, animted image = frame count * bytes per frame (= bytesPerPixel * pixel count)

And then by using the maxBufferSize, it could help with “Long images” then

Yes, but pay attention this is not global control but for current image view

If SwiftUI deallocates the view, the buffer should be released I think

Yes, then buffer is just a NSMutableDictionary hosted in SDAnimatedPlayer which retained by SDAnimatedImageView

will the previous buffer be cleared

Yes, a new setImage: call cause that SDAnimatedImagePlayer been dealloced, so as buffer NSMutableDictionary

Do I still need to pass “clearBufferWhenStopped” just in case

Maybe. A OOM may not always been that case “total RAM usage is high”, a rapid memory peak may still cause OOM. This can help for that

if i need to get the full size image, not the thumbnail, I just need to make another load image call without the thumbnail size context option right?

Yes. Because I told that thumnail by defaults store full image data into disk cache(actually no thumnail data is stored into disk, only thumnail image object into memory). When you query without thumnail it will hit disk cache async less than 1 second

If I limited the bufferSize here to something small, such as 2MB, would this help alleviate potential crashes? Thanks again

Maybe, but this limit is per-image-view-level, right ? If you have 100 image view (which means, you use SwiftUI.List/UIScrollView, or some non-lazy no re-using cell), you keep all image view into memory and consume RAM even that image view is not visible.

For this case, we have a special option called clearBufferWhenStopped (defaults to NO, I doubt this is a wrong design), because when the image view is not visible, the image view will be sent a stopAnimating call and get stopped. Then it will clear temp buffer.

Another solution, it’s strongly recommaned to use the LazyVStack or UITableView/UICollectionView instead, which is lazy and cell-reusing. So actually even you have a list of 100 GIF, in the RAM there are only SDAnimatedImageView living, the temp buffer is guranteed to < 5 GIF images all frames

does SDWebImage only return the image as thumbnail and never the full image?

It will return thumbnail image, and nil data. But by defaults, the data is stored into disk cache, thumbnail image is stored into memory cache. This behavior can also be controlled by advanced context arg .storeCacheType, .originalStoreCacheType two, see documentation there

I see it accepts a CGSize, how would I pass the image view to it?

From newer version, you can always pass decode options to coder and do customization, don’t need the same naming context arg (There are one SDWebImageContextImageThumbnailPixelSize, same name)

let decodeOptions: [SDImageCoderOption: Any] = [.decodeThumbnailPixelSize: CGSize(width: 1000, height: 1000)]
let context: [SDWebImageContext: Any] = [.imageDecodeOptions: decodeOptions]
imageView.sd_setImage(with: url, placeholderImage: nil, options: [], context: context, progress:nil, completed: nil)

This design is better, since actually decoder is plugin, which means, not written by me or SDWebImage, it can written by you and provide new option wo don’t know. Pass that raw dictionary directly to decoder is correct design.

or

let context: [SDWebImageContext: Any] = [.imageThumbnailPixelSize: CGSize(width: 1000, height: 1000)]
imageView.sd_setImage(with: url, placeholderImage: nil, options: [], context: context, progress:nil, completed: nil)

This only supports some pre-defined context option which passthrough to decoder options (but not extensible)