-
-
Notifications
You must be signed in to change notification settings - Fork 6k
Common Problems
UITableView determines the size of the image by the first image set for a cell. If your remote images don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue. The following article gives a way to workaround this issue:
http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/
You may not need all these configurations, pick what you need base on the use case.
- Objective-C
SDImageCache.sharedImageCache.config.maxDiskAge = 3600 * 24 * 7; // 1 Week
SDImageCache.sharedImageCache.config.maxMemoryCost = 1024 * 1024 * 4 * 20; // 20 images (1024 * 1024 pixels)
SDImageCache.sharedImageCache.config.shouldCacheImagesInMemory = NO; // Disable memory cache, may cause cell-reusing flash because disk query is async
SDImageCache.sharedImageCache.config.shouldUseWeakMemoryCache = NO; // Disable weak cache, may see blank when return from background because memory cache is purged under pressure
SDImageCache.sharedImageCache.config.diskCacheReadingOptions = NSDataReadingMappedIfSafe; // Use mmap for disk cache query
SDWebImageManager.sharedManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
// Disable Force Decoding in global, may reduce the frame rate
options |= SDWebImageAvoidDecodeImage;
return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
}];
- Swift
SDImageCache.shared.config.maxDiskAge = 3600 * 24 * 7 // 1 Week
SDImageCache.shared.config.maxMemoryCost = 1024 * 1024 * 4 * 20 // 20 images (1024 * 1024 pixels)
SDImageCache.shared.config.shouldCacheImagesInMemory = false // Disable memory cache, may cause cell-reusing flash because disk query is async
SDImageCache.shared.config.shouldUseWeakMemoryCache = false // Disable weak cache, may see blank when return from background because memory cache is purged under pressure
SDImageCache.shared.config.diskCacheReadingOptions = .mappedIfSafe // Use mmap for disk cache query
SDWebImageManager.shared.optionsProcessor = SDWebImageOptionsProcessor() { url, options, context in
// Disable Force Decoding in global, may reduce the frame rate
var mutableOptions = options
mutableOptions.insert(.avoidDecodeImage)
return SDWebImageOptionsResult(options: mutableOptions, context: context)
}
For bitmap image, the memory usage is simple to follows the formula:
RAM = pixel count * bytes per pixel (4 for RGBA)
So, try to reduce both resolution and channel as much as you can.
-
Channel
- Use non-alpha channel image if possible (25% reduce)
- Use greyscale image if possible (75% reduce)
-
Resolution
- Best: Optimize image resolution on server, before sending to client
- Use SDWebImageScaleDownLargeImages option
- Use Resize Image Transformer to scale down
- Use
.imageThumbnailPixelSize
context option to use Thumbnail Decoding, which does not allocate original image buffer size, but only with your thumbnail size. For example, if you want to load an image which have 10000*10000 pixels, use thumbnail at 1000*1000 pixels can save RAM from 381 MB to 3.81 MB.
SDAnimatedImageView provide the Decoding Just in Time feature for animated image. It also provides a flexible buffer size and based on current CPU/RAM pressure.
Compared to UIImageView (which keep all frames in memory), it can help you to reduce the memory usage. Especially you have large and long frames animated images.
For all these details see #1544
The View Category including UIImage+WebCache
, UIView+WebCache
or some other categories supports cell reusing from the scratch design. Actually, for most of use cases. All you need to do to work with cell reusing in UITableView
, UICollectionView
on iOS (NSTableView
, NSCollectionView
on macOS) is quite simple, just use:
- Objective-C:
[cell.imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder"]];
- Swift:
cell.imageView?.sd_setImage(with: url, placeholderImage: UIImage(named: “placeholder"))
However, there are some options to control the detailed behavior using for View Category such as SDWebImageDelayPlaceholder
, SDWebImageAvoidAutoSetImage
. You can try these options if the default behavior cause some issues with your use cases.
-
When you call
sd_setImageWithURL:
, whatever you provide a valid url or nil, we will firstly set the placeholder(Or nil, which means will clear the current rendered image) to the current imageView's image before any cache or network request. Then the placeholder argument is totally unused, unless you specifySDWebImageDelayPlaceholder
. -
If the image fetched in memory cache, the callback from manager is synchronously and is dispatched to the main queue if need(If you already call
sd_setImageWithURL:
in the main queue, there is no queue disaptch and just like a normal function call). So it will immediately set the result image to override the placeholder. In the set image process, we will call setNeedsLayout to mark the view it's ready for rendering. UIKit draw all the views during the same runloop(No asynchronous or queue dispatch) depended on view's first state and last state but not the intermediate state. The state we set the placeholder(or nil) at the beginning will be totally ignored and not even rendering to cause a flashing. If you do not understand this behavior, see Understanding UIKit Rendering -
If the image fetched in disk cache, the callback is asynchronously and dispatched from the cache queue to the main queue. So the placeholder (or nil) will be rendered with a little short time(depend on the IO speed and decoding time. This may looks like a flashing). And then replaced by the final image. (Note if you really do not want any flashing, try Image Transition to reduce this visual effect. Or using
SDWebImageQueryDiskSync
, which may decrease the frame rate) -
If the image fetched from network, the callback is asynchronously and dispatched from the downloader session queue to the main queue. So the placeholder (or nil) will be rendered with a long time(depend on the network speed and decoding time). And then replcaed by the final image. This behavior is nearlly the same as disk cache except the duration for placeholder rendering.(network speed is far more slower than disk cache)
-
If you use
SDWebImageDelayPlaceholder
, we will use the placeholder as target image after the fetch finished if the image is nil.
SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly.
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the SDWebImageRefreshCached
flag. This will slightly degrade the performance but will respect the HTTP caching control headers:
- Objective-C
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"]
placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"]
options:SDWebImageRefreshCached];
- Swift
imageView.sd_setImage(with: URL(string: "https://graph.facebook.com/olivier.poitrey/picture"), placeholderImage: UIImage(named: "avatar-placeholder.png"), options: .refreshCached)
Add these before you call sd_setImageWithURL
- Objective-C
[imageView sd_setShowActivityIndicatorView:YES];
[imageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];
- Swift
imageView.sd_setShowActivityIndicatorView(true)
imageView.sd_setIndicatorStyle(.Gray)
When you try to use sd_setImageWithURL:completed
and write some code in the completion block which contains self
. You should take care that this may cause Strong Reference Cycles for Closures
. This because the completion block you provided will be retained by SDWebImageManager
's running operations. So the self
in the completionBlock will also be captured until the load progress finished (Cache fetched or network finished).
If you do not want to keep self
instance alive during load progress, just mark it to weak.
- For Objective-C, use a weak reference to
self
:
__weak typeof(self) wself = self;
[self.imageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
wself.imageView.image = image;
}];
- For Swift, use
weak self
in capture lists:
self.imageView.sd_setImage(with: url, completed: { [weak self] (image, error, cacheType, imageURL) in
self?.imageView.image = image
})
SDWebImage supports to load images into your view site, but however, UIKit have the behavior to only render the last state in the same runloop. See more about this behavior: Understanding UIKit Rendering
Because of this, there may be something complicated use case when query synchronously or asynchronously. SDWebImage provide 3 options can control all the behavior you need, though it's a little confused. Here I list the result by combining these 3 options. More clear.
-
SDWebImageQueryMemoryData
: means we want image data, but not care about it's async or sync -
SDWebImageQueryMemoryDataSync
: means we want image data, and it must be sync when data from memory, must be combined withSDWebImageQueryMemoryData
-
SDWebImageQueryDiskDataSync
: means we want image data, and it must be sync when data from disk
Options | Memory hit data | Memory miss data |
---|---|---|
0 | nil | async |
QueryMemoryDataSync (invalid) | nil | async |
QueryDiskDataSync | nil | sync |
QueryMemoryData | async | async |
QueryMemoryData + QueryMemoryDataSync | sync | async |
QueryMemoryData + QueryDiskDataSync | async | sync |
QueryMemoryData + QueryMemoryDataSync + QueryDiskDataSync | sync | sync |
In general, SDWebImage's loading system, do not care whether the image is downloaded from network (via SDWebImageDownloader
), or even from extra resource like Photos (via SDImagePhotosLoader), Firebase (via FUIStorageImageLoader). So there is no something like NSURLResponse
or NSURLSessionTaskMetrics
inside the current 5.x completion block. (Note this design may be changed in the future)
But some users need to grab the underlaying information of HTTP response, or monitor the HTTP loading metrics. Here we provide one possible solution for this task:
Firstly, you need to get the SDWebImageCombinedOperation. You can do this via operation key, or keep the return value from View Category API.
Next, there is one loaderOperation
property. If you use SDWebImageDownloader
as loader to load HTTP URL, then you can cast that into the actual SDWebImageDownloadToken class. (For other loader like Firebase, check their API documentation)
Finally you can just grab the response
and metrics
.
- Objective-C
[imageView sd_setImageWithURL:url
completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
// Grab the combined operation
SDWebImageCombinedOperation *operation = [imageView sd_imageLoadOperationForKey:imageView.sd_latestOperationKey];
if (![operation isKindOfClass:[SDWebImageCombinedOperation class]]) {
return;
}
// Cast to actual class
SDWebImageDownloadToken *token = operation.loaderOperation;
if (![token isKindOfClass:[SDWebImageDownloadToken class]]) {
return;
}
// Use the response and metrics
NSURLResponse *response = token.response;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSLog(@"Response Headers: %@", ((NSHTTPURLResponse *)response).allHeaderFields);
NSLog(@"Response Status: %ld", (long)((NSHTTPURLResponse *)response).statusCode);
}
if (@available(iOS 10.0, *)) {
NSURLSessionTaskMetrics *metrics = token.metrics;
if (metrics) {
NSLog(@"Metrics: %@ download in (%f) seconds", imageURL.absoluteString , metrics.taskInterval.duration);
}
}
}];
- Swift
imageView.sd_setImage(with: url) { (image, error, _, _) in
// Grab the combined operation
guard let operation = imageView.sd_imageLoadOperation(forKey: imageView.sd_latestOperationKey) as? SDWebImageCombinedOperation else {
return
}
// Cast to actual class
guard let token = operation.loaderOperation as? SDWebImageDownloadToken else {
return
}
// Use the response and metrics
if let response = token.response as? HTTPURLResponse {
print("Response Headers: \(response.allHeaderFields)")
print("Response Status: \(response.statusCode)")
}
if #available(iOS 10.0, *) {
if let metrics = token.metrics {
print("Metrics: \(url.absoluteString) download in (\(metrics.taskInterval.duration)) seconds")
}
}
}