Merge branch 'master' of https://github.com/rs/SDWebImage into 5.x

* 'master' of https://github.com/rs/SDWebImage:
  Bumped version to 4.3.0
  update CHANGELOG
  Update the readme
  Update the readme and issue template
  Use a lock to ensure headers mutable dictionary thread-safe
  Do not hard-code cache policy. Use SDWebImageDownloaderUseNSURLCache to check
  Use the correct way to specify cancel if the response status code is invalid.
  Ensure all the session delegate completionHandler called. Fix the leak when response error code below iOS 10
  Fix the issue that prefetcher will cause stack overflow is the input urls list is huge because of recursion function call
  Update the comments for image transition
  Expose the associate FLAnimatedImage to user for advanced usage. Update the comments
This commit is contained in:
DreamPiggy 2018-02-01 11:53:13 +08:00
commit af282c1d5e
11 changed files with 171 additions and 135 deletions

View File

@ -8,11 +8,11 @@
Info | Value |
-------------------------|-------------------------------------|
Platform Name | e.g. ios / tvos
Platform Version | e.g. 8.0
SDWebImage Version | e.g. 3.7.6
Platform Name | e.g. ios / macos / tvos / watchos
Platform Version | e.g. 11.0 / 10.13.0 / 11.0 / 4.0
SDWebImage Version | e.g. 4.2.0 / 4.1.0
Integration Method | e.g. carthage / cocoapods / manually
Xcode Version | e.g. Xcode 7.3
Xcode Version | e.g. Xcode 9 / Xcode 8
Repro rate | e.g. all the time (100%) / sometimes x% / only once
Repro with our demo prj | e.g. does it happen with our demo project?
Demo project link | e.g. link to a demo project that highlights the issue

View File

@ -1,3 +1,48 @@
## [4.3.0 - Image Progress & Transition, on Jan 31th, 2018](https://github.com/rs/SDWebImage/releases/tag/4.3.0)
See [all tickets marked for the 4.3.0 release](https://github.com/rs/SDWebImage/milestone/21)
#### Features
- View Category
- Add NSProgress API in `sd_imageProgress` property represent the image loading progress, this allow user add KVO on it for complicated logic #2172
- Add Image Transition API in `sd_imageTransition` property support custom image transition after image load finished #2182
- Add NSButton WebCache category on macOS #2183
- Cache
- Add query cache options for query method #2162
- Add sync version API diskImageDataExistsWithKey and diskCacheWritingOptions #2190
- Manager
- Add a option SDWebImageFromCacheOnly to load the image from cache only and prevent network #2186
#### Fixes
- Coder
- Fix GIF loopCount when the GIF image has no Netscape 2.0 loop extension #2155
- View Category
- Fix the issue that `setAnimationImagesWithURLs` weak reference may dealloc before the animated images was set #2178
- Cache
- Fix the getSize method which use the default file manager instead of current file manager #2180
- Manager
- Fix the leak of runningOperations in race condition #2177 (Manager)
- Downloader
- Ensure all the session delegate completionHandler called and fix the leak when response error code below iOS 10 #2197
- Fix dispatch_sync blocking the main queue on race condition #2184
- Fix the thread-safe issue for headers mutable dictionary in downlaoder #2204
- Download Operation
- Fix that 0 pixels error should be used when width OR height is zero but not AND #2160
- Use the synchronized to access NSURLCache and try fix the potential thread-safe problem #2174
- Prefetcher
- Fix the issue that prefetcher will cause stack overflow when the input urls list is huge because of recursion function call #2196
#### Performance
- View Category
- Use the associate object to store the FLAnimatedImage into memory cache, avoid blinking or UIView transaction #2181
#### Improvements
- Cache
- Remove the extra memory warning notification for AutoPurgeCache #2153
- Downloader
- Avoid user accidentally invalidates the session used in shared downloader #2154
- Project
- Update the spec file to define the dependency version for libwebp #2169
## [4.2.3 - 4.2 Patch, on Nov 30th, 2017](https://github.com/rs/SDWebImage/releases/tag/4.2.3)
See [all tickets marked for the 4.2.3 release](https://github.com/rs/SDWebImage/milestone/20)

View File

@ -48,11 +48,12 @@ This library provides an async image downloader with cache support. For convenie
- Read this Readme doc
- Read the [How to use section](https://github.com/rs/SDWebImage#how-to-use)
- Read the [documentation @ CocoaDocs](http://cocoadocs.org/docsets/SDWebImage/)
- Read [How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F)
- Read the [Documentation @ CocoaDocs](http://cocoadocs.org/docsets/SDWebImage/)
- Try the example by downloading the project from Github or even easier using CocoaPods try `pod try SDWebImage`
- Get to the [installation steps](https://github.com/rs/SDWebImage#installation)
- Read the [Installation Guide](https://github.com/rs/SDWebImage/wiki/Installation-Guide)
- Read the [SDWebImage 4.0 Migration Guide](Docs/SDWebImage-4.0-Migration-guide.md) to get an idea of the changes from 3.x to 4.x
- Read the [Common Problems](https://github.com/rs/SDWebImage/wiki/Common-Problems) to find the solution for common problems
- Go to the [Wiki Page](https://github.com/rs/SDWebImage/wiki) for more information such as [Advanced Usage](https://github.com/rs/SDWebImage/wiki/Advanced-Usage)
## Who Uses It
- Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list.
@ -67,18 +68,18 @@ This library provides an async image downloader with cache support. For convenie
## How To Use
```objective-c
Objective-C:
* Objective-C
```objective-c
#import <SDWebImage/UIImageView+WebCache.h>
...
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
```
```swift
Swift:
* Swift
```swift
import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
@ -91,46 +92,9 @@ imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg
- Starting with the 4.0 version, we rely on [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) to take care of our animated images.
- If you use cocoapods, add `pod 'SDWebImage/GIF'` to your podfile.
- To use it, simply make sure you use `FLAnimatedImageView` instead of `UIImageView`.
- **Note**: there is a backwards compatible feature, so if you are still trying to load a GIF into a `UIImageView`, it will only show the 1st frame as a static image.
- **Note**: there is a backwards compatible feature, so if you are still trying to load a GIF into a `UIImageView`, it will only show the 1st frame as a static image by default. However, you can enable the full GIF support by using the built-in GIF coder. See [GIF coder](https://github.com/rs/SDWebImage/wiki/Advanced-Usage#gif-coder)
- **Important**: FLAnimatedImage only works on the iOS platform. For OS X, use `NSImageView` with `animates` set to `YES` to show the entire animated images and `NO` to only show the 1st frame. For all the other platforms (tvOS, watchOS) we will fallback to the backwards compatibility feature described above
## Common Problems
### Using dynamic image size with UITableViewCell
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/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/)
### Handle image refresh
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];
```
### Add a progress indicator
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)
```
## Installation
There are three ways to use SDWebImage in your project:

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '4.2.3'
s.version = '4.3.0'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'

View File

@ -18,6 +18,18 @@
#import "SDWebImageManager.h"
/**
* FLAnimatedImage is not a subclass of UIImage, so it's not possible to store it into the memory cache currently. However, for performance issue and cell reuse on FLAnimatedImageView, we use associate object to bind a FLAnimatedImage into UIImage when an animated GIF image load. For most cases, you don't need to touch this.
*/
@interface UIImage (FLAnimatedImage)
/**
* The FLAnimatedImage associated to the UIImage instance when an animated GIF image load.
*/
@property (nonatomic, strong, readonly, nullable) FLAnimatedImage *sd_FLAnimatedImage;
@end
/**
* A category for the FLAnimatedImage imageView class that hooks it to the SDWebImage system.

View File

@ -15,12 +15,6 @@
#import "NSData+ImageContentType.h"
#import "UIImageView+WebCache.h"
@interface UIImage (FLAnimatedImage)
@property (nonatomic, strong) FLAnimatedImage *sd_FLAnimatedImage;
@end
@implementation UIImage (FLAnimatedImage)
- (FLAnimatedImage *)sd_FLAnimatedImage {
@ -71,37 +65,36 @@
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
// We could not directlly create the animated image on bacakground queue because it's time consuming, by the time we set it back, the current runloop has passed and the placeholder has been rendered and then replaced with animated image, this cause a flashing.
// Previously we use a trick to firstly set the static poster image, then set animated image back to avoid flashing, but this trick fail when using with UIView transition because it's based on the Core Animation. Core Animation will capture the current layer state to do rendering, so even we later set it back, the transition will not update. (it's recommended to use `SDWebImageTransition` instead)
// Previously we use a trick to firstly set the static poster image, then set animated image back to avoid flashing, but this trick fail when using with custom UIView transition. Core Animation will use the current layer state to do rendering, so even we later set it back, the transition will not update. (it's recommended to use `SDWebImageTransition` instead)
// So we have no choice to force store the FLAnimatedImage into memory cache using a associated object binding to UIImage instance. This consumed memory is adoptable and much smaller than `_UIAnimatedImage` for big GIF
FLAnimatedImage *associatedAnimatedImage = image.sd_FLAnimatedImage;
if (associatedAnimatedImage || imageFormat == SDImageFormatGIF) {
if (associatedAnimatedImage) {
weakSelf.animatedImage = associatedAnimatedImage;
weakSelf.image = nil;
if (group) {
dispatch_group_leave(group);
}
} else {
// Firstly set the static poster image to avoid flashing
UIImage *posterImage = image.images ? image.images.firstObject : image;
weakSelf.image = posterImage;
weakSelf.animatedImage = nil;
// The imageData should not be nil, create FLAnimatedImage in global queue because it's time consuming, then set it back
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
FLAnimatedImage *animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
image.sd_FLAnimatedImage = animatedImage;
weakSelf.animatedImage = animatedImage;
weakSelf.image = nil;
if (group) {
dispatch_group_leave(group);
}
});
});
if (associatedAnimatedImage) {
// Asscociated animated image exist
weakSelf.animatedImage = associatedAnimatedImage;
weakSelf.image = nil;
if (group) {
dispatch_group_leave(group);
}
} else if ([NSData sd_imageFormatForImageData:imageData] == SDImageFormatGIF) {
// Firstly set the static poster image to avoid flashing
UIImage *posterImage = image.images ? image.images.firstObject : image;
weakSelf.image = posterImage;
weakSelf.animatedImage = nil;
// Secondly create FLAnimatedImage in global queue because it's time consuming, then set it back
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
FLAnimatedImage *animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
image.sd_FLAnimatedImage = animatedImage;
weakSelf.animatedImage = animatedImage;
weakSelf.image = nil;
if (group) {
dispatch_group_leave(group);
}
});
});
} else {
// Not animated image
weakSelf.image = image;
weakSelf.animatedImage = nil;
if (group) {

View File

@ -40,6 +40,7 @@
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@ -99,6 +100,7 @@
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
_downloadTimeout = 15.0;
[self createNewSessionWithConfiguration:sessionConfiguration];
@ -144,15 +146,27 @@
}
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
LOCK(self.headersLock);
if (value) {
self.HTTPHeaders[field] = value;
} else {
[self.HTTPHeaders removeObjectForKey:field];
}
UNLOCK(self.headersLock);
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
return self.HTTPHeaders[field];
if (!field) {
return nil;
}
return [[self allHTTPHeaderFields] objectForKey:field];
}
- (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields {
LOCK(self.headersLock);
SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
UNLOCK(self.headersLock);
return allHTTPHeaderFields;
}
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
@ -206,10 +220,10 @@
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
}
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
@ -324,16 +338,22 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
}
- (void)URLSession:(NSURLSession *)session
@ -343,8 +363,13 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
}
#pragma mark NSURLSessionTaskDelegate
@ -353,19 +378,21 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
completionHandler(request);
if (completionHandler) {
completionHandler(request);
}
}
}
@ -373,8 +400,13 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
}
@end

View File

@ -184,7 +184,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
}
#if SD_UIKIT
@ -217,7 +217,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the connection, its callback won't be called and thus won't
// As we cancelled the task, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
@ -280,48 +280,36 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
self.expectedSize = expected;
self.response = response;
//'304 Not Modified' is an exceptional one
//'304 Not Modified' is an exceptional one. It should be treated as cancelled.
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
self.expectedSize = expected;
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
} else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
// Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
disposition = NSURLSessionResponseCancel;
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
completionHandler(disposition);
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
if (!self.imageData) {
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
}
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
@ -367,7 +355,7 @@ didReceiveResponse:(NSURLResponse *)response
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
if (!(self.options & SDWebImageDownloaderUseNSURLCache)) {
// Prevents caching of responses
cachedResponse = nil;
}

View File

@ -80,7 +80,8 @@
];
}
if (self.prefetchURLs.count > self.requestedCount) {
dispatch_queue_async_safe(self.prefetcherQueue, ^{
dispatch_async(self.prefetcherQueue, ^{
// we need dispatch to avoid function recursion call. This can prevent stack overflow even for huge urls list
[self startPrefetchingAtIndex:self.requestedCount];
});
} else if (self.finishedCount == self.requestedCount) {

View File

@ -11,8 +11,9 @@
#if SD_UIKIT || SD_MAC
#import "SDImageCache.h"
// for UIKit(iOS & tvOS), we use `+[UIView transitionWithView:duration:options:animations:completion]` for transition animations.
// for AppKit(macOS), we use `+[NSAnimationContext runAnimationGroup:completionHandler:]` for transition animations. You can call `+[NSAnimationContext currentContext]` to grab the context during animations block.
// This class is used to provide a transition animation after the view category load image finished. Use this on `sd_imageTransition` in UIView+WebCache.h
// for UIKit(iOS & tvOS), we use `+[UIView transitionWithView:duration:options:animations:completion]` for transition animation.
// for AppKit(macOS), we use `+[NSAnimationContext runAnimationGroup:completionHandler:]` for transition animation. You can call `+[NSAnimationContext currentContext]` to grab the context during animations block.
// These transition are provided for basic usage. If you need complicated animation, consider to directly use Core Animation or use `SDWebImageAvoidAutoSetImage` and implement your own after image load finished.
#if SD_UIKIT
@ -60,7 +61,7 @@ typedef void (^SDWebImageTransitionCompletionBlock)(BOOL finished);
@end
// Convenience way to create transition. Remember to specify the duration
// Convenience way to create transition. Remember to specify the duration if needed.
// for UIKit, these transition just use the correspond `animationOptions`
// for AppKit, these transition use Core Animation in `animations`. So your view must be layer-backed. Set `wantsLayer = YES` before you apply it.

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.2.3</string>
<string>4.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4.2.3</string>
<string>4.3.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>