seperate iOS project from main project

This commit is contained in:
pengfei.zhou
2019-12-04 13:29:26 +08:00
commit ca06b501d8
473 changed files with 40553 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
//
// CALayer+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Web image methods for CALayer.
It will set image to layer.contents.
*/
@interface CALayer (YYWebImage)
#pragma mark - image
/**
Current image URL.
@discussion Set a new value to this property will cancel the previous request
operation and create a new request operation to fetch image. Set nil to clear
the image and image URL.
*/
@property (nullable, nonatomic, strong) NSURL *yy_imageURL;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param options The options to use when request the image.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder he image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current image request.
*/
- (void)yy_cancelCurrentImageRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,189 @@
//
// CALayer+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "CALayer+YYWebImage.h"
#import "YYWebImageOperation.h"
#import "_YYWebImageSetter.h"
#import <objc/runtime.h>
// Dummy class for category
@interface CALayer_YYWebImage : NSObject @end
@implementation CALayer_YYWebImage @end
static int _YYWebImageSetterKey;
@implementation CALayer (YYWebImage)
- (NSURL *)yy_imageURL {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
return setter.imageURL;
}
- (void)setYy_imageURL:(NSURL *)imageURL {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:transform
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
[self removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.contents = (id)placeholder.CGImage;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.contents = (id)imageFromMemory.CGImage;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.contents = (id)placeholder.CGImage;
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
BOOL showFade = (options & YYWebImageOptionSetImageWithFadeAnimation);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.contents = (id)image.CGImage;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)yy_cancelCurrentImageRequest {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (setter) [setter cancel];
}
@end

View File

@@ -0,0 +1,109 @@
//
// MKAnnotationView+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Web image methods for MKAnnotationView.
*/
@interface MKAnnotationView (YYWebImage)
/**
Current image URL.
@discussion Set a new value to this property will cancel the previous request
operation and create a new request operation to fetch image. Set nil to clear
the image and image URL.
*/
@property (nullable, nonatomic, strong) NSURL *yy_imageURL;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param options The options to use when request the image.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder he image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current image request.
*/
- (void)yy_cancelCurrentImageRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,187 @@
//
// MKAnnotationView+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "MKAnnotationView+YYWebImage.h"
#import "YYWebImageOperation.h"
#import "_YYWebImageSetter.h"
#import <objc/runtime.h>
// Dummy class for category
@interface MKAnnotationView_YYWebImage : NSObject @end
@implementation MKAnnotationView_YYWebImage @end
static int _YYWebImageSetterKey;
@implementation MKAnnotationView (YYWebImage)
- (NSURL *)yy_imageURL {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
return setter.imageURL;
}
- (void)setYy_imageURL:(NSURL *)imageURL {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:transform
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (!self.highlighted) {
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.image = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.image = image;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)yy_cancelCurrentImageRequest {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (setter) [setter cancel];
}
@end

View File

@@ -0,0 +1,213 @@
//
// UIButton+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Web image methods for UIButton.
*/
@interface UIButton (YYWebImage)
#pragma mark - image
/**
Current image URL for the specified state.
@return The image URL, or nil.
*/
- (nullable NSURL *)yy_imageURLForState:(UIControlState)state;
/**
Set the button's image with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder;
/**
Set the button's image with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param options The options to use when request the image.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
options:(YYWebImageOptions)options;
/**
Set the button's image with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the button's image with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the button's image with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current image request for a specified state.
@param state The state that uses the specified image.
*/
- (void)yy_cancelImageRequestForState:(UIControlState)state;
#pragma mark - background image
/**
Current backgroundImage URL for the specified state.
@return The image URL, or nil.
*/
- (nullable NSURL *)yy_backgroundImageURLForState:(UIControlState)state;
/**
Set the button's backgroundImage with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder;
/**
Set the button's backgroundImage with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param options The options to use when request the image.
*/
- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
options:(YYWebImageOptions)options;
/**
Set the button's backgroundImage with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the button's backgroundImage with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the button's backgroundImage with a specified URL for the specified state.
@param imageURL The image url (remote or local file path).
@param state The state that uses the specified image.
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL
forState:(UIControlState)state
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current backgroundImage request for a specified state.
@param state The state that uses the specified image.
*/
- (void)yy_cancelBackgroundImageRequestForState:(UIControlState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,439 @@
//
// UIButton+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIButton+YYWebImage.h"
#import "YYWebImageOperation.h"
#import "_YYWebImageSetter.h"
#import <objc/runtime.h>
// Dummy class for category
@interface UIButton_YYWebImage : NSObject @end
@implementation UIButton_YYWebImage @end
static inline NSNumber *UIControlStateSingle(UIControlState state) {
if (state & UIControlStateHighlighted) return @(UIControlStateHighlighted);
if (state & UIControlStateDisabled) return @(UIControlStateDisabled);
if (state & UIControlStateSelected) return @(UIControlStateSelected);
return @(UIControlStateNormal);
}
static inline NSArray *UIControlStateMulti(UIControlState state) {
NSMutableArray *array = [NSMutableArray new];
if (state & UIControlStateHighlighted) [array addObject:@(UIControlStateHighlighted)];
if (state & UIControlStateDisabled) [array addObject:@(UIControlStateDisabled)];
if (state & UIControlStateSelected) [array addObject:@(UIControlStateSelected)];
if ((state & 0xFF) == 0) [array addObject:@(UIControlStateNormal)];
return array;
}
static int _YYWebImageSetterKey;
static int _YYWebImageBackgroundSetterKey;
@interface _YYWebImageSetterDicForButton : NSObject
- (_YYWebImageSetter *)setterForState:(NSNumber *)state;
- (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state;
@end
@implementation _YYWebImageSetterDicForButton {
NSMutableDictionary *_dic;
dispatch_semaphore_t _lock;
}
- (instancetype)init {
self = [super init];
_lock = dispatch_semaphore_create(1);
_dic = [NSMutableDictionary new];
return self;
}
- (_YYWebImageSetter *)setterForState:(NSNumber *)state {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
_YYWebImageSetter *setter = _dic[state];
dispatch_semaphore_signal(_lock);
return setter;
}
- (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
_YYWebImageSetter *setter = _dic[state];
if (!setter) {
setter = [_YYWebImageSetter new];
_dic[state] = setter;
}
dispatch_semaphore_signal(_lock);
return setter;
}
@end
@implementation UIButton (YYWebImage)
#pragma mark - image
- (void)_yy_setImageWithURL:(NSURL *)imageURL
forSingleState:(NSNumber *)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!dic) {
dic = [_YYWebImageSetterDicForButton new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
_YYWebImageSetter *setter = [dic lazySetterForState:state];
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
[self setImage:placeholder forState:state.integerValue];
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
[self setImage:imageFromMemory forState:state.integerValue];
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
[self setImage:placeholder forState:state.integerValue];
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
[self setImage:image forState:state.integerValue];
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)_yy_cancelImageRequestForSingleState:(NSNumber *)state {
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
_YYWebImageSetter *setter = [dic setterForState:state];
if (setter) [setter cancel];
}
- (NSURL *)yy_imageURLForState:(UIControlState)state {
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
_YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)];
return setter.imageURL;
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder {
[self yy_setImageWithURL:imageURL
forState:state
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
options:(YYWebImageOptions)options {
[self yy_setImageWithURL:imageURL
forState:state
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
forState:state
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
forState:state
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:transform
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
for (NSNumber *num in UIControlStateMulti(state)) {
[self _yy_setImageWithURL:imageURL
forSingleState:num
placeholder:placeholder
options:options
manager:manager
progress:progress
transform:transform
completion:completion];
}
}
- (void)yy_cancelImageRequestForState:(UIControlState)state {
for (NSNumber *num in UIControlStateMulti(state)) {
[self _yy_cancelImageRequestForSingleState:num];
}
}
#pragma mark - background image
- (void)_yy_setBackgroundImageWithURL:(NSURL *)imageURL
forSingleState:(NSNumber *)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
if (!dic) {
dic = [_YYWebImageSetterDicForButton new];
objc_setAssociatedObject(self, &_YYWebImageBackgroundSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
_YYWebImageSetter *setter = [dic lazySetterForState:state];
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
[self setBackgroundImage:placeholder forState:state.integerValue];
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
[self setBackgroundImage:imageFromMemory forState:state.integerValue];
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
[self setBackgroundImage:placeholder forState:state.integerValue];
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
[self setBackgroundImage:image forState:state.integerValue];
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)_yy_cancelBackgroundImageRequestForSingleState:(NSNumber *)state {
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
_YYWebImageSetter *setter = [dic setterForState:state];
if (setter) [setter cancel];
}
- (NSURL *)yy_backgroundImageURLForState:(UIControlState)state {
_YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
_YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)];
return setter.imageURL;
}
- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder {
[self yy_setBackgroundImageWithURL:imageURL
forState:state
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
options:(YYWebImageOptions)options {
[self yy_setBackgroundImageWithURL:imageURL
forState:state
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setBackgroundImageWithURL:imageURL
forState:state
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setBackgroundImageWithURL:imageURL
forState:state
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:transform
completion:completion];
}
- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
forState:(UIControlState)state
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
for (NSNumber *num in UIControlStateMulti(state)) {
[self _yy_setBackgroundImageWithURL:imageURL
forSingleState:num
placeholder:placeholder
options:options
manager:manager
progress:progress
transform:transform
completion:completion];
}
}
- (void)yy_cancelBackgroundImageRequestForState:(UIControlState)state {
for (NSNumber *num in UIControlStateMulti(state)) {
[self _yy_cancelBackgroundImageRequestForSingleState:num];
}
}
@end

View File

@@ -0,0 +1,316 @@
//
// UIImage+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some commen method for `UIImage`.
Image process is based on CoreGraphic and vImage.
*/
@interface UIImage (YYWebImage)
#pragma mark - Create image
///=============================================================================
/// @name Create image
///=============================================================================
/**
Create an animated image with GIF data. After created, you can access
the images via property '.images'. If the data is not animated gif, this
function is same as [UIImage imageWithData:data scale:scale];
@discussion It has a better display performance, but costs more memory
(width * height * frames Bytes). It only suited to display small
gif such as animated emoticon. If you want to display large gif,
see `YYImage`.
@param data GIF data.
@param scale The scale factor
@return A new image created from GIF, or nil when an error occurs.
*/
+ (nullable UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale;
/**
Create and return a 1x1 point size image with the given color.
@param color The color.
*/
+ (nullable UIImage *)yy_imageWithColor:(UIColor *)color;
/**
Create and return a pure color image with the given color and size.
@param color The color.
@param size New image's type.
*/
+ (nullable UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size;
/**
Create and return an image with custom draw code.
@param size The image size.
@param drawBlock The draw block.
@return The new image.
*/
+ (nullable UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock;
#pragma mark - Image Info
///=============================================================================
/// @name Image Info
///=============================================================================
/**
Whether this image has alpha channel.
*/
- (BOOL)yy_hasAlphaChannel;
#pragma mark - Modify Image
///=============================================================================
/// @name Modify Image
///=============================================================================
/**
Draws the entire image in the specified rectangle, content changed with
the contentMode.
@discussion This method draws the entire image in the current graphics context,
respecting the image's orientation setting. In the default coordinate system,
images are situated down and to the right of the origin of the specified
rectangle. This method respects any transforms applied to the current graphics
context, however.
@param rect The rectangle in which to draw the image.
@param contentMode Draw content mode
@param clips A Boolean value that determines whether content are confined to the rect.
*/
- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips;
/**
Returns a new image which is scaled from this image.
The image will be stretched as needed.
@param size The new size to be scaled, values should be positive.
@return The new image with the given size.
*/
- (nullable UIImage *)yy_imageByResizeToSize:(CGSize)size;
/**
Returns a new image which is scaled from this image.
The image content will be changed with thencontentMode.
@param size The new size to be scaled, values should be positive.
@param contentMode The content mode for image content.
@return The new image with the given size.
*/
- (nullable UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode;
/**
Returns a new image which is cropped from this image.
@param rect Image's inner rect.
@return The new image, or nil if an error occurs.
*/
- (nullable UIImage *)yy_imageByCropToRect:(CGRect)rect;
/**
Returns a new image which is edge inset from this image.
@param insets Inset (positive) for each of the edges, values can be negative to 'outset'.
@param color Extend edge's fill color, nil means clear color.
@return The new image, or nil if an error occurs.
*/
- (nullable UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(nullable UIColor *)color;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to half
the width or height.
*/
- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to
half the width or height.
@param borderWidth The inset border line width. Values larger than half the rectangle's
width or height are clamped appropriately to half the width
or height.
@param borderColor The border stroke color. nil means clear color.
*/
- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
borderColor:(nullable UIColor *)borderColor;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to
half the width or height.
@param corners A bitmask value that identifies the corners that you want
rounded. You can use this parameter to round only a subset
of the corners of the rectangle.
@param borderWidth The inset border line width. Values larger than half the rectangle's
width or height are clamped appropriately to half the width
or height.
@param borderColor The border stroke color. nil means clear color.
@param borderLineJoin The border line join.
*/
- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(nullable UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin;
/**
Returns a new rotated image (relative to the center).
@param radians Rotated radians in counterclockwise.⟲
@param fitSize YES: new image's size is extend to fit all content.
NO: image's size will not change, content may be clipped.
*/
- (nullable UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize;
/**
Returns a new image rotated counterclockwise by a quarterturn (90°). ⤺
The width and height will be exchanged.
*/
- (nullable UIImage *)yy_imageByRotateLeft90;
/**
Returns a new image rotated clockwise by a quarterturn (90°). ⤼
The width and height will be exchanged.
*/
- (nullable UIImage *)yy_imageByRotateRight90;
/**
Returns a new image rotated 180° . ↻
*/
- (nullable UIImage *)yy_imageByRotate180;
/**
Returns a vertically flipped image. ⥯
*/
- (nullable UIImage *)yy_imageByFlipVertical;
/**
Returns a horizontally flipped image. ⇋
*/
- (nullable UIImage *)yy_imageByFlipHorizontal;
#pragma mark - Image Effect
///=============================================================================
/// @name Image Effect
///=============================================================================
/**
Tint the image in alpha channel with the given color.
@param color The color.
*/
- (nullable UIImage *)yy_imageByTintColor:(UIColor *)color;
/**
Returns a grayscaled image.
*/
- (nullable UIImage *)yy_imageByGrayscale;
/**
Applies a blur effect to this image. Suitable for blur any content.
*/
- (nullable UIImage *)yy_imageByBlurSoft;
/**
Applies a blur effect to this image. Suitable for blur any content except pure white.
(same as iOS Control Panel)
*/
- (nullable UIImage *)yy_imageByBlurLight;
/**
Applies a blur effect to this image. Suitable for displaying black text.
(same as iOS Navigation Bar White)
*/
- (nullable UIImage *)yy_imageByBlurExtraLight;
/**
Applies a blur effect to this image. Suitable for displaying white text.
(same as iOS Notification Center)
*/
- (nullable UIImage *)yy_imageByBlurDark;
/**
Applies a blur and tint color to this image.
@param tintColor The tint color.
*/
- (nullable UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor;
/**
Applies a blur, tint color, and saturation adjustment to this image,
optionally within the area specified by @a maskImage.
@param blurRadius The radius of the blur in points, 0 means no blur effect.
@param tintColor An optional UIColor object that is uniformly blended with
the result of the blur and saturation operations. The
alpha channel of this color determines how strong the
tint is. nil means no tint.
@param tintBlendMode The @a tintColor blend mode. Default is kCGBlendModeNormal (0).
@param saturation A value of 1.0 produces no change in the resulting image.
Values less than 1.0 will desaturation the resulting image
while values greater than 1.0 will have the opposite effect.
0 means gray scale.
@param maskImage If specified, @a inputImage is only modified in the area(s)
defined by this mask. This must be an image mask or it
must meet the requirements of the mask parameter of
CGContextClipToMask.
@return image with effect, or nil if an error occurs (e.g. no
enough memory).
*/
- (nullable UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius
tintColor:(nullable UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(nullable UIImage *)maskImage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,745 @@
//
// UIImage+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIImage+YYWebImage.h"
#import <ImageIO/ImageIO.h>
#import <Accelerate/Accelerate.h>
#import <objc/runtime.h>
// Dummy class for category
@interface UIImage_YYWebImage : NSObject @end
@implementation UIImage_YYWebImage @end
/// Convert degrees to radians.
static inline CGFloat _DegreesToRadians(CGFloat degrees) {
return degrees * M_PI / 180;
}
/**
Resize rect to fit the size using a given contentMode.
@param rect The draw rect
@param size The content size
@param mode The content mode
@return A resized rect for the given content mode.
@discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
*/
static CGRect _YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
rect = CGRectStandardize(rect);
size.width = size.width < 0 ? -size.width : size.width;
size.height = size.height < 0 ? -size.height : size.height;
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
switch (mode) {
case UIViewContentModeScaleAspectFit:
case UIViewContentModeScaleAspectFill: {
if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
size.width < 0.01 || size.height < 0.01) {
rect.origin = center;
rect.size = CGSizeZero;
} else {
CGFloat scale;
if (mode == UIViewContentModeScaleAspectFit) {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.height / size.height;
} else {
scale = rect.size.width / size.width;
}
} else {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.width / size.width;
} else {
scale = rect.size.height / size.height;
}
}
size.width *= scale;
size.height *= scale;
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
}
} break;
case UIViewContentModeCenter: {
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
} break;
case UIViewContentModeTop: {
rect.origin.x = center.x - size.width * 0.5;
rect.size = size;
} break;
case UIViewContentModeBottom: {
rect.origin.x = center.x - size.width * 0.5;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeLeft: {
rect.origin.y = center.y - size.height * 0.5;
rect.size = size;
} break;
case UIViewContentModeRight: {
rect.origin.y = center.y - size.height * 0.5;
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeTopLeft: {
rect.size = size;
} break;
case UIViewContentModeTopRight: {
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeBottomLeft: {
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeBottomRight: {
rect.origin.x += rect.size.width - size.width;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeScaleToFill:
case UIViewContentModeRedraw:
default: {
rect = rect;
}
}
return rect;
}
static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
NSTimeInterval delay = 0;
CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
if (dic) {
CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
if (dicGIF) {
NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
if (num.doubleValue <= __FLT_EPSILON__) {
num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
}
delay = num.doubleValue;
}
CFRelease(dic);
}
// http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
if (delay < 0.02) delay = 0.1;
return delay;
}
@implementation UIImage (YYWebImage)
+ (UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
if (!source) return nil;
size_t count = CGImageSourceGetCount(source);
if (count <= 1) {
CFRelease(source);
return [self.class imageWithData:data scale:scale];
}
NSUInteger frames[count];
double oneFrameTime = 1 / 50.0; // 50 fps
NSTimeInterval totalTime = 0;
NSUInteger totalFrame = 0;
NSUInteger gcdFrame = 0;
for (size_t i = 0; i < count; i++) {
NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
totalTime += delay;
NSInteger frame = lrint(delay / oneFrameTime);
if (frame < 1) frame = 1;
frames[i] = frame;
totalFrame += frames[i];
if (i == 0) gcdFrame = frames[i];
else {
NSUInteger frame = frames[i], tmp;
if (frame < gcdFrame) {
tmp = frame; frame = gcdFrame; gcdFrame = tmp;
}
while (true) {
tmp = frame % gcdFrame;
if (tmp == 0) break;
frame = gcdFrame;
gcdFrame = tmp;
}
}
}
NSMutableArray *array = [NSMutableArray new];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
CFRelease(source);
return nil;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
if (width == 0 || height == 0) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
CGColorSpaceRelease(space);
if (!context) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef decoded = CGBitmapContextCreateImage(context);
CFRelease(context);
if (!decoded) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
UIImage *image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGImageRelease(decoded);
if (!image) {
CFRelease(source);
return nil;
}
for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
[array addObject:image];
}
}
CFRelease(source);
UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
return image;
}
+ (UIImage *)yy_imageWithColor:(UIColor *)color {
return [self yy_imageWithColor:color size:CGSizeMake(1, 1)];
}
+ (UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size {
if (!color || size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
if (!drawBlock) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) return nil;
drawBlock(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (BOOL)yy_hasAlphaChannel {
if (self.CGImage == NULL) return NO;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
return (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
}
- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
CGRect drawRect = _YYCGRectFitWithContentMode(rect, self.size, contentMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) {
CGContextRef context = UIGraphicsGetCurrentContext();
if (context) {
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
[self drawInRect:drawRect];
CGContextRestoreGState(context);
}
} else {
[self drawInRect:drawRect];
}
}
- (UIImage *)yy_imageByResizeToSize:(CGSize)size {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self yy_drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByCropToRect:(CGRect)rect {
rect.origin.x *= self.scale;
rect.origin.y *= self.scale;
rect.size.width *= self.scale;
rect.size.height *= self.scale;
if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imageRef);
return image;
}
- (UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
CGSize size = self.size;
size.width -= insets.left + insets.right;
size.height -= insets.top + insets.bottom;
if (size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (color) {
CGContextSetFillColorWithColor(context, color.CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
CGPathAddRect(path, NULL, rect);
CGContextAddPath(context, path);
CGContextEOFillPath(context);
CGPathRelease(path);
}
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius {
return [self yy_imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
}
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor {
return [self yy_imageByRoundCornerRadius:radius corners:UIRectCornerAllCorners borderWidth:borderWidth borderColor:borderColor borderLineJoin:kCGLineJoinMiter];
}
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin {
if (corners != UIRectCornerAllCorners) {
UIRectCorner tmp = 0;
if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage);
CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
[path closePath];
path.lineWidth = borderWidth;
path.lineJoinStyle = borderLineJoin;
[borderColor setStroke];
[path stroke];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
(size_t)newRect.size.width,
(size_t)newRect.size.height,
8,
(size_t)newRect.size.width * 4,
colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
CGContextRotateCTM(context, radians);
CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
CGContextRelease(context);
return img;
}
- (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
if (!data) {
CGContextRelease(context);
return nil;
}
vImage_Buffer src = { data, height, width, bytesPerRow };
vImage_Buffer dest = { data, height, width, bytesPerRow };
if (vertical) {
vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
if (horizontal) {
vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
return img;
}
- (UIImage *)yy_imageByRotateLeft90 {
return [self yy_imageByRotate:_DegreesToRadians(90) fitSize:YES];
}
- (UIImage *)yy_imageByRotateRight90 {
return [self yy_imageByRotate:_DegreesToRadians(-90) fitSize:YES];
}
- (UIImage *)yy_imageByRotate180 {
return [self _yy_flipHorizontal:YES vertical:YES];
}
- (UIImage *)yy_imageByFlipVertical {
return [self _yy_flipHorizontal:NO vertical:YES];
}
- (UIImage *)yy_imageByFlipHorizontal {
return [self _yy_flipHorizontal:YES vertical:NO];
}
- (UIImage *)yy_imageByTintColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
[color set];
UIRectFill(rect);
[self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage *)yy_imageByGrayscale {
return [self yy_imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
}
- (UIImage *)yy_imageByBlurSoft {
return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurLight {
return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurExtraLight {
return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurDark {
return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor {
const CGFloat EffectColorAlpha = 0.6;
UIColor *effectColor = tintColor;
size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
} else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self yy_imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
}
- (UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius
tintColor:(UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(UIImage *)maskImage {
if (self.size.width < 1 || self.size.height < 1) {
NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
// iOS7 and above can use new func.
BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
CGImageRef imageRef = self.CGImage;
BOOL opaque = NO;
if (!hasBlur && !hasSaturation) {
return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
vImage_Buffer effect = { 0 }, scratch = { 0 };
vImage_Buffer *input = NULL, *output = NULL;
vImage_CGImageFormat format = {
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
};
if (hasNewFunc) {
vImage_Error err;
err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
return nil;
}
err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
return nil;
}
} else {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef effectCtx = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectCtx, 1.0, -1.0);
CGContextTranslateCTM(effectCtx, 0, -size.height);
CGContextDrawImage(effectCtx, rect, imageRef);
effect.data = CGBitmapContextGetData(effectCtx);
effect.width = CGBitmapContextGetWidth(effectCtx);
effect.height = CGBitmapContextGetHeight(effectCtx);
effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
scratch.data = CGBitmapContextGetData(scratchCtx);
scratch.width = CGBitmapContextGetWidth(scratchCtx);
scratch.height = CGBitmapContextGetHeight(scratchCtx);
scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
}
input = &effect;
output = &scratch;
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * scale;
if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
radius |= 1; // force radius to be odd so that the three box-blur methodology works.
int iterations;
if (blurRadius * scale < 0.5) iterations = 1;
else if (blurRadius * scale < 1.5) iterations = 2;
else iterations = 3;
NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
void *temp = malloc(tempSize);
for (int i = 0; i < iterations; i++) {
vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
// swap
vImage_Buffer *swap_tmp = input;
input = output;
output = swap_tmp;
}
free(temp);
}
if (hasSaturation) {
// These values appear in the W3C Filter Effects spec:
// https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
CGFloat s = saturation;
CGFloat matrixFloat[] = {
0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
int16_t matrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
}
vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
// swap
vImage_Buffer *swap_tmp = input;
input = output;
output = swap_tmp;
}
UIImage *outputImage = nil;
if (hasNewFunc) {
CGImageRef effectCGImage = NULL;
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
if (effectCGImage == NULL) {
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
free(input->data);
}
free(output->data);
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
CGImageRelease(effectCGImage);
} else {
CGImageRef effectCGImage;
UIImage *effectImage;
if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
effectCGImage = effectImage.CGImage;
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
return outputImage;
}
// Helper function to handle deferred cleanup of a buffer.
static void _yy_cleanupBuffer(void *userData, void *buf_data) {
free(buf_data);
}
// Helper function to add tint and mask.
- (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
tintColor:(UIColor *)tintColor
tintBlendMode:(CGBlendMode)tintBlendMode
maskImage:(UIImage *)maskImage
opaque:(BOOL)opaque {
BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
BOOL hasMask = maskImage != nil;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
if (!hasTint && !hasMask) {
return [UIImage imageWithCGImage:effectCGImage];
}
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -size.height);
if (hasMask) {
CGContextDrawImage(context, rect, self.CGImage);
CGContextSaveGState(context);
CGContextClipToMask(context, rect, maskImage.CGImage);
}
CGContextDrawImage(context, rect, effectCGImage);
if (hasTint) {
CGContextSaveGState(context);
CGContextSetBlendMode(context, tintBlendMode);
CGContextSetFillColorWithColor(context, tintColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
}
if (hasMask) {
CGContextRestoreGState(context);
}
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
@end

View File

@@ -0,0 +1,193 @@
//
// UIImageView+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Web image methods for UIImageView.
*/
@interface UIImageView (YYWebImage)
#pragma mark - image
/**
Current image URL.
@discussion Set a new value to this property will cancel the previous request
operation and create a new request operation to fetch image. Set nil to clear
the image and image URL.
*/
@property (nullable, nonatomic, strong) NSURL *yy_imageURL;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param options The options to use when request the image.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `image` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder he image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current image request.
*/
- (void)yy_cancelCurrentImageRequest;
#pragma mark - highlight image
/**
Current highlighted image URL.
@discussion Set a new value to this property will cancel the previous request
operation and create a new request operation to fetch image. Set nil to clear
the highlighted image and image URL.
*/
@property (nullable, nonatomic, strong) NSURL *yy_highlightedImageURL;
/**
Set the view's `highlightedImage` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
*/
- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder;
/**
Set the view's `highlightedImage` with a specified URL.
@param imageURL The image url (remote or local file path).
@param options The options to use when request the image.
*/
- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options;
/**
Set the view's `highlightedImage` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `highlightedImage` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Set the view's `highlightedImage` with a specified URL.
@param imageURL The image url (remote or local file path).
@param placeholder The image to be set initially, until the image request finishes.
@param options The options to use when request the image.
@param manager The manager to create image request operation.
@param progress The block invoked (on main thread) during image request.
@param transform The block invoked (on background thread) to do additional image process.
@param completion The block invoked (on main thread) when image request completed.
*/
- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(nullable YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
Cancel the current highlighed image request.
*/
- (void)yy_cancelCurrentHighlightedImageRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,355 @@
//
// UIImageView+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIImageView+YYWebImage.h"
#import "YYWebImageOperation.h"
#import "_YYWebImageSetter.h"
#import <objc/runtime.h>
// Dummy class for category
@interface UIImageView_YYWebImage : NSObject @end
@implementation UIImageView_YYWebImage @end
static int _YYWebImageSetterKey;
static int _YYWebImageHighlightedSetterKey;
@implementation UIImageView (YYWebImage)
#pragma mark - image
- (NSURL *)yy_imageURL {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
return setter.imageURL;
}
- (void)setYy_imageURL:(NSURL *)imageURL {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:transform
completion:completion];
}
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (!self.highlighted) {
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.image = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.image = image;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)yy_cancelCurrentImageRequest {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (setter) [setter cancel];
}
#pragma mark - highlighted image
- (NSURL *)yy_highlightedImageURL {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey);
return setter.imageURL;
}
- (void)setYy_highlightedImageURL:(NSURL *)imageURL {
[self yy_setHighlightedImageWithURL:imageURL
placeholder:nil
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder {
[self yy_setHighlightedImageWithURL:imageURL
placeholder:placeholder
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options {
[self yy_setHighlightedImageWithURL:imageURL
placeholder:nil
options:options
manager:nil
progress:nil
transform:nil
completion:nil];
}
- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(YYWebImageCompletionBlock)completion {
[self yy_setHighlightedImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:nil
transform:nil
completion:completion];
}
- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
[self yy_setHighlightedImageWithURL:imageURL
placeholder:placeholder
options:options
manager:nil
progress:progress
transform:nil
completion:completion];
}
- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageHighlightedSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
_yy_dispatch_sync_on_main_queue(^{
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (self.highlighted) {
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.highlightedImage = placeholder;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.highlightedImage = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.highlightedImage = placeholder;
}
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && self.highlighted);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.highlightedImage = image;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
- (void)yy_cancelCurrentHighlightedImageRequest {
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey);
if (setter) [setter cancel];
}
@end

View File

@@ -0,0 +1,67 @@
//
// _YYWebImageSetter.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/7/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <pthread.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
Submits a block for execution on a main queue and waits until the block completes.
*/
static inline void _yy_dispatch_sync_on_main_queue(void (^block)()) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
extern NSString *const _YYWebImageFadeAnimationKey;
extern const NSTimeInterval _YYWebImageFadeTime;
extern const NSTimeInterval _YYWebImageProgressiveFadeTime;
/**
Private class used by web image categories.
Typically, you should not use this class directly.
*/
@interface _YYWebImageSetter : NSObject
/// Current image url.
@property (nullable, nonatomic, readonly) NSURL *imageURL;
/// Current sentinel.
@property (nonatomic, readonly) int32_t sentinel;
/// Create new operation for web image and return a sentinel value.
- (int32_t)setOperationWithSentinel:(int32_t)sentinel
url:(nullable NSURL *)imageURL
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/// Cancel and return a sentinel value. The imageURL will be set to nil.
- (int32_t)cancel;
/// Cancel and return a sentinel value. The imageURL will be set to new value.
- (int32_t)cancelWithNewURL:(nullable NSURL *)imageURL;
/// A queue to set operation.
+ (dispatch_queue_t)setterQueue;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,103 @@
//
// _YYWebImageSetter.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/7/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "_YYWebImageSetter.h"
#import "YYWebImageOperation.h"
#import <libkern/OSAtomic.h>
NSString *const _YYWebImageFadeAnimationKey = @"YYWebImageFade";
const NSTimeInterval _YYWebImageFadeTime = 0.2;
const NSTimeInterval _YYWebImageProgressiveFadeTime = 0.4;
@implementation _YYWebImageSetter {
dispatch_semaphore_t _lock;
NSURL *_imageURL;
NSOperation *_operation;
int32_t _sentinel;
}
- (instancetype)init {
self = [super init];
_lock = dispatch_semaphore_create(1);
return self;
}
- (NSURL *)imageURL {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
NSURL *imageURL = _imageURL;
dispatch_semaphore_signal(_lock);
return imageURL;
}
- (void)dealloc {
OSAtomicIncrement32(&_sentinel);
[_operation cancel];
}
- (int32_t)setOperationWithSentinel:(int32_t)sentinel
url:(NSURL *)imageURL
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if (sentinel != _sentinel) {
if (completion) completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
return _sentinel;
}
NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
if (!operation && completion) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"YYWebImageOperation create failed." };
completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageFinished, [NSError errorWithDomain:@"com.ibireme.webimage" code:-1 userInfo:userInfo]);
}
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
if (sentinel == _sentinel) {
if (_operation) [_operation cancel];
_operation = operation;
sentinel = OSAtomicIncrement32(&_sentinel);
} else {
[operation cancel];
}
dispatch_semaphore_signal(_lock);
return sentinel;
}
- (int32_t)cancel {
return [self cancelWithNewURL:nil];
}
- (int32_t)cancelWithNewURL:(NSURL *)imageURL {
int32_t sentinel;
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
if (_operation) {
[_operation cancel];
_operation = nil;
}
_imageURL = imageURL;
sentinel = OSAtomicIncrement32(&_sentinel);
dispatch_semaphore_signal(_lock);
return sentinel;
}
+ (dispatch_queue_t)setterQueue {
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.ibireme.webimage.setter", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
});
return queue;
}
@end

View File

@@ -0,0 +1,228 @@
//
// YYImageCache.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
@class YYMemoryCache, YYDiskCache;
NS_ASSUME_NONNULL_BEGIN
/// Image cache type
typedef NS_OPTIONS(NSUInteger, YYImageCacheType) {
/// No value.
YYImageCacheTypeNone = 0,
/// Get/store image with memory cache.
YYImageCacheTypeMemory = 1 << 0,
/// Get/store image with disk cache.
YYImageCacheTypeDisk = 1 << 1,
/// Get/store image with both memory cache and disk cache.
YYImageCacheTypeAll = YYImageCacheTypeMemory | YYImageCacheTypeDisk,
};
/**
YYImageCache is a cache that stores UIImage and image data based on memory cache and disk cache.
@discussion The disk cache will try to protect the original image data:
* If the original image is still image, it will be saved as png/jpeg file based on alpha information.
* If the original image is animated gif, apng or webp, it will be saved as original format.
* If the original image's scale is not 1, the scale value will be saved as extended data.
Although UIImage can be serialized with NSCoding protocol, but it's not a good idea:
Apple actually use UIImagePNGRepresentation() to encode all kind of image, it may
lose the original multi-frame data. The result is packed to plist file and cannot
view with photo viewer directly. If the image has no alpha channel, using JPEG
instead of PNG can save more disk size and encoding/decoding time.
*/
@interface YYImageCache : NSObject
#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================
/** The name of the cache. Default is nil. */
@property (nullable, copy) NSString *name;
/** The underlying memory cache. see `YYMemoryCache` for more information.*/
@property (strong, readonly) YYMemoryCache *memoryCache;
/** The underlying disk cache. see `YYDiskCache` for more information.*/
@property (strong, readonly) YYDiskCache *diskCache;
/**
Whether decode animated image when fetch image from disk cache. Default is YES.
@discussion When fetch image from disk cache, it will use 'YYImage' to decode
animated image such as WebP/APNG/GIF. Set to 'NO' to ignore animated image.
*/
@property BOOL allowAnimatedImage;
/**
Whether decode the image to memory bitmap. Default is YES.
@discussion If the value is YES, then the image will be decoded to memory bitmap
for better display performance, but may cost more memory.
*/
@property BOOL decodeForDisplay;
#pragma mark - Initializer
///=============================================================================
/// @name Initializer
///=============================================================================
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
Returns global shared image cache instance.
@return The singleton YYImageCache instance.
*/
+ (instancetype)sharedCache;
/**
The designated initializer. Multiple instances with the same path will make the
cache unstable.
@param path Full path of a directory in which the cache will write data.
Once initialized you should not read and write to this directory.
@result A new cache object, or nil if an error occurs.
*/
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================
/**
Sets the image with the specified key in the cache (both memory and disk).
This method returns immediately and executes the store operation in background.
@param image The image to be stored in the cache. If nil, this method has no effect.
@param key The key with which to associate the image. If nil, this method has no effect.
*/
- (void)setImage:(UIImage *)image forKey:(NSString *)key;
/**
Sets the image with the specified key in the cache.
This method returns immediately and executes the store operation in background.
@discussion If the `type` contain `YYImageCacheTypeMemory`, then the `image` will
be stored in the memory cache; `imageData` will be used instead if `image` is nil.
If the `type` contain `YYImageCacheTypeDisk`, then the `imageData` will
be stored in the disk cache; `image` will be used instead if `imageData` is nil.
@param image The image to be stored in the cache.
@param imageData The image data to be stored in the cache.
@param key The key with which to associate the image. If nil, this method has no effect.
@param type The cache type to store image.
*/
- (void)setImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(NSString *)key
withType:(YYImageCacheType)type;
/**
Removes the image of the specified key in the cache (both memory and disk).
This method returns immediately and executes the remove operation in background.
@param key The key identifying the image to be removed. If nil, this method has no effect.
*/
- (void)removeImageForKey:(NSString *)key;
/**
Removes the image of the specified key in the cache.
This method returns immediately and executes the remove operation in background.
@param key The key identifying the image to be removed. If nil, this method has no effect.
@param type The cache type to remove image.
*/
- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type;
/**
Returns a Boolean value that indicates whether a given key is in cache.
If the image is not in memory, this method may blocks the calling thread until
file read finished.
@param key A string identifying the image. If nil, just return NO.
@return Whether the image is in cache.
*/
- (BOOL)containsImageForKey:(NSString *)key;
/**
Returns a Boolean value that indicates whether a given key is in cache.
If the image is not in memory and the `type` contains `YYImageCacheTypeDisk`,
this method may blocks the calling thread until file read finished.
@param key A string identifying the image. If nil, just return NO.
@param type The cache type.
@return Whether the image is in cache.
*/
- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type;
/**
Returns the image associated with a given key.
If the image is not in memory, this method may blocks the calling thread until
file read finished.
@param key A string identifying the image. If nil, just return nil.
@return The image associated with key, or nil if no image is associated with key.
*/
- (nullable UIImage *)getImageForKey:(NSString *)key;
/**
Returns the image associated with a given key.
If the image is not in memory and the `type` contains `YYImageCacheTypeDisk`,
this method may blocks the calling thread until file read finished.
@param key A string identifying the image. If nil, just return nil.
@return The image associated with key, or nil if no image is associated with key.
*/
- (nullable UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type;
/**
Asynchronously get the image associated with a given key.
@param key A string identifying the image. If nil, just return nil.
@param type The cache type.
@param block A completion block which will be called on main thread.
*/
- (void)getImageForKey:(NSString *)key
withType:(YYImageCacheType)type
withBlock:(void(^)(UIImage * _Nullable image, YYImageCacheType type))block;
/**
Returns the image data associated with a given key.
This method may blocks the calling thread until file read finished.
@param key A string identifying the image. If nil, just return nil.
@return The image data associated with key, or nil if no image is associated with key.
*/
- (nullable NSData *)getImageDataForKey:(NSString *)key;
/**
Asynchronously get the image data associated with a given key.
@param key A string identifying the image. If nil, just return nil.
@param block A completion block which will be called on main thread.
*/
- (void)getImageDataForKey:(NSString *)key
withBlock:(void(^)(NSData * _Nullable imageData))block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,253 @@
//
// YYImageCache.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYImageCache.h"
#import "YYImage.h"
#import "UIImage+YYWebImage.h"
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYImage.h>
#else
#import "YYImage.h"
#endif
#if __has_include(<YYCache/YYCache.h>)
#import <YYCache/YYCache.h>
#else
#import "YYCache.h"
#endif
static inline dispatch_queue_t YYImageCacheIOQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
static inline dispatch_queue_t YYImageCacheDecodeQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}
@interface YYImageCache ()
- (NSUInteger)imageCost:(UIImage *)image;
- (UIImage *)imageFromData:(NSData *)data;
@end
@implementation YYImageCache
- (NSUInteger)imageCost:(UIImage *)image {
CGImageRef cgImage = image.CGImage;
if (!cgImage) return 1;
CGFloat height = CGImageGetHeight(cgImage);
size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
NSUInteger cost = bytesPerRow * height;
if (cost == 0) cost = 1;
return cost;
}
- (UIImage *)imageFromData:(NSData *)data {
NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data];
CGFloat scale = 0;
if (scaleData) {
scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue;
}
if (scale <= 0) scale = [UIScreen mainScreen].scale;
UIImage *image;
if (_allowAnimatedImage) {
image = [[YYImage alloc] initWithData:data scale:scale];
if (_decodeForDisplay) image = [image yy_imageByDecoded];
} else {
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image;
}
return image;
}
#pragma mark Public
+ (instancetype)sharedCache {
static YYImageCache *cache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask, YES) firstObject];
cachePath = [cachePath stringByAppendingPathComponent:@"com.ibireme.yykit"];
cachePath = [cachePath stringByAppendingPathComponent:@"images"];
cache = [[self alloc] initWithPath:cachePath];
});
return cache;
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"YYImageCache init error" reason:@"YYImageCache must be initialized with a path. Use 'initWithPath:' instead." userInfo:nil];
return [self initWithPath:@""];
}
- (instancetype)initWithPath:(NSString *)path {
YYMemoryCache *memoryCache = [YYMemoryCache new];
memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;
memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;
memoryCache.countLimit = NSUIntegerMax;
memoryCache.costLimit = NSUIntegerMax;
memoryCache.ageLimit = 12 * 60 * 60;
YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };
diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };
if (!memoryCache || !diskCache) return nil;
self = [super init];
_memoryCache = memoryCache;
_diskCache = diskCache;
_allowAnimatedImage = YES;
_decodeForDisplay = YES;
return self;
}
- (void)setImage:(UIImage *)image forKey:(NSString *)key {
[self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll];
}
- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type {
if (!key || (image == nil && imageData.length == 0)) return;
__weak typeof(self) _self = self;
if (type & YYImageCacheTypeMemory) { // add to memory cache
if (image) {
if (image.yy_isDecodedForDisplay) {
[_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];
} else {
dispatch_async(YYImageCacheDecodeQueue(), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self.memoryCache setObject:[image yy_imageByDecoded] forKey:key withCost:[self imageCost:image]];
});
}
} else if (imageData) {
dispatch_async(YYImageCacheDecodeQueue(), ^{
__strong typeof(_self) self = _self;
if (!self) return;
UIImage *newImage = [self imageFromData:imageData];
[self.memoryCache setObject:newImage forKey:key withCost:[self imageCost:newImage]];
});
}
}
if (type & YYImageCacheTypeDisk) { // add to disk cache
if (imageData) {
if (image) {
[YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData];
}
[_diskCache setObject:imageData forKey:key];
} else if (image) {
dispatch_async(YYImageCacheIOQueue(), ^{
__strong typeof(_self) self = _self;
if (!self) return;
NSData *data = [image yy_imageDataRepresentation];
[YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];
[self.diskCache setObject:data forKey:key];
});
}
}
}
- (void)removeImageForKey:(NSString *)key {
[self removeImageForKey:key withType:YYImageCacheTypeAll];
}
- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type {
if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key];
if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key];
}
- (BOOL)containsImageForKey:(NSString *)key {
return [self containsImageForKey:key withType:YYImageCacheTypeAll];
}
- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type {
if (type & YYImageCacheTypeMemory) {
if ([_memoryCache containsObjectForKey:key]) return YES;
}
if (type & YYImageCacheTypeDisk) {
if ([_diskCache containsObjectForKey:key]) return YES;
}
return NO;
}
- (UIImage *)getImageForKey:(NSString *)key {
return [self getImageForKey:key withType:YYImageCacheTypeAll];
}
- (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type {
if (!key) return nil;
if (type & YYImageCacheTypeMemory) {
UIImage *image = [_memoryCache objectForKey:key];
if (image) return image;
}
if (type & YYImageCacheTypeDisk) {
NSData *data = (id)[_diskCache objectForKey:key];
UIImage *image = [self imageFromData:data];
if (image && (type & YYImageCacheTypeMemory)) {
[_memoryCache setObject:image forKey:key withCost:[self imageCost:image]];
}
return image;
}
return nil;
}
- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block {
if (!block) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = nil;
if (type & YYImageCacheTypeMemory) {
image = [_memoryCache objectForKey:key];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
block(image, YYImageCacheTypeMemory);
});
return;
}
}
if (type & YYImageCacheTypeDisk) {
NSData *data = (id)[_diskCache objectForKey:key];
image = [self imageFromData:data];
if (image) {
[_memoryCache setObject:image forKey:key];
dispatch_async(dispatch_get_main_queue(), ^{
block(image, YYImageCacheTypeDisk);
});
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
block(nil, YYImageCacheTypeNone);
});
});
}
- (NSData *)getImageDataForKey:(NSString *)key {
return (id)[_diskCache objectForKey:key];
}
- (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block {
if (!block) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = (id)[_diskCache objectForKey:key];
dispatch_async(dispatch_get_main_queue(), ^{
block(data);
});
});
}
@end

View File

@@ -0,0 +1,65 @@
//
// YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/23.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
FOUNDATION_EXPORT double YYWebImageVersionNumber;
FOUNDATION_EXPORT const unsigned char YYWebImageVersionString[];
#import <YYWebImage/YYImageCache.h>
#import <YYWebImage/YYWebImageOperation.h>
#import <YYWebImage/YYWebImageManager.h>
#import <YYWebImage/UIImage+YYWebImage.h>
#import <YYWebImage/UIImageView+YYWebImage.h>
#import <YYWebImage/UIButton+YYWebImage.h>
#import <YYWebImage/CALayer+YYWebImage.h>
#import <YYWebImage/MKAnnotationView+YYWebImage.h>
#else
#import "YYImageCache.h"
#import "YYWebImageOperation.h"
#import "YYWebImageManager.h"
#import "UIImage+YYWebImage.h"
#import "UIImageView+YYWebImage.h"
#import "UIButton+YYWebImage.h"
#import "CALayer+YYWebImage.h"
#import "MKAnnotationView+YYWebImage.h"
#endif
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYImage.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYImage.h>
#import <YYWebImage/YYFrameImage.h>
#import <YYWebImage/YYSpriteSheetImage.h>
#import <YYWebImage/YYImageCoder.h>
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYImage.h"
#import "YYFrameImage.h"
#import "YYSpriteSheetImage.h"
#import "YYImageCoder.h"
#import "YYAnimatedImageView.h"
#endif
#if __has_include(<YYCache/YYCache.h>)
#import <YYCache/YYCache.h>
#elif __has_include(<YYWebImage/YYCache.h>)
#import <YYWebImage/YYCache.h>
#import <YYWebImage/YYMemoryCache.h>
#import <YYWebImage/YYDiskCache.h>
#import <YYWebImage/YYKVStorage.h>
#else
#import "YYCache.h"
#import "YYMemoryCache.h"
#import "YYDiskCache.h"
#import "YYKVStorage.h"
#endif

View File

@@ -0,0 +1,312 @@
//
// YYWebImageManager.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYImageCache.h>
#else
#import "YYImageCache.h"
#endif
@class YYWebImageOperation;
NS_ASSUME_NONNULL_BEGIN
/// The options to control image operation.
typedef NS_OPTIONS(NSUInteger, YYWebImageOptions) {
/// Show network activity on status bar when download image.
YYWebImageOptionShowNetworkActivity = 1 << 0,
/// Display progressive/interlaced/baseline image during download (same as web browser).
YYWebImageOptionProgressive = 1 << 1,
/// Display blurred progressive JPEG or interlaced PNG image during download.
/// This will ignore baseline image for better user experience.
YYWebImageOptionProgressiveBlur = 1 << 2,
/// Use NSURLCache instead of YYImageCache.
YYWebImageOptionUseNSURLCache = 1 << 3,
/// Allows untrusted SSL ceriticates.
YYWebImageOptionAllowInvalidSSLCertificates = 1 << 4,
/// Allows background task to download image when app is in background.
YYWebImageOptionAllowBackgroundTask = 1 << 5,
/// Handles cookies stored in NSHTTPCookieStore.
YYWebImageOptionHandleCookies = 1 << 6,
/// Load the image from remote and refresh the image cache.
YYWebImageOptionRefreshImageCache = 1 << 7,
/// Do not load image from/to disk cache.
YYWebImageOptionIgnoreDiskCache = 1 << 8,
/// Do not change the view's image before set a new URL to it.
YYWebImageOptionIgnorePlaceHolder = 1 << 9,
/// Ignore image decoding.
/// This may used for image downloading without display.
YYWebImageOptionIgnoreImageDecoding = 1 << 10,
/// Ignore multi-frame image decoding.
/// This will handle the GIF/APNG/WebP/ICO image as single frame image.
YYWebImageOptionIgnoreAnimatedImage = 1 << 11,
/// Set the image to view with a fade animation.
/// This will add a "fade" animation on image view's layer for better user experience.
YYWebImageOptionSetImageWithFadeAnimation = 1 << 12,
/// Do not set the image to the view when image fetch complete.
/// You may set the image manually.
YYWebImageOptionAvoidSetImage = 1 << 13,
/// This flag will add the URL to a blacklist (in memory) when the URL fail to be downloaded,
/// so the library won't keep trying.
YYWebImageOptionIgnoreFailedURL = 1 << 14,
};
/// Indicated where the image came from.
typedef NS_ENUM(NSUInteger, YYWebImageFromType) {
/// No value.
YYWebImageFromNone = 0,
/// Fetched from memory cache immediately.
/// If you called "setImageWithURL:..." and the image is already in memory,
/// then you will get this value at the same call.
YYWebImageFromMemoryCacheFast,
/// Fetched from memory cache.
YYWebImageFromMemoryCache,
/// Fetched from disk cache.
YYWebImageFromDiskCache,
/// Fetched from remote (web or file path).
YYWebImageFromRemote,
};
/// Indicated image fetch complete stage.
typedef NS_ENUM(NSInteger, YYWebImageStage) {
/// Incomplete, progressive image.
YYWebImageStageProgress = -1,
/// Cancelled.
YYWebImageStageCancelled = 0,
/// Finished (succeed or failed).
YYWebImageStageFinished = 1,
};
/**
The block invoked in remote image fetch progress.
@param receivedSize Current received size in bytes.
@param expectedSize Expected total size in bytes (-1 means unknown).
*/
typedef void(^YYWebImageProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
/**
The block invoked before remote image fetch finished to do additional image process.
@discussion This block will be invoked before `YYWebImageCompletionBlock` to give
you a chance to do additional image process (such as resize or crop). If there's
no need to transform the image, just return the `image` parameter.
@example You can clip the image, blur it and add rounded corners with these code:
^(UIImage *image, NSURL *url) {
// Maybe you need to create an @autoreleasepool to limit memory cost.
image = [image yy_imageByResizeToSize:CGSizeMake(100, 100) contentMode:UIViewContentModeScaleAspectFill];
image = [image yy_imageByBlurRadius:20 tintColor:nil tintMode:kCGBlendModeNormal saturation:1.2 maskImage:nil];
image = [image yy_imageByRoundCornerRadius:5];
return image;
}
@param image The image fetched from url.
@param url The image url (remote or local file path).
@return The transformed image.
*/
typedef UIImage * _Nullable (^YYWebImageTransformBlock)(UIImage *image, NSURL *url);
/**
The block invoked when image fetch finished or cancelled.
@param image The image.
@param url The image url (remote or local file path).
@param from Where the image came from.
@param error Error during image fetching.
@param finished If the operation is cancelled, this value is NO, otherwise YES.
*/
typedef void (^YYWebImageCompletionBlock)(UIImage * _Nullable image,
NSURL *url,
YYWebImageFromType from,
YYWebImageStage stage,
NSError * _Nullable error);
/**
A manager to create and manage web image operation.
*/
@interface YYWebImageManager : NSObject
/**
Returns global YYWebImageManager instance.
@return YYWebImageManager shared instance.
*/
+ (instancetype)sharedManager;
/**
Creates a manager with an image cache and operation queue.
@param cache Image cache used by manager (pass nil to avoid image cache).
@param queue The operation queue on which image operations are scheduled and run
(pass nil to make the new operation start immediately without queue).
@return A new manager.
*/
- (instancetype)initWithCache:(nullable YYImageCache *)cache
queue:(nullable NSOperationQueue *)queue NS_DESIGNATED_INITIALIZER;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
Creates and returns a new image operation, the operation will start immediately.
@param url The image url (remote or local file path).
@param options The options to control image operation.
@param progress Progress block which will be invoked on background thread (pass nil to avoid).
@param transform Transform block which will be invoked on background thread (pass nil to avoid).
@param completion Completion block which will be invoked on background thread (pass nil to avoid).
@return A new image operation.
*/
- (nullable YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
/**
The image cache used by image operation.
You can set it to nil to avoid image cache.
*/
@property (nullable, nonatomic, strong) YYImageCache *cache;
/**
The operation queue on which image operations are scheduled and run.
You can set it to nil to make the new operation start immediately without queue.
You can use this queue to control maximum number of concurrent operations, to obtain
the status of the current operations, or to cancel all operations in this manager.
*/
@property (nullable, nonatomic, strong) NSOperationQueue *queue;
/**
The shared transform block to process image. Default is nil.
When called `requestImageWithURL:options:progress:transform:completion` and
the `transform` is nil, this block will be used.
*/
@property (nullable, nonatomic, copy) YYWebImageTransformBlock sharedTransformBlock;
/**
The image request timeout interval in seconds. Default is 15.
*/
@property (nonatomic) NSTimeInterval timeout;
/**
The username used by NSURLCredential, default is nil.
*/
@property (nullable, nonatomic, copy) NSString *username;
/**
The password used by NSURLCredential, default is nil.
*/
@property (nullable, nonatomic, copy) NSString *password;
/**
The image HTTP request header. Default is "Accept:image/webp,image/\*;q=0.8".
*/
@property (nullable, nonatomic, copy) NSDictionary<NSString *, NSString *> *headers;
/**
A block which will be invoked for each image HTTP request to do additional
HTTP header process. Default is nil.
Use this block to add or remove HTTP header field for a specified URL.
*/
@property (nullable, nonatomic, copy) NSDictionary<NSString *, NSString *> *(^headersFilter)(NSURL *url, NSDictionary<NSString *, NSString *> * _Nullable header);
/**
A block which will be invoked for each image operation. Default is nil.
Use this block to provide a custom image cache key for a specified URL.
*/
@property (nullable, nonatomic, copy) NSString *(^cacheKeyFilter)(NSURL *url);
/**
Returns the HTTP headers for a specified URL.
@param url A specified URL.
@return HTTP headers.
*/
- (nullable NSDictionary<NSString *, NSString *> *)headersForURL:(NSURL *)url;
/**
Returns the cache key for a specified URL.
@param url A specified URL
@return Cache key used in YYImageCache.
*/
- (NSString *)cacheKeyForURL:(NSURL *)url;
/**
Increments the number of active network requests.
If this number was zero before incrementing, this will start animating the
status bar network activity indicator.
This method is thread safe.
This method has no effect in App Extension.
*/
+ (void)incrementNetworkActivityCount;
/**
Decrements the number of active network requests.
If this number becomes zero after decrementing, this will stop animating the
status bar network activity indicator.
This method is thread safe.
This method has no effect in App Extension.
*/
+ (void)decrementNetworkActivityCount;
/**
Get current number of active network requests.
This method is thread safe.
This method has no effect in App Extension.
*/
+ (NSInteger)currentNetworkActivityCount;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,184 @@
//
// YYWebImageManager.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYWebImageManager.h"
#import "YYImageCache.h"
#import "YYWebImageOperation.h"
#import "YYImageCoder.h"
#import <objc/runtime.h>
#define kNetworkIndicatorDelay (1/30.0)
/// Returns nil in App Extension.
static UIApplication *_YYSharedApplication() {
static BOOL isAppExtension = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIApplication");
if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)];
#pragma clang diagnostic pop
}
@interface _YYWebImageApplicationNetworkIndicatorInfo : NSObject
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation _YYWebImageApplicationNetworkIndicatorInfo
@end
@implementation YYWebImageManager
+ (instancetype)sharedManager {
static YYWebImageManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
YYImageCache *cache = [YYImageCache sharedCache];
NSOperationQueue *queue = [NSOperationQueue new];
if ([queue respondsToSelector:@selector(setQualityOfService:)]) {
queue.qualityOfService = NSQualityOfServiceBackground;
}
manager = [[self alloc] initWithCache:cache queue:queue];
});
return manager;
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"YYWebImageManager init error" reason:@"Use the designated initializer to init." userInfo:nil];
return [self initWithCache:nil queue:nil];
}
- (instancetype)initWithCache:(YYImageCache *)cache queue:(NSOperationQueue *)queue{
self = [super init];
if (!self) return nil;
_cache = cache;
_queue = queue;
_timeout = 15.0;
if (YYImageWebPAvailable()) {
_headers = @{ @"Accept" : @"image/webp,image/*;q=0.8" };
} else {
_headers = @{ @"Accept" : @"image/*;q=0.8" };
}
return self;
}
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = _timeout;
request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0;
request.allHTTPHeaderFields = [self headersForURL:url];
request.HTTPShouldUsePipelining = YES;
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request
options:options
cache:_cache
cacheKey:[self cacheKeyForURL:url]
progress:progress
transform:transform ? transform : _sharedTransformBlock
completion:completion];
if (_username && _password) {
operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession];
}
if (operation) {
NSOperationQueue *queue = _queue;
if (queue) {
[queue addOperation:operation];
} else {
[operation start];
}
}
return operation;
}
- (NSDictionary *)headersForURL:(NSURL *)url {
if (!url) return nil;
return _headersFilter ? _headersFilter(url, _headers) : _headers;
}
- (NSString *)cacheKeyForURL:(NSURL *)url {
if (!url) return nil;
return _cacheKeyFilter ? _cacheKeyFilter(url) : url.absoluteString;
}
#pragma mark Network Indicator
+ (_YYWebImageApplicationNetworkIndicatorInfo *)_networkIndicatorInfo {
return objc_getAssociatedObject(self, @selector(_networkIndicatorInfo));
}
+ (void)_setNetworkIndicatorInfo:(_YYWebImageApplicationNetworkIndicatorInfo *)info {
objc_setAssociatedObject(self, @selector(_networkIndicatorInfo), info, OBJC_ASSOCIATION_RETAIN);
}
+ (void)_delaySetActivity:(NSTimer *)timer {
UIApplication *app = _YYSharedApplication();
if (!app) return;
NSNumber *visiable = timer.userInfo;
if (app.networkActivityIndicatorVisible != visiable.boolValue) {
[app setNetworkActivityIndicatorVisible:visiable.boolValue];
}
[timer invalidate];
}
+ (void)_changeNetworkActivityCount:(NSInteger)delta {
if (!_YYSharedApplication()) return;
void (^block)() = ^{
_YYWebImageApplicationNetworkIndicatorInfo *info = [self _networkIndicatorInfo];
if (!info) {
info = [_YYWebImageApplicationNetworkIndicatorInfo new];
[self _setNetworkIndicatorInfo:info];
}
NSInteger count = info.count;
count += delta;
info.count = count;
[info.timer invalidate];
info.timer = [NSTimer timerWithTimeInterval:kNetworkIndicatorDelay target:self selector:@selector(_delaySetActivity:) userInfo:@(info.count > 0) repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:info.timer forMode:NSRunLoopCommonModes];
};
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
+ (void)incrementNetworkActivityCount {
[self _changeNetworkActivityCount:1];
}
+ (void)decrementNetworkActivityCount {
[self _changeNetworkActivityCount:-1];
}
+ (NSInteger)currentNetworkActivityCount {
_YYWebImageApplicationNetworkIndicatorInfo *info = [self _networkIndicatorInfo];
return info.count;
}
@end

View File

@@ -0,0 +1,97 @@
//
// YYWebImageOperation.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYWebImage/YYWebImage.h>)
#import <YYWebImage/YYImageCache.h>
#import <YYWebImage/YYWebImageManager.h>
#else
#import "YYImageCache.h"
#import "YYWebImageManager.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
The YYWebImageOperation class is an NSOperation subclass used to fetch image
from URL request.
@discussion It's an asynchronous operation. You typically execute it by adding
it to an operation queue, or calls 'start' to execute it manually. When the
operation is started, it will:
1. Get the image from the cache, if exist, return it with `completion` block.
2. Start an URL connection to fetch image from the request, invoke the `progress`
to notify request progress (and invoke `completion` block to return the
progressive image if enabled by progressive option).
3. Process the image by invoke the `transform` block.
4. Put the image to cache and return it with `completion` block.
*/
@interface YYWebImageOperation : NSOperation
@property (nonatomic, strong, readonly) NSURLRequest *request; ///< The image URL request.
@property (nullable, nonatomic, strong, readonly) NSURLResponse *response; ///< The response for request.
@property (nullable, nonatomic, strong, readonly) YYImageCache *cache; ///< The image cache.
@property (nonatomic, strong, readonly) NSString *cacheKey; ///< The image cache key.
@property (nonatomic, readonly) YYWebImageOptions options; ///< The operation's option.
/**
Whether the URL connection should consult the credential storage for authenticating
the connection. Default is YES.
@discussion This is the value that is returned in the `NSURLConnectionDelegate`
method `-connectionShouldUseCredentialStorage:`.
*/
@property (nonatomic) BOOL shouldUseCredentialStorage;
/**
The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
@discussion This will be overridden by any shared credentials that exist for the
username or password of the request URL, if present.
*/
@property (nullable, nonatomic, strong) NSURLCredential *credential;
/**
Creates and returns a new operation.
You should call `start` to execute this operation, or you can add the operation
to an operation queue.
@param request The Image request. This value should not be nil.
@param options A mask to specify options to use for this operation.
@param cache An image cache. Pass nil to avoid image cache.
@param cacheKey An image cache key. Pass nil to avoid image cache.
@param progress A block invoked in image fetch progress.
The block will be invoked in background thread. Pass nil to avoid it.
@param transform A block invoked before image fetch finished to do additional image process.
The block will be invoked in background thread. Pass nil to avoid it.
@param completion A block invoked when image fetch finished or cancelled.
The block will be invoked in background thread. Pass nil to avoid it.
@return The image request opeartion, or nil if an error occurs.
*/
- (instancetype)initWithRequest:(NSURLRequest *)request
options:(YYWebImageOptions)options
cache:(nullable YYImageCache *)cache
cacheKey:(nullable NSString *)cacheKey
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion NS_DESIGNATED_INITIALIZER;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,868 @@
//
// YYWebImageOperation.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 15/2/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYWebImageOperation.h"
#import "UIImage+YYWebImage.h"
#import <ImageIO/ImageIO.h>
#import <libkern/OSAtomic.h>
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYImage.h>
#else
#import "YYImage.h"
#endif
#define MIN_PROGRESSIVE_TIME_INTERVAL 0.2
#define MIN_PROGRESSIVE_BLUR_TIME_INTERVAL 0.4
/// Returns nil in App Extension.
static UIApplication *_YYSharedApplication() {
static BOOL isAppExtension = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIApplication");
if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)];
#pragma clang diagnostic pop
}
/// Returns YES if the right-bottom pixel is filled.
static BOOL YYCGImageLastPixelFilled(CGImageRef image) {
if (!image) return NO;
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
if (width == 0 || height == 0) return NO;
CGContextRef ctx = CGBitmapContextCreate(NULL, 1, 1, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault);
if (!ctx) return NO;
CGContextDrawImage(ctx, CGRectMake( -(int)width + 1, 0, width, height), image);
uint8_t *bytes = CGBitmapContextGetData(ctx);
BOOL isAlpha = bytes && bytes[0] == 0;
CFRelease(ctx);
return !isAlpha;
}
/// Returns JPEG SOS (Start Of Scan) Marker
static NSData *JPEGSOSMarker() {
// "Start Of Scan" Marker
static NSData *marker = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
uint8_t bytes[2] = {0xFF, 0xDA};
marker = [NSData dataWithBytes:bytes length:2];
});
return marker;
}
static NSMutableSet *URLBlacklist;
static dispatch_semaphore_t URLBlacklistLock;
static void URLBlacklistInit() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
URLBlacklist = [NSMutableSet new];
URLBlacklistLock = dispatch_semaphore_create(1);
});
}
static BOOL URLBlackListContains(NSURL *url) {
if (!url || url == (id)[NSNull null]) return NO;
URLBlacklistInit();
dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
BOOL contains = [URLBlacklist containsObject:url];
dispatch_semaphore_signal(URLBlacklistLock);
return contains;
}
static void URLInBlackListAdd(NSURL *url) {
if (!url || url == (id)[NSNull null]) return;
URLBlacklistInit();
dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
[URLBlacklist addObject:url];
dispatch_semaphore_signal(URLBlacklistLock);
}
/// A proxy used to hold a weak object.
@interface _YYWebImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation _YYWebImageWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[_YYWebImageWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
@interface YYWebImageOperation() <NSURLConnectionDelegate>
@property (readwrite, getter=isExecuting) BOOL executing;
@property (readwrite, getter=isFinished) BOOL finished;
@property (readwrite, getter=isCancelled) BOOL cancelled;
@property (readwrite, getter=isStarted) BOOL started;
@property (nonatomic, strong) NSRecursiveLock *lock;
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, assign) NSInteger expectedSize;
@property (nonatomic, assign) UIBackgroundTaskIdentifier taskID;
@property (nonatomic, assign) NSTimeInterval lastProgressiveDecodeTimestamp;
@property (nonatomic, strong) YYImageDecoder *progressiveDecoder;
@property (nonatomic, assign) BOOL progressiveIgnored;
@property (nonatomic, assign) BOOL progressiveDetected;
@property (nonatomic, assign) NSUInteger progressiveScanedLength;
@property (nonatomic, assign) NSUInteger progressiveDisplayCount;
@property (nonatomic, copy) YYWebImageProgressBlock progress;
@property (nonatomic, copy) YYWebImageTransformBlock transform;
@property (nonatomic, copy) YYWebImageCompletionBlock completion;
@end
@implementation YYWebImageOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
@synthesize cancelled = _cancelled;
/// Network thread entry point.
+ (void)_networkThreadMain:(id)object {
@autoreleasepool {
[[NSThread currentThread] setName:@"com.ibireme.webimage.request"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
/// Global image request network thread, used by NSURLConnection delegate.
+ (NSThread *)_networkThread {
static NSThread *thread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
thread.qualityOfService = NSQualityOfServiceBackground;
}
[thread start];
});
return thread;
}
/// Global image queue, used for image reading and decoding.
+ (dispatch_queue_t)_imageQueue {
#define MAX_QUEUE_COUNT 16
static int queueCount;
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
queues[i] = dispatch_queue_create("com.ibireme.image.decode", attr);
}
} else {
for (NSUInteger i = 0; i < queueCount; i++) {
queues[i] = dispatch_queue_create("com.ibireme.image.decode", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
}
}
});
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"YYWebImageOperation init error" reason:@"YYWebImageOperation must be initialized with a request. Use the designated initializer to init." userInfo:nil];
return [self initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] options:0 cache:nil cacheKey:nil progress:nil transform:nil completion:nil];
}
- (instancetype)initWithRequest:(NSURLRequest *)request
options:(YYWebImageOptions)options
cache:(YYImageCache *)cache
cacheKey:(NSString *)cacheKey
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
self = [super init];
if (!self) return nil;
if (!request) return nil;
_request = request;
_options = options;
_cache = cache;
_cacheKey = cacheKey ? cacheKey : request.URL.absoluteString;
_shouldUseCredentialStorage = YES;
_progress = progress;
_transform = transform;
_completion = completion;
_executing = NO;
_finished = NO;
_cancelled = NO;
_taskID = UIBackgroundTaskInvalid;
_lock = [NSRecursiveLock new];
return self;
}
- (void)dealloc {
[_lock lock];
if (_taskID != UIBackgroundTaskInvalid) {
[_YYSharedApplication() endBackgroundTask:_taskID];
_taskID = UIBackgroundTaskInvalid;
}
if ([self isExecuting]) {
self.cancelled = YES;
self.finished = YES;
if (_connection) {
[_connection cancel];
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager decrementNetworkActivityCount];
}
}
if (_completion) {
@autoreleasepool {
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
}
}
}
[_lock unlock];
}
- (void)_endBackgroundTask {
[_lock lock];
if (_taskID != UIBackgroundTaskInvalid) {
[_YYSharedApplication() endBackgroundTask:_taskID];
_taskID = UIBackgroundTaskInvalid;
}
[_lock unlock];
}
#pragma mark - Runs in operation thread
- (void)_finish {
self.executing = NO;
self.finished = YES;
[self _endBackgroundTask];
}
// runs on network thread
- (void)_startOperation {
if ([self isCancelled]) return;
@autoreleasepool {
// get image from cache
if (_cache &&
!(_options & YYWebImageOptionUseNSURLCache) &&
!(_options & YYWebImageOptionRefreshImageCache)) {
UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
if (image) {
[_lock lock];
if (![self isCancelled]) {
if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
}
[self _finish];
[_lock unlock];
return;
}
if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
__weak typeof(self) _self = self;
dispatch_async([self.class _imageQueue], ^{
__strong typeof(_self) self = _self;
if (!self || [self isCancelled]) return;
UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
if (image) {
[self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
[self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
} else {
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
});
return;
}
}
}
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
// runs on network thread
- (void)_startRequest:(id)object {
if ([self isCancelled]) return;
@autoreleasepool {
if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
[_lock lock];
if (![self isCancelled]) {
if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
[self _finish];
[_lock unlock];
return;
}
if (_request.URL.isFileURL) {
NSArray *keys = @[NSURLFileSizeKey];
NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil];
NSNumber *fileSize = attr[NSURLFileSizeKey];
_expectedSize = fileSize ? fileSize.unsignedIntegerValue : -1;
}
// request image from web
[_lock lock];
if (![self isCancelled]) {
_connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]];
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager incrementNetworkActivityCount];
}
}
[_lock unlock];
}
}
// runs on network thread, called from outer "cancel"
- (void)_cancelOperation {
@autoreleasepool {
if (_connection) {
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager decrementNetworkActivityCount];
}
}
[_connection cancel];
_connection = nil;
if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
[self _endBackgroundTask];
}
}
// runs on network thread
- (void)_didReceiveImageFromDiskCache:(UIImage *)image {
@autoreleasepool {
[_lock lock];
if (![self isCancelled]) {
if (image) {
if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil);
[self _finish];
} else {
[self _startRequest:nil];
}
}
[_lock unlock];
}
}
- (void)_didReceiveImageFromWeb:(UIImage *)image {
@autoreleasepool {
[_lock lock];
if (![self isCancelled]) {
if (_cache) {
if (image || (_options & YYWebImageOptionRefreshImageCache)) {
NSData *data = _data;
dispatch_async([YYWebImageOperation _imageQueue], ^{
[_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll];
});
}
}
_data = nil;
NSError *error = nil;
if (!image) {
error = [NSError errorWithDomain:@"com.ibireme.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
if (_options & YYWebImageOptionIgnoreFailedURL) {
if (URLBlackListContains(_request.URL)) {
error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
} else {
URLInBlackListAdd(_request.URL);
}
}
}
if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
[self _finish];
}
[_lock unlock];
}
}
#pragma mark - NSURLConnectionDelegate runs in operation thread
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {
return _shouldUseCredentialStorage;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
@autoreleasepool {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(_options & YYWebImageOptionAllowInvalidSSLCertificates) &&
[challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
} else {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
} else {
if ([challenge previousFailureCount] == 0) {
if (_credential) {
[[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
}
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
if (!cachedResponse) return cachedResponse;
if (_options & YYWebImageOptionUseNSURLCache) {
return cachedResponse;
} else {
// ignore NSURLCache
return nil;
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
@autoreleasepool {
NSError *error = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (id) response;
NSInteger statusCode = httpResponse.statusCode;
if (statusCode >= 400 || statusCode == 304) {
error = [NSError errorWithDomain:NSURLErrorDomain code:statusCode userInfo:nil];
}
}
if (error) {
[_connection cancel];
[self connection:_connection didFailWithError:error];
} else {
if (response.expectedContentLength) {
_expectedSize = (NSInteger)response.expectedContentLength;
if (_expectedSize < 0) _expectedSize = -1;
}
_data = [NSMutableData dataWithCapacity:_expectedSize > 0 ? _expectedSize : 0];
if (_progress) {
[_lock lock];
if (![self isCancelled]) _progress(0, _expectedSize);
[_lock unlock];
}
}
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
@autoreleasepool {
[_lock lock];
BOOL canceled = [self isCancelled];
[_lock unlock];
if (canceled) return;
if (data) [_data appendData:data];
if (_progress) {
[_lock lock];
if (![self isCancelled]) {
_progress(_data.length, _expectedSize);
}
[_lock unlock];
}
/*--------------------------- progressive ----------------------------*/
BOOL progressive = (_options & YYWebImageOptionProgressive) > 0;
BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0;
if (!_completion || !(progressive || progressiveBlur)) return;
if (data.length <= 16) return;
if (_expectedSize > 0 && data.length >= _expectedSize * 0.99) return;
if (_progressiveIgnored) return;
NSTimeInterval min = progressiveBlur ? MIN_PROGRESSIVE_BLUR_TIME_INTERVAL : MIN_PROGRESSIVE_TIME_INTERVAL;
NSTimeInterval now = CACurrentMediaTime();
if (now - _lastProgressiveDecodeTimestamp < min) return;
if (!_progressiveDecoder) {
_progressiveDecoder = [[YYImageDecoder alloc] initWithScale:[UIScreen mainScreen].scale];
}
[_progressiveDecoder updateData:_data final:NO];
if ([self isCancelled]) return;
if (_progressiveDecoder.type == YYImageTypeUnknown ||
_progressiveDecoder.type == YYImageTypeWebP ||
_progressiveDecoder.type == YYImageTypeOther) {
_progressiveDecoder = nil;
_progressiveIgnored = YES;
return;
}
if (progressiveBlur) { // only support progressive JPEG and interlaced PNG
if (_progressiveDecoder.type != YYImageTypeJPEG &&
_progressiveDecoder.type != YYImageTypePNG) {
_progressiveDecoder = nil;
_progressiveIgnored = YES;
return;
}
}
if (_progressiveDecoder.frameCount == 0) return;
if (!progressiveBlur) {
YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
if (frame.image) {
[_lock lock];
if (![self isCancelled]) {
_completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
_lastProgressiveDecodeTimestamp = now;
}
[_lock unlock];
}
return;
} else {
if (_progressiveDecoder.type == YYImageTypeJPEG) {
if (!_progressiveDetected) {
NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
NSDictionary *jpeg = dic[(id)kCGImagePropertyJFIFDictionary];
NSNumber *isProg = jpeg[(id)kCGImagePropertyJFIFIsProgressive];
if (!isProg.boolValue) {
_progressiveIgnored = YES;
_progressiveDecoder = nil;
return;
}
_progressiveDetected = YES;
}
NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4;
if (scanLength <= 2) return;
NSRange scanRange = NSMakeRange(_progressiveScanedLength, scanLength);
NSRange markerRange = [_data rangeOfData:JPEGSOSMarker() options:kNilOptions range:scanRange];
_progressiveScanedLength = _data.length;
if (markerRange.location == NSNotFound) return;
if ([self isCancelled]) return;
} else if (_progressiveDecoder.type == YYImageTypePNG) {
if (!_progressiveDetected) {
NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
NSDictionary *png = dic[(id)kCGImagePropertyPNGDictionary];
NSNumber *isProg = png[(id)kCGImagePropertyPNGInterlaceType];
if (!isProg.boolValue) {
_progressiveIgnored = YES;
_progressiveDecoder = nil;
return;
}
_progressiveDetected = YES;
}
}
YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
UIImage *image = frame.image;
if (!image) return;
if ([self isCancelled]) return;
if (!YYCGImageLastPixelFilled(image.CGImage)) return;
_progressiveDisplayCount++;
CGFloat radius = 32;
if (_expectedSize > 0) {
radius *= 1.0 / (3 * _data.length / (CGFloat)_expectedSize + 0.6) - 0.25;
} else {
radius /= (_progressiveDisplayCount);
}
image = [image yy_imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil];
if (image) {
[_lock lock];
if (![self isCancelled]) {
_completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
_lastProgressiveDecodeTimestamp = now;
}
[_lock unlock];
}
}
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
@autoreleasepool {
[_lock lock];
_connection = nil;
if (![self isCancelled]) {
__weak typeof(self) _self = self;
dispatch_async([self.class _imageQueue], ^{
__strong typeof(_self) self = _self;
if (!self) return;
BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0;
BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0;
UIImage *image;
BOOL hasAnimation = NO;
if (allowAnimation) {
image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale];
if (shouldDecode) image = [image yy_imageByDecoded];
if ([((YYImage *)image) animatedImageFrameCount] > 1) {
hasAnimation = YES;
}
} else {
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:self.data scale:[UIScreen mainScreen].scale];
image = [decoder frameAtIndex:0 decodeForDisplay:shouldDecode].image;
}
/*
If the image has animation, save the original image data to disk cache.
If the image is not PNG or JPEG, re-encode the image to PNG or JPEG for
better decoding performance.
*/
YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
switch (imageType) {
case YYImageTypeJPEG:
case YYImageTypeGIF:
case YYImageTypePNG:
case YYImageTypeWebP: { // save to disk cache
if (!hasAnimation) {
if (imageType == YYImageTypeGIF ||
imageType == YYImageTypeWebP) {
self.data = nil; // clear the data, re-encode for disk cache
}
}
} break;
default: {
self.data = nil; // clear the data, re-encode for disk cache
} break;
}
if ([self isCancelled]) return;
if (self.transform && image) {
UIImage *newImage = self.transform(image, self.request.URL);
if (newImage != image) {
self.data = nil;
}
image = newImage;
if ([self isCancelled]) return;
}
[self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
});
if (![self.request.URL isFileURL] && (self.options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager decrementNetworkActivityCount];
}
}
[_lock unlock];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
@autoreleasepool {
[_lock lock];
if (![self isCancelled]) {
if (_completion) {
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
_connection = nil;
_data = nil;
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager decrementNetworkActivityCount];
}
[self _finish];
if (_options & YYWebImageOptionIgnoreFailedURL) {
if (error.code != NSURLErrorNotConnectedToInternet &&
error.code != NSURLErrorCancelled &&
error.code != NSURLErrorTimedOut &&
error.code != NSURLErrorUserCancelledAuthentication) {
URLInBlackListAdd(_request.URL);
}
}
}
[_lock unlock];
}
}
#pragma mark - Override NSOperation
- (void)start {
@autoreleasepool {
[_lock lock];
self.started = YES;
if ([self isCancelled]) {
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
self.finished = YES;
} else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
if (!_request) {
self.finished = YES;
if (_completion) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
} else {
self.executing = YES;
[self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) {
__weak __typeof__ (self) _self = self;
if (_taskID == UIBackgroundTaskInvalid) {
_taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (_self) self = _self;
if (self) {
[self cancel];
self.finished = YES;
}
}];
}
}
}
}
[_lock unlock];
}
}
- (void)cancel {
[_lock lock];
if (![self isCancelled]) {
[super cancel];
self.cancelled = YES;
if ([self isExecuting]) {
self.executing = NO;
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
}
if (self.started) {
self.finished = YES;
}
}
[_lock unlock];
}
- (void)setExecuting:(BOOL)executing {
[_lock lock];
if (_executing != executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
[_lock unlock];
}
- (BOOL)isExecuting {
[_lock lock];
BOOL executing = _executing;
[_lock unlock];
return executing;
}
- (void)setFinished:(BOOL)finished {
[_lock lock];
if (_finished != finished) {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
[_lock unlock];
}
- (BOOL)isFinished {
[_lock lock];
BOOL finished = _finished;
[_lock unlock];
return finished;
}
- (void)setCancelled:(BOOL)cancelled {
[_lock lock];
if (_cancelled != cancelled) {
[self willChangeValueForKey:@"isCancelled"];
_cancelled = cancelled;
[self didChangeValueForKey:@"isCancelled"];
}
[_lock unlock];
}
- (BOOL)isCancelled {
[_lock lock];
BOOL cancelled = _cancelled;
[_lock unlock];
return cancelled;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isAsynchronous {
return YES;
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"isExecuting"] ||
[key isEqualToString:@"isFinished"] ||
[key isEqualToString:@"isCancelled"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (NSString *)description {
NSMutableString *string = [NSMutableString stringWithFormat:@"<%@: %p ",self.class, self];
[string appendFormat:@" executing:%@", [self isExecuting] ? @"YES" : @"NO"];
[string appendFormat:@" finished:%@", [self isFinished] ? @"YES" : @"NO"];
[string appendFormat:@" cancelled:%@", [self isCancelled] ? @"YES" : @"NO"];
[string appendString:@">"];
return string;
}
@end