技术分享-持续学习

SDWebImage-源码学习(二)

2018-02-07  本文已影响8人  Sunxb

常用的枚举

在使用SDWebImage的时候, 会根据实际的需求对操作进行设置, 这就用到了SDWebImageManager类中的枚举SDWebImageOptions
, 首先我们就先来看一下这个枚举, 了解一下每个字段代表什么意思

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    /**
     * 默认情况下, 当一个url下载失败会被加入黑名单, 被加入黑名单的url就不会再被尝试下载了
     * 本标识作用: 禁用黑名单, 也就是失败的url会再次尝试请求
     */
    SDWebImageRetryFailed = 1 << 0,

    /**
     默认情况, 图片在交互的时候就开始下载,
     本标识作用:  降低图片下载的优先级, 让图片在UIScrollView减速的时候才下载
     */
    SDWebImageLowPriority = 1 << 1,

    /**
      禁用磁盘缓存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

    /**
      默认情况下, 图片是下载完事之后在直接显示出来
      本标识的作用是开启下载进度, 让图片的显示跟pc上的浏览器加载图片一样从上到下一边下载一边展示(差不多就是这个意思啦, 可能描述不太准确)
     */
    SDWebImageProgressiveDownload = 1 << 3,

    /**
      即使图片已经缓存, 也要根据HTTP缓存策略去网上重新加载图片,然后刷新缓存数据
      这个磁盘缓存是用NSURLCache处理而不是SDWebImage,这会导致一些性能下降
      这个选项是用来处理那些相同url但是图片改变了的情况
      如果缓存的图片刷新了, complete block会调用两次, 一次是原来的缓存图片,一次是新的图片
      在你不能保证url对应图片不变的时候使用此选项
     */
    SDWebImageRefreshCached = 1 << 4,

    /**
     在iOS4之后版本中, 如果app进入后台也可以继续下载. app进入后台,通过申请额外时间来完成下载. 如果后台任务超时, 则操作会被取消
     */
    SDWebImageContinueInBackground = 1 << 5,

    /**
     通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES, 处理缓存在NSHTTPCookieStore中的cookies
     */
    SDWebImageHandleCookies = 1 << 6,

    /**
    允许不受信任的SSL证书. 在测试环境下很有用, 生产环境慎用
     */
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    /**
      默认情况下, 图片在队列中按顺序下载, 本选项可以提高他们的优先级~ 
     */
    SDWebImageHighPriority = 1 << 8,
    
    /**
    默认情况下, 占位图会在图片开始加载时就先显示出来
    本选项会延迟占位图的显示, 直到图片加载完才加载占位图(那还不是不显示占位图了?)
     */
    SDWebImageDelayPlaceholder = 1 << 9,

    /**
    通常我们不会去调用animated images(估计就是多张图片循环显示或者GIF图片)的transformDownloadedImage的代理方法来处理图片。因为大部分transformation操作会对图片做无用处理。
     这个选项表示无论如何都要对图片做transform处理
     */
    SDWebImageTransformAnimatedImage = 1 << 10,
    
    /**
    默认情况下, 图片在下载完成后就会添加到imageView上. 但是有一些情况下, 我们希望在添加到imageView前对图片做一些操作,  所以可以在你想要手动设置下载的图片时使用此选项
     */
    SDWebImageAvoidAutoSetImage = 1 << 11
};

拓展 : NS_ENUM和NS_OPTIONS

之前我们定义枚举一般都是用NS_ENUM(enum太老 ~ 就不提了), 它和NS_OPTIONS有什么区别呢 ?
先看一下定义时的规范

//**要注意值的区间不要超过所使用类型的最大容纳范围。**
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
    UISwipeGestureRecognizerDirectionNone = 0,  //值为0
    UISwipeGestureRecognizerDirectionRight = 1 << 0,  //值为2的0次方
    UISwipeGestureRecognizerDirectionLeft = 1 << 1,  //值为2的1次方
    UISwipeGestureRecognizerDirectionUp = 1 << 2,  //值为2的2次方
    UISwipeGestureRecognizerDirectionDown = 1 << 3  //值为2的3次方
};
typedef NS_ENUM(NSInteger, NSWritingDirection) {
    NSWritingDirectionNatural = 0,  //值为0    
    NSWritingDirectionLeftToRight,  //值为1
    NSWritingDirectionRightToLeft  //值为2       
};

NS_ENUM定义通用枚举,NS_OPTIONS定义位移枚举, 位移枚举即是在你需要的地方可以同时存在多个枚举值如这样:

UISwipeGestureRecognizer *swipeGR = [[UISwipeGestureRecognizer alloc] init];
  swipeGR.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;

而NS_ENUM定义的枚举不能几个枚举项同时存在,只能选择其中一项,像这样:

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.baseWritingDirection = NSWritingDirectionNatural;

SDImageCache 缓存部分

缓存这部分的代码, 虽然看起来不少, 但是主要的流程还是比较清晰的, 代码也不难懂, 下面是主要的两个方法

/**
 把一张图片存入缓存的具体实现

 @param image 缓存的图片对象
 @param imageData 缓存的图片数据
 @param key 缓存对应的key
 @param toDisk 是否缓存到磁盘
 @param completionBlock 缓存完成回调
 */
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    //缓存到内存
    if (self.config.shouldCacheImagesInMemory) {
        //计算缓存数据的大小
        NSUInteger cost = SDCacheCostForImage(image);
        //加入缓存
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
    if (toDisk) {
        /*
         在一个线性队列中做磁盘缓存操作。
         */
        dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;
            if (!data && image) {
                //获取图片的类型GIF/PNG等
                SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
                //根据指定的SDImageFormat。把图片转换为对应的data数据
                data = [image sd_imageDataAsFormat:imageFormatFromData];
            }
            //把处理好了的数据存入磁盘
            [self storeImageDataToDisk:data forKey:key];
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}

/**
 把图片资源存入磁盘

 @param imageData 图片数据
 @param key key
 */
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    
    [self checkIfQueueIsIOQueue];
    //缓存目录是否已经初始化
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // get cache Path for image key
    //获取key对应的完整缓存路径
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //把数据存入路径
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
    
    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        //给文件添加到运行存储到iCloud属性
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

其中还涉及到一些图片类型和转化的问题, 具体可以看代码来了解

运行时

从源码中可以看到大量的使用运行时特性的地方, 如果加载图片时候的菊花loading, 就是通过运行时加上的

#pragma mark - Activity indicator
#pragma mark -
#if SD_UIKIT
- (UIActivityIndicatorView *)activityIndicator {
    return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}

- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
    objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
#endif

- (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];
}

#if SD_UIKIT
- (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];
}

这几个方法不仅仅包括使用runtime在类中动态添加变量, 还包括为动态添加的变量赋值,取值, 同时呢我还注意到了OBJC_ASSOCIATION_RETAIN这个宏, 然后就查了一下

/* Associative References */

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

objc_AssociationPolicy是一个枚举类型的数据结构定义了OBJC_ASSOCIATION_ASSIGNOBJC_ASSOCIATION_RETAIN_NONATOMICOBJC_ASSOCIATION_COPY_NONATOMICOBJC_ASSOCIATION_RETAINOBJC_ASSOCIATION_COPY这样五个关联对象特性,每个特性的描述如下:

上一篇下一篇

猜你喜欢

热点阅读