1 Overview
SDWebImage
It’s basically standard for IOS projects. With a flexible and simple API, it provides a series of functions such as loading, parsing, processing, caching, cleaning and so on. Let’s focus on the business. But it doesn’t mean that you can use it. Through source code analysis and learning, let us know how to use it well. Learning and analyzing excellent source code can also imperceptibly provide us with a lot of ideas to solve our daily needs. Here is a picture to summarizeSDWebImage
All classes of:
Through the analysis of this picture, we canSDWebImage
There are three kinds of source code
-
Various categories:
-
UIButton(WebCache)
byUIButton
Class to load the image. For example, under normal circumstances, click the case, the image properties and background images. -
MKAnnotationView(WebCache)
byMKAnnotationView
Class to add various methods to load pictures. -
UIImageView(WebCache)
byUIImageView
Class to add a method to load a picture. -
UIImageView(HighlightedWebCache)
byUIImageView
Class to add a method to load a picture in the highlighted state. -
FLAnimatedImageView(WebCache)
byFLAnimatedImageView
Class to add dynamic loading methods, this classification needs to be introducedFLAnimatedImage
Framework.SDWebImage
It is recommended to use this framework to handle dynamic picture (GIF) loading. -
Uiimageview, uibutton and flanimatedimageview
sd_setImageWithURL
Wait for API to do image loading request. That’s the only thing we need to do. -
The above several uiview subclasses will call
UIView(WebCache)
classifiedsd_internalSetImageWithURL
Method to do the image loading request. Specifically, throughSDWebImageManager
Call. At the same time, operation cancellation and activity indicator addition and cancellation are realized.
-
-
Various tools:
-
NSData+ImageContentType
: get the image type according to the image data, such as GIF, PNG, etc. -
SDWebImageCompat
: enlarge or reduce the size of the picture according to the resolution of the screen. -
SDImageCacheConfig
: image cache policy record. For example, whether to decompress, whether to allow icloud, whether to allow memory cache, cache time, etc. The default cache time is one week. -
UIImage+MultiFormat
: getting the data corresponding to uiimage object or generating uiimage of specified format according to data is actually the conversion between uiimage and nsdata. -
UIImage+GIF
: judge whether a picture is GIF or not. The uiimage object of a GIF can be returned according to nsdata, and only the GIF generated by the first picture of the GIF can be returned. If you want to display multiple gifs, useFLAnimatedImageView
。 -
SDWebImageDecoder
: according to the situation of the picture, do the decompression of the picture. And decide how to deal with decompression according to the situation of the picture.
-
-
Core class:
-
SDImageCache
: responsible for the whole cache of sdwebimage, is aSingle column object
. Cache path processing, cache name processing, managing the creation and deletion of memory cache and disk cache, getting pictures according to the specified key, storing picture type processing, deleting cache according to the creation and modification date of cache. -
SDWebImageManager
: have aSDWebImageCache
andSDWebImageDownloader
Attributes are used for image caching and loading respectively. It provides a unified interface for uiview and its subclasses to load pictures. Manages the collection of loading operations. This class isA single column
. There is also the processing of various loading options. -
SDWebImageDownloader
: realize the specific processing of image loading, if the image exists in the cache, then from the cache. If the cache does not exist, create one directly.SDWebImageDownloaderOperation
Object to download the image. Manage the encapsulation, cache and cookie settings of nsurlrequest object request header. Loading options processing and other functions. Manage dependencies between operations. This class isA single column
. -
SDWebImageDownloaderOperation
: a custom parallel operation subclass. This class mainly implements the specific operation of image download, as well as the image decompression and operation lifecycle management after image download. -
UIView+WebCache
: all uibutton and uiimageview call back this classification method to complete the image loading process. At the same timeUIView+WebCacheOperation
Classification to manage the cancellation and recording of requests. All classifications of uiview and its subclasses use this classsd_intemalSetImageWithURL:
To achieve the loading of images. -
FLAnimatedImageView
: the data of dynamic picture is encapsulated by alanimatedimage object.FLAnimatedImageView
yesUIImageView
Subclass of. Through it, dynamic pictures can be loaded, displayed and managed. And more thanUIImageView
Process optimization has been done.
-
2 implementation process
SDWebImage
For us to achieve the image loading, data processing, image caching and other column work. Through the figure below, we can analyze his process:
From this graph, we find thatSDWebImage
The process of loading is to load data from the cache first. Moreover, the cache loading is priority from the memory cache, and then the disk loading. Finally, if there is no cache, it will be loaded from the network. At the same time, after the network successfully loads the image, it is stored in the local cache.
3 UIView+WebCache
analysis
Uiimageview, uibutton and flanimatedimageview will callUIView(WebCache)
classifiedsd_internalSetImageWithURL
Method to do the image loading request. Specifically, throughSDWebImageManager
Call. At the same time, operation cancellation and activity indicator addition and cancellation are realized. Let’s look at it firstsd_internalSetImageWithURL
The implementation of the method is as follows
/**
All uiview and its subclasses use this method to load images
@Param URL loaded URL
@Param placeholder
@Param options loading options
@param operationKey key
@param setImageBlock Block
@Param progressblock
@Param completedblock callback block
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
//Cancels all download operation objects corresponding to the current class
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/*
Associate the uiimageview's image loading operation with its own associated object to facilitate later operations such as canceling. The associated key is the class name corresponding to uiimageview
*/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//If the station map is set, the station map will be displayed first
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
//If the uiimageview object has a setting to add rotation chrysanthemum data, the rotation chrysanthemum data will be added when loading
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
/*
*Operation is a 'sdwebimagecombinedoperation' object. Get the image through this object
*/
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
//Stop Chrysanthemum
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
//If it is set not to display pictures automatically, call completedblock directly to let the caller handle the display of pictures
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
//Auto display picture
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
//If the delay display occupation bitmap is set, the occupation bitmap will be displayed if the picture loading fails
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
//Complete callback
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//Associate the operationkey with the operation object. It is convenient to cancel the operation according to the key.
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//Loading failure
dispatch_main_async_safe(^{
//Remove Chrysanthemum
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
Adding rotation chrysanthemum to uiview and its subclasses is realized by associating objects. It is realized by the following methods:
#Pragma mark realizes the addition of Chrysanthemum by associating objects
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
#Does pragma mark show rotation
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)sd_showActivityIndicatorView {
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
#The style of rotating Chrysanthemum
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)sd_getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
And then there is throughUIView+WebCacheOperation
Class to realize the image download of uiviewOperation
Association and cancellation of. Specific key values can be obtained fromsd_internalSetImageWithURL
Find the specific access method in this methodOperation
Association and cancellation of.
/**
Associate operation object with key object
@Param operation object
@param key key
*/
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
/**
Cancel all the operation objects corresponding to the current key that implement the sdwebimageoperation protocol
@Param key operation
*/
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
//Get all the keys corresponding to the current view
SDOperationsDictionary *operationDictionary = [self operationDictionary];
//Get the corresponding image to load operation
id operations = operationDictionary[key];
//Cancel all operations corresponding to the current view
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
4. Analysis of flanimatedimageview
SDWebImage
useFLAnimatedImage
Frame to handle dynamic images, which containFLAnimatedImage
andFLAnimatedImageView
Two thunder. The data of dynamic picture is transmitted throughALAnimatedImage
Object.FLAnimatedImageView
yesUIImageView
Subclass of. Through it, dynamic pictures can be loaded, displayed and managed. And more thanUIImageView
Process optimization has been done. Let’s take a lookFLAnimatedImageView.h
Interface defined in it:
/**
`Flanimatedimageview is a subclass of uiimageview. The 'start / stop / isanimating' method of 'uiimageview' is implemented. So we can directly use 'flanimatedimageview' instead of 'uiimageview'.
The 'cadisplaylink' object is used to process the display of the current picture frame and the next picture.
*/
@interface FLAnimatedImageView : UIImageView
/**
Encapsulation object of dynamic picture. First, by setting`[ UIImageView.image ]`Clear the existing dynamic image for nil. Setting the 'animatedimage' property will automatically set the new dynamic picture and start to display. In addition, the uiimage currently displayed will be saved in the 'currentframe'.
*/
@property (nonatomic, strong) FLAnimatedImage *animatedImage;
@property (nonatomic, copy) void(^loopCompletionBlock)(NSUInteger loopCountRemaining);
/**
Uiimage object corresponding to the current animation frame
*/
@property (nonatomic, strong, readonly) UIImage *currentFrame;
/**
The index of the current picture
*/
@property (nonatomic, assign, readonly) NSUInteger currentFrameIndex;
/**
Specifies the mode of the runloop in which the dynamic picture is executed. NSRunLoopCommonMode
*/
@property (nonatomic, copy) NSString *runLoopMode;
@end
We passedFLAnimatedImageView+WebCache
This is classifiedsd_setImageWithURL
To load dynamic pictures:
/**
Flanimatedimage + webcache classification uses this method to load dynamic images
@Param URL the URL of the image
@Param placeholder
@Param options loading options
@Param progressblock
@Param completedblock
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
//Get the type of the picture according to the type of nsdata
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
//If it is GIF, it will be processed
if (imageFormat == SDImageFormatGIF) {
//Set the dynamic picture to the animatedimage property of flanimatedimageview. This setter method is overridden
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
weakSelf.image = nil;
} else {
//If it is not a dynamic picture, it will be displayed normally
weakSelf.image = image;
weakSelf.animatedImage = nil;
}
}
progress:progressBlock
completed:completedBlock];
}
As can be seen from the above, after obtaining the image data. First of allSDImageFormat
Get the type of picture. If it is GIF type, the image data is encapsulated into aFLAnimatedImage
Object. Then set it to the animatedimage property. The setter method for this property is as follows:
/**
The setter method of animatedimage. Use this property setter method to set the data of flanimatedimageview. And start dynamic display
@Param animatedimage animatedimage property
*/
- (void)setAnimatedImage:(FLAnimatedImage *)animatedImage
{
if (![_animatedImage isEqual:animatedImage]) {
if (animatedImage) {
//Clear image data before uiimageview
super.image = nil;
super.highlighted = NO;
//Let's start with the intrinsiccontentsize, which is the built-in size of the control. For example, uilabel, uibutton and other controls have their own built-in sizes. The built-in size of a control is often determined by the content of the control itself. For example, if the text of a uilabel is very long, the built-in size of the uilabel will be very long. The built-in size of the control can be obtained by using the intrinsic contentsize property of uiview, or the intrinsic contentsize can be recalculated in the next UI planning event by using the invalideintensize method. If you create an original uiview object directly, its built-in size is obviously 0.
[self invalidateIntrinsicContentSize];
} else {
//Stop dynamic display of dynamic pictures
[self stopAnimating];
}
//Assignment
_animatedImage = animatedImage;
//Current dynamic picture data frame
self.currentFrame = animatedImage.posterImage;
//Current data frame index
self.currentFrameIndex = 0;
if (animatedImage.loopCount > 0) {
self.loopCountdown = animatedImage.loopCount;
} else {
self.loopCountdown = NSUIntegerMax;
}
self.accumulator = 0.0;
//Update the state of the object. This updates the value of the attribute shouldanimated.
[self updateShouldAnimate];
if (self.shouldAnimate) {
//Start dynamic display
[self startAnimating];
}
[self.layer setNeedsDisplay];
}
}
/**
Determine whether the current flanimatedimageview needs to display animation
*/
- (void)updateShouldAnimate
{
BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0;
self.shouldAnimate = self.animatedImage && isVisible;
}
5 CADisplayLink
What’s interesting is thatFLAnimatedImageView
Refresh the display of dynamic picture frame through cadisplaylink. Cadisplaylink is a timer that allows us to draw content to the screen at the same rate as the screen refresh rate. We create a new cadisplaylink object in the application, add it to a runloop, and provide it with a target and selector to call when the screen is refreshed.
Once cadisplaylink is registered with runloop in a specific mode, runloop will call the selector on the target bound to cadisplaylink whenever the screen needs to be refreshed. At this time, the target can read the timestamp of each call of cadisplaylink to prepare the data needed for the next frame display. For example, a video application uses a timestamp to calculate the video data to be displayed in the next frame. In the process of UI animation, we need to calculate the size of the UI object to be updated in the next frame of the animation through the timestamp. When adding runloop, we should choose a higher priority to ensure the smoothness of the animation. We can imagine that in the process of animation, a high priority task is added to the runloop. Then, the next call will be suspended, and the high priority task will be executed first, and then the call of cadisplaylink will be executed. As a result, the animation process will get stuck and the animation will not flow smoothly. The duration property provides the time between each frame, that is, the time between each refresh of the screen. We can use this time to calculate the value of the UI to be displayed in the next frame. But duration is only a rough time. If the CPU is busy with other calculations, it can’t guarantee to perform the screen drawing operation at the same frequency, which will skip the chance to call the callback method several times. The frameinterval attribute is a readable and writable nsinteger type value, which identifies how many frames to call the selector method once. The default value is 1, that is, every frame is called once. If every frame is called once, for IOS devices, the refresh rate is 60Hz, that is, 60 times per second. If the frameinterval is set to 2, two frames will be called once, that is, 30 times per second. We control the operation of cadisplaylink through the pause property. When we want to end a cadisplaylink, we should call – (void) invalidate to delete the previously bound target and selector from the runloop. In addition, cadisplaylink cannot be inherited.
//Call back every 1 / 60 seconds and use the displaydidrefresh method to do UI processing
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
//Add the DisplayLink to the commommode of the main thread
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
lastOriginal address.html),Demo address。