美文网首页
SDWebImage 源码解析

SDWebImage 源码解析

作者: sunny冲哥 | 来源:发表于2017-08-24 09:10 被阅读7次

    近期工作不算忙,因此简单看了一下这个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 <SDWebImageOperation> 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 源码解析

          本文链接:https://www.haomeiwen.com/subject/myysdxtx.html