SDWebImage 源码解析

近期工作不算忙,因此简单看了一下这个OC常用的图片解析下载类

前言

首先,我们应该知道,SDWebImage底层实现有沙盒缓存机制,主要由三块组成
1、内存图片缓存
2、内存操作缓存
3、磁盘沙盒缓存


NSData+ImageContentType

根据图片二进制数据判断图片类型

+ (NSString *)sd_contentTypeForImageData:(NSData *)data{
    uint8_t c;
    /*
     *  常用的这几种格式图片的头部信息标识(十六进制)。
       1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG....。
       2.Jpg图片文件包括2字节:FF D8。
       3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
       4.Bmp图片文件包括2字节:42 4D。即为 BM。
     */
    //获取每一个字符
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return @"image/jpeg";
        case 0x89:
            return @"image/png";
        case 0x47:
            return @"image/gif";
        case 0x49:
        case 0x4D:
            return @"image/tiff";
        case 0x52:
            // R as RIFF for WEBP
            if ([data length] < 12) {
                return nil;
            }
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return @"image/webp";
            }
            return nil;
    }
    return nil;
}

SDImageCache

图片缓存类, 根据key进行图片缓存(内存/磁盘),根据key从缓存中
取出图片,以及删除图片,清除所有缓存等.


SDWebImageCompat

  1. 线程操作安全
同步操作
#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }
异步操作
#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
用法
 dispatch_main_async_safe(^{
        执行代码
    });

SDWebImageDecoder

alpha通道的解释:

1.  Alpha 没有透明度的意思,不代表透明度。opacity 和 transparency 才和透明度有关,前者是不透明度,后者是透明度。比如 css 中的「opacity: 0.5」就是设定元素有 50% 的不透明度。

2.    一个图像的每个像素都有 RGB 三个通道,后来 [Alvy Ray Smith](http://en.wikipedia.org/wiki/Alvy_Ray_Smith) 提出每个像素再增加一个 Alpha 通道,取值为0到1,用来储存这个像素是否对图片有「贡献」,0代表透明、1代表不透明。也就是说,「Alpha 通道」储存一个值,其外在表现是「透明度」,Alpha 和透明度没啥关系。

3.   为什么取名为 Alpha 通道,我觉得是因为这是除RGB以外「第一个通道」的意思,没有别的更深刻的含义。

4.   「Alpha 通道」是图片内在的一个属性,用 css 或者其他外部方法设定透明度,并没有改变图片的 Alpha 通道的值。
5. 阿尔法通道(α Channel或Alpha Channel)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。

CGImageRef ,这个结构用来创建像素位图,可以通过操作存储的像素位来编辑图片。
CGImageRef imageRef = image.CGImage;

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};
/**
 图片解码
 
 传统的UIImage进行解码都是在主线程上进行的,比如
 UIImage * image = [UIImage imageNamed:@"123.jpg"];
 self.imageView.image = image;
 在这个时候,图片其实并没有解码。而是,当图片实际需要显示到屏幕上的时候,CPU才会进行解码,绘制成纹理什么的,交给GPU渲染。这其实是很占用主线程CPU时间的,而众所周知,主线程的时间真的很宝贵

 @param image 原图片
 @return 解码后的图片
 */
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    // while downloading huge amount of images
    // autorelease the bitmap context
    // and all vars to help system to free memory
    // when there are memory warning.
    // on iOS7, do not forget to call
    // [[SDImageCache sharedImageCache] clearMemory];
    
    if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
        return nil;
    }
    
    @autoreleasepool{
        // 如果是gif图片的话,直接返回
        if (image.images != nil) {
            return image;
        }
        //CGImageRef ,这个结构用来创建像素位图,可以通过操作存储的像素位来编辑图片。
        CGImageRef imageRef = image.CGImage;
        //图片是否包含alpha通道
        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);
        if (anyAlpha) {
            //有alpha通道,直接返回
            return image;
        }
        //在IOS中,进过处理的图片数据会被保存在CGImage对象中,而8位图的调色板会被保存在CGImage的颜色空间CGColorSpace中
        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
        
        BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                      imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                      imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace) {
            //色彩空间:(Color Space)这是一个色彩范围的容器,类型必须是CGColorSpaceRef.对于这个参数,我们可以传入CGColorSpaceCreateDeviceRGB函数的返回值,它将给我们一个RGB色彩空间。
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
        }
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        NSUInteger bytesPerPixel = 4;
        NSUInteger bytesPerRow = bytesPerPixel * width;
        NSUInteger bitsPerComponent = 8;
        

        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        //创建bitmapcontext
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     bitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        //回执image到context中,强制解码
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        //根据上下文获取解码后的图片
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        if (unsupportedColorSpace) {
            CGColorSpaceRelease(colorspaceRef);
        }
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}

clang diagnostic的使用
使用格式大致如下:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相关命令"
    //需要操作的代码
#pragma clang diagnostic pop

clang diagnostic的使用


SDWebImageDownloader

 NSString *oneImageURL =
    @"http://wallpapers.wallbase.cc/rozne/wallpaper-573934.jpg";
    
    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:oneImageURL]
                                                          options:0
     
     progress:^(NSInteger receivedSize, NSInteger expectedSize)
     {
         //此处为下载进度
     }
     completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
     {
         //下载完成后进入这里执行
     }];

UIImage+GIF

用于解析gif图片

/**
 根据data解析出图片

 @param data sata
 @return 图片
 */
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
    if (!data) {
        return nil;
    }
    //获取图片数据
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    //获取source里面图片数量
    size_t count = CGImageSourceGetCount(source);

    UIImage *animatedImage;

    if (count <= 1) {//如果只有一张图,说明是静态图。
        animatedImage = [[UIImage alloc] initWithData:data];
    }
    else {
        NSMutableArray *images = [NSMutableArray array];

        NSTimeInterval duration = 0.0f;

        for (size_t i = 0; i < count; i++) {
            //取出每一张图片
            CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
            if (!image) {
                continue;
            }
            //计算图片显示时间
            duration += [self sd_frameDurationAtIndex:i source:source];

            [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
            //释放图片资源
            CGImageRelease(image);
        }

        if (!duration) {
            duration = (1.0f / 10.0f) * count;
        }
        //动态图图片资源,以及显示时间。
        animatedImage = [UIImage animatedImageWithImages:images duration:duration];
    }
    //释放
    CFRelease(source);

    return animatedImage;
}

UIImageView+WebCache

核心内容
用于解析图片展示在imageview上

/**
 下载图片,展示在UIImageView上

 @param url 图片url
 @param placeholder 占位图
 @param options 操作类型(枚举值)
 @param progressBlock 下载进度回调
 @param completedBlock 完成后的回调
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    //取消当前下载的图片
    [self sd_cancelCurrentImageLoad];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //设置显示占位图
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }
    
    if (url) {

        // 选择是否显示加载进度
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        //下载图片
        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            //移除进度条
            [wself removeActivityIndicator];
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                //SDWebImageAvoidAutoSetImage 手动设置图片(一般情况下是下载完成后自动设置)
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    wself.image = image;
                    
                    //刷新页面
                    [wself setNeedsLayout];
                }else {
                    //如果下载失败,并且选择显示占位图的情况下,显示占位图
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //进入下载图片
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        //如果url为nil
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

你可能感兴趣的:(SDWebImage 源码解析)