New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SDWebImage 5.17.0 iOS 16 SDImageIOAnimatedCoder createFrameAtIndex: still crashes EXC_BAD_ACCESS #3612
Comments
AnimatedImage do still support thumbnail decoding, so you can use "thumbnailPixelSize" and pass the image view or render size For example, your image list is 1000x1000, even a user uploaded huge animted image is 8K, it still only occupy 4MB each frame (1000x1000x4 bytesPerPixel) |
@dreampiggy Sorry I haven't fully understood everything. By "thumbnailPixelSize" you mean the "SDImageCoderDecodeThumbnailPixelSize" option under SDImageCoderOption? I see it accepts a CGSize, how would I pass the image view to it? How does thumbnail decoding work? If I pass a valid size for thumbnailPixelSize, does SDWebImage only return the image as thumbnail and never the full image? I use SDWebImageManager.loadImageWithURL to load most of my images (animated and static) and then manually set the image with animation (SwiftUI) or transition in UIImageView / SDAnimatedImageView. I searched other issues and someone also mentioned the "maxBufferSize" property on SDAnimatedImagePlayer. If I limited the bufferSize here to something small, such as 2MB, would this help alleviate potential crashes? Thanks again |
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 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) |
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 |
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 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 |
@dreampiggy Thanks for the super detailed replies! They are incredibly helpful! Yes we are not using a ScrollView + VStack, we don't even use LazyVStack, we always use List (which is backed by UICollectionView on iOS 16 and UITableView on iOS 15, we support min iOS15) or just UIKit UICollectionView, so we have plenty of cell re-use. I think in SwiftUI's case they don't re-use the cell, they just destroy and remake. Anyways the memory should be cleared and limited to maybe less than 10 GIF images at all times. I personally have never encountered the crash so I am surprised to see them from the organizer. If I don't have that many animated images, yet it still crashes, I can only assume it is because of some really big or really long GIF images? In that case if I take your suggestion of using thumbnail decoding, with a limit of maybe 1000 x 1000, it should help with "Big images" i think? And then by using the maxBufferSize, it could help with "Long images" then? The buffer is kept on the SDAnimatedImageView right? If SwiftUI deallocates the view, the buffer should be released I think? For UIKit's case, when I reuse a SDAnimatedImageView and load in a new GIF, will the previous buffer be cleared? Do I still need to pass "clearBufferWhenStopped" just in case? And lastly sorry for so many questions XD, just out of curiosity, 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, animted image = frame count * bytes per frame (= bytesPerPixel * pixel count)
Yes, but pay attention this is not global control but for current image view
Yes, then buffer is just a NSMutableDictionary hosted in SDAnimatedPlayer which retained by SDAnimatedImageView
Yes, a new setImage: call cause that SDAnimatedImagePlayer been dealloced, so as buffer NSMutableDictionary
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
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 |
Great. Thanks a lot. I have a clear picture now. I will try all these techniques. I think i am supposed to close the ticket now? I'll go do that. |
@dreampiggy Sorry to bother again. After some detail checking, I found that most of the GIFs we encounter are really long, hundreds of frames, their sizes however are way below 1000 x 1000. I have applied clearBufferWhenStopped = true and a max buffer size of 10 MB for all animated image views. But I notice something strange. When I open a navigation page containing a lot of these animated images, the memory usage goes from 100 MB to somewhere around 200 MB. As I scroll i can confirm that memory is being released and allocated back and forth, and under good control. However when I push another navigation page onto the stack, triggering window to become nil for all the animated images (can confirm window becomes nil and animation stops), the memory usage displayed by Xcode is still around 200 MB, CPU usage drops drastically as all the animation stops and the new page has little UI elements on it. I tried different settings, including setting max buffer size to 1, manually clearing frame buffer, disabling storing cache to memory. Nothing helps. Not sure what is holding onto that memory? When i finally dismiss the initial navigation page that contains all the animated images, Xcode displays memory usage drops back down to around 100 MB. Any clue as to why it is like this? Does the SDAnimatedImage itself consume a lot of memory? Or the loaded image & data is held somewhere that i am not aware of? I checked my code, and I don't think i am strongly holding onto any of these (besides setting the image of course) |
SD is open source project. If you think that clearBufferWhenStopped behaves incorrectly, you can have a debug to check actual reason. PR is welcomed.
SDAnimatedImage retain original image data, but it should not be large compared to all frame buffers. But pay attention if you use UIImageView without "decodeFirstFrameOnly", then it will create "_UIAnimatedImage" which retain all frame into memory. Use Xcode's memory graph tool to see all the object living in the memory |
@dreampiggy I see. I experimented more and is able to confirm that "clearBufferWhenStopped" works correctly. However there is not much buffer to discard in the first place. When clearing all the buffers, it merely shreds off ~20MB of memory, the majority of the memory is used by SDAnimatedImage itself. I am not sure if this is an issue with the animated image format or the animated image coder. I found an alternative solution for now: I simply de-allocate the SDAnimatedImage when a SDAnimatedImageView loses its window, by setting image = nil. And then query for it again from disk later when I need it. This successfully drops the 200 MB usage back to 100 MB when i am not on that page, and then it grows back to 200 MB when i come to the page again. Another thing I found is that "thumbnailPixelSize" option does not work for the animated images we encounter. It works on static images only. For animated images, setting a small value does not affect the resulting size, until I set something really small, such as (50,50), which then simply makes the animated image not animate, but its resolution is still never affected. I suspect we are not dealing with GIF here, but APNG instead? I have little knowledge of all the image formats and decoding, pretty clueless. But at least the solution i came up kinda fixes the problem. Thanks again for all the suggestions and help! Edit: Ah, I realized some more. I have set a global memory cache limit of 50MB, so there wouldn't be much cache at all, setting maxBufferSize does not help, since there's already a global limit of 50. Most of the memory usage only comes from the image data, i simply have too many images on screen, and CPU usage is also super high as many frames are decoded again and again. There's not much to do here, we simply need to not push things to the limit. |
Problem solved? What is the ultimate solution? |
I think The ultimate solution, is to refactor the current Previous it use the individual control, like But since, RAM is a resource We should move the |
have you made any modifications to the plan? this bug has been around for a long time |
This is not It's actually OOM To reduce memory usage, whatever in SDWebImage's loading system, or your App code, need to consider more. |
@hnny09 I also feel that it can't be quite considered a bug, it does not happen normally, but only happens when you have a ton of animated images and the memory they occupy exceed the amount the app could handle. The ultimate solution is to just handle memory better. What I came up in the end is simple:
Of course any improvement or safety that can be implemented from SDWebImage's end would also be appreciated.. |
This can be easily changed because in SDWebImage 5.x Because If we set the There are already something handled during |
You can configurate it in SD. Using the options processor to filter |
Many of them The only or better things we can do , it's to provide the default options (behavior) which match most of users. However, SD is never designed to provide any |
Anyway, I think the We can not simply be stupid and create a dispatch queue then submit all ImageIO decoding work into that queue. Which is wrong. All the decoding process should be controlled and scheduled, or blocking, or cancelling |
ok, I check my code. |
Yes, I understand this. I did it this way because the animated images themselves, even when not decoded, was taking about 10MB each. It was just the extreme case I encountered, not a common issue. If we have 20 of these images on screen, it would be 200MB + memory already, not even considering the buffer. So I only tried to de-allocate the image completely due to the extreme scenario I encountered. It's not something others should follow. Because the image disappears totally, I just keep the url around and query again from disk when I need it again.
Yeah sounds like a good idea! Thanks! |
New Issue Checklist
Issue Info
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:
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!
The text was updated successfully, but these errors were encountered: