深入理解SDWebImage(三)

按照SDWebImage中給到的主流程,進(jìn)入到SDWebImageManager轧简,SDWebImageManager總結(jié),按照資源的查找順序我們梳理一下SDImageCache和SDWebImageDownloader龄捡。

SDImageCache

在上一篇文章中圖片從緩存中進(jìn)行query拣帽,以及圖片下載完只有的store践瓷,都是進(jìn)入了SDImageCache中耕驰,并且SDImageCache是一個單例對象炸卑,我們總結(jié)一下類中的功能點:

(1)既鞠、內(nèi)部生成了分別處理內(nèi)存緩存/磁盤緩存的兩個屬性memoryCache/diskCache,在memoryCache內(nèi)部用于保存和讀取內(nèi)存中的圖片盖文,在diskCache中可以根據(jù)key來設(shè)置和讀取磁盤數(shù)據(jù)嘱蛋,刪除過期數(shù)據(jù)等

(2)、設(shè)置defaultCacheConfig,來設(shè)置磁盤緩存大腥髅簟(沒有設(shè)置)龄恋,磁盤緩存時間(7天),是否允許內(nèi)存緩存凶伙、是否進(jìn)入后臺自動清除過期磁盤數(shù)據(jù)等

(3)郭毕、接收UIApplicationWillTerminateNotification通知,程序被殺死時函荣,會調(diào)用deleteOldFilesWithCompletionBlock显押,通過diskCache來清理磁盤緩存。

  • 先獲取當(dāng)前磁盤目錄偏竟,遍歷當(dāng)前磁盤目錄URL煮落,查找磁盤數(shù)據(jù)中所有修改日期超過最大磁盤緩存時間的數(shù)據(jù),清除超時數(shù)據(jù)

  • 在遍歷磁盤數(shù)據(jù)的時候踊谋,更新未超時的本地磁盤占用大胁醭稹;如果最后超過了規(guī)定的磁盤占用大小殖蚕,對磁盤文件的修改時間進(jìn)行排序轿衔,然后for循環(huán)中移除最老的數(shù)據(jù),直到占用磁盤總大小小于設(shè)置的磁盤最大占用

(4)睦疫、UIApplicationDidEnterBackgroundNotification通知害驹,進(jìn)入后臺還是調(diào)用deleteOldFilesWithCompletionBlock,刪除數(shù)據(jù)緩存

(5)蛤育、在SDImageCache方法中宛官,定義了存儲、查詢瓦糕、刪除圖片的方法底洗。

首先我們看一下初始化SDImageCache方法,是一個單例咕娄,并且初始化各種屬性以及添加對app的監(jiān)聽

//單例方法
+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
//初始化主要用到了下面的方法
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nullable NSString *)directory
                                   config:(nullable SDImageCacheConfig *)config {
    if ((self = [super init])) {
        NSAssert(ns, @"Cache namespace should not be nil");
        
        // Create IO serial queue
        //在串行隊列異步線程中進(jìn)行數(shù)據(jù)的IO
        _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);
        
        if (!config) {
            config = SDImageCacheConfig.defaultCacheConfig;
        }
        _config = [config copy];
        
        // Init the memory cache
        //初始化內(nèi)存緩存
        NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol");
        _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
        
        // Init the disk cache
        //初始化磁盤緩存地址
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:ns];
        } else {
            NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
            _diskCachePath = path;
        }
        
        NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
        //初始化磁盤緩存工具類
        _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
        
        // Check and migrate disk cache directory if need
        [self migrateDiskCacheDirectory];

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidEnterBackground:)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
#if SD_MAC
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:NSApplicationWillTerminateNotification
                                                   object:nil];
#endif
    }

    return self;
}

圖片初始化完之后需要保存圖片到磁盤或者內(nèi)存中

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
          toMemory:(BOOL)toMemory
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //將image對象加入可以放入內(nèi)存中
    if (toMemory && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = image.sd_memoryCost;
        //將圖片image放入NSCache中的NSMapTable中亥揖,并記錄當(dāng)前總共占用多少內(nèi)存
        [self.memoryCache setObject:image forKey:key cost:cost];
    }
    
    if (toDisk) {
        //假如放入磁盤,這個過程中會有比較大的內(nèi)存消耗圣勒,需要放入自動釋放池
        //放入磁盤中的是二進(jìn)制data费变,并且包含指定的圖片格式信息
        //放入磁盤中
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
                    SDImageFormat format;
                    //如果包含alpha,那么PNG圣贸,否則JPEG
                    if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
                        format = SDImageFormatPNG;
                    } else {
                        format = SDImageFormatJPEG;
                    }
                    //假如沒有data挚歧,有image,那么按照指定的格式對圖片進(jìn)行壓縮
                    data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
                }
                //放入磁盤中
                [self _storeImageDataToDisk:data forKey:key];
            }
            
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}

//磁盤中
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    
    [self.diskCache setData:imageData forKey:key];
}

內(nèi)存或者磁盤分別調(diào)用了SDMemoryCache/SDDiskCache來對內(nèi)存和緩存圖片進(jìn)行處理旁趟,并由SDImageCacheConfig來設(shè)置緩存的策略昼激,包括是否需要內(nèi)存緩存庇绽、最大磁盤容量锡搜、最大磁盤緩存時長等

SDImageCacheConfig

static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week


/**
 *不用icloud中的圖片橙困,默認(rèn)是YES.
 */
@property (assign, nonatomic) BOOL shouldDisableiCloud;

/**
 //在緩存中緩存圖片,默認(rèn)是YES
 */
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

/*
 //設(shè)置該選項表示將圖片放入內(nèi)存中的同時也會保存一份到maptable中(弱引用)
 //當(dāng)內(nèi)存清理之后耕餐,由于某些images還被UIImageViews強引用凡傅,所以image并不會釋放
 //由于maptable的弱引用存在,內(nèi)存中的某些image就可以恢復(fù)
 //這樣可以保證某些圖片不會進(jìn)入磁盤查找/重新下載
 */
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;

/**
 //進(jìn)入后臺之后清理過期的磁盤中的圖片,默認(rèn)是YES
 */
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground;

/**
 //從磁盤讀取文件到內(nèi)存中的option
 //默認(rèn)是0
 */
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;

/**
 //寫入disk的option
 //默認(rèn)是NSDataWritingAtomic肠缔,也可以設(shè)置成NSDataWritingWithoutOverwriting夏跷,避免之前的數(shù)據(jù)被覆蓋
 */
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;

/**
 //磁盤中的超時時間,默認(rèn)一周
 */
@property (assign, nonatomic) NSTimeInterval maxDiskAge;

/**
 //最大的磁盤容量明未,默認(rèn)沒有限制
 */
@property (assign, nonatomic) NSUInteger maxDiskSize;
/**
 //最大內(nèi)存容量槽华,默認(rèn)沒有限制
 */
@property (assign, nonatomic) NSUInteger maxMemoryCost;

/**
 //最大內(nèi)存對象個數(shù)
 */
@property (assign, nonatomic) NSUInteger maxMemoryCount;

/*
 //磁盤緩存的過期類型
 */
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;

/**
 */
@property (strong, nonatomic, nullable) NSFileManager *fileManager;

/**
 //處理緩存的類SDMemoryCache
 */
@property (assign, nonatomic, nonnull) Class memoryCacheClass;

/**
 //SDDiskCache類
 */
@property (assign ,nonatomic, nonnull) Class diskCacheClass;

@end

SDMemoryCache

SDMemoryCache繼承自NSCache,內(nèi)部遵守SDMemoryCache協(xié)議趟妥,用來設(shè)置緩存猫态,獲取緩存數(shù)據(jù)萧朝。

(1)握侧、對內(nèi)存數(shù)據(jù)進(jìn)行增(set)、刪(remove)锅睛、查(objectForKey)疚膊,方法內(nèi)存實現(xiàn)父類的方法

(2)义辕、在實現(xiàn)上邊過程中,需要往本地NSMapTable中也進(jìn)行刪除過程寓盗,并進(jìn)行弱引用灌砖,當(dāng)內(nèi)存中沒有查詢到數(shù)據(jù),但是從MapTable中查詢到數(shù)據(jù)傀蚌,需要將數(shù)據(jù)放到父類NSCache中一份基显,實現(xiàn)了內(nèi)存恢復(fù)的作用

(3)、監(jiān)聽config中maxMemoryCost改變喳张,同步到NSCache中

- (void)commonInit {
    //設(shè)置默認(rèn)的config
    SDImageCacheConfig *config = self.config;
    self.totalCostLimit = config.maxMemoryCost;
    self.countLimit = config.maxMemoryCount;
    
    //對config中的maxMemoryCost续镇、maxMemoryCount添加監(jiān)聽
    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
    
#if SD_UIKIT
    //聲明NSPointerFunctionsWeakMemory的NSMapTable
    self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
    self.weakCacheLock = dispatch_semaphore_create(1);
    
    //監(jiān)聽系統(tǒng)的內(nèi)存警告
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveMemoryWarning:)
                                                 name:UIApplicationDidReceiveMemoryWarningNotification
                                               object:nil];
#endif
}

#if SD_UIKIT
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
    // 內(nèi)存警告
    [super removeAllObjects];
}

//依據(jù)setObject:forKey: 放入NSCache中
//并記錄內(nèi)存中增加了多少
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    //分裂的NSCache
    [super setObject:obj forKey:key cost:g];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key && obj) {
        //信號量,加鎖來保證內(nèi)存訪問安全
        SD_LOCK(self.weakCacheLock);
        //本地保存mapTable
        [self.weakCache setObject:obj forKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}

//查詢數(shù)據(jù)销部,如果內(nèi)存沒查到摸航,但本地MapTable中查找到了,放入內(nèi)存一份
- (id)objectForKey:(id)key {
    //分類中查找
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        //當(dāng)清除掉緩存中的數(shù)據(jù)舅桩,但是mapTable中有弱引用
        //同步到當(dāng)前NSCache中
        SD_LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
        if (obj) {
            //可以看到拿到的就是UIImage
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = [(UIImage *)obj sd_memoryCost];
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}

//刪除
- (void)removeObjectForKey:(id)key {
    [super removeObjectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key) {
        //mapTable刪除
        SD_LOCK(self.weakCacheLock);
        [self.weakCache removeObjectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if (context == SDMemoryCacheContext) {
        //監(jiān)聽內(nèi)存的設(shè)置酱虎,同步到NSCache中totalCostLimit、countLimit
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
            self.totalCostLimit = self.config.maxMemoryCost;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
            self.countLimit = self.config.maxMemoryCount;
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

SDImageCodersManager

圖片的磁盤緩存過程中用到了對圖片進(jìn)行類型判斷擂涛、encode读串、decode的過程,我們先看一下這一塊的代碼。

(1)恢暖、encode/decode過程用到了SDImageCodersManager,內(nèi)部將所有的遵循SDImageCoder協(xié)議的類都放入Manager數(shù)組中嫁佳,每次進(jìn)行decode或者encode處理瓤漏,都會進(jìn)行遍歷,對應(yīng)到指定的coder類中進(jìn)行處理浅侨。

(2)稳吮、SDImageCoder協(xié)議中聲明了是否可以進(jìn)行encode/decode撞蚕,以及將image傳遞進(jìn)來進(jìn)行encode/decode的操作谦秧;包含SDImageIOCoder - 支持PNG惶翻、JPEG、TIFF蜻拨;SDImageGIFCoder - 支持對GIF類型進(jìn)行編解碼缎讼;SDImageAPNGCoder - APNG格式的圖片進(jìn)行編解碼设凹。

(3)、NSData的圖片格式判斷是在NSData+ImageContentType分類中進(jìn)行的

{
    NSMutableArray<id<SDImageCoder>> *_imageCoders;
}
//單例進(jìn)行處理
+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

//初始化本地可以進(jìn)行code的類SDImageGIFCoder萍悴、SDImageAPNGCoder等都是單例
- (instancetype)init {
    if (self = [super init]) {
        // initialize with default coders
        _imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
        _codersLock = dispatch_semaphore_create(1);
    }
    return self;
}

//進(jìn)行壓縮處理
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    NSArray<id<SDImageCoder>> *coders = self.coders;
   //根據(jù)對應(yīng)的格式退腥,交給指定的encode類去進(jìn)行壓縮處理
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canEncodeToFormat:format]) {
            return [coder encodedDataWithImage:image format:format options:options];
        }
    }
    return nil;
}

拿GIF圖的decode來了解具體的壓縮解壓縮編碼 - SDImageGIFCoder
通過CGImageSourceRef繪制拿到UIImage,將UIImage轉(zhuǎn)化成每一張圖片的CGImageRef并進(jìn)行拼合

- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    CGFloat scale = 1;
    //外部設(shè)置的圖片縮放范圍
    NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
    if (scaleFactor != nil) {
        scale = MAX([scaleFactor doubleValue], 1);
    }
    
    //圖片上下文資源
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    if (!source) {
        return nil;
    }
    size_t count = CGImageSourceGetCount(source);
    UIImage *animatedImage;
    
    //是否只獲取第一幀
    BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
    if (decodeFirstFrame || count <= 1) {
        //假如是再榄,那么直接生成圖片
        animatedImage = [[UIImage alloc] initWithData:data scale:scale];
    } else {
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
        
        //根據(jù)獲得的資源中的count來遍歷圖片ref
        for (size_t i = 0; i < count; i++) {
            CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
            if (!imageRef) {
                continue;
            }
            
            float duration = [self sd_frameDurationAtIndex:i source:source];
            UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
            CGImageRelease(imageRef);
            
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
            [frames addObject:frame];
        }
        
        NSUInteger loopCount = [self sd_imageLoopCountWithSource:source];
        
        animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
        animatedImage.sd_imageLoopCount = loopCount;
    }
    animatedImage.sd_imageFormat = SDImageFormatGIF;
    CFRelease(source);
    
    return animatedImage;
}


- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    
    if (format != SDImageFormatGIF) {
        return nil;
    }
    
    NSMutableData *imageData = [NSMutableData data];
    //圖片的CFStringRef 為kUTTypeGIF
    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF];
    NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
    
    // Create an image destination. GIF does not support EXIF image orientation
    // The `CGImageDestinationCreateWithData` will log a warning when count is 0, use 1 instead.
    //圖片的imageUTType生成對應(yīng)的CGImageDestinationRef
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count ?: 1, NULL);
    if (!imageDestination) {
        // Handle failure.
        return nil;
    }
    
    //GIF的CFDictionaryRef
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    double compressionQuality = 1;
    if (options[SDImageCoderEncodeCompressionQuality]) {
        compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
    }
    properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
    
    BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
    if (encodeFirstFrame || frames.count == 0) {
        // for static single GIF images
        //獲取一個單一的圖片
        CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
    } else {
        // for animated GIF images
        //獲取分類中添加的sd_imageLoopCount數(shù)量
        NSUInteger loopCount = image.sd_imageLoopCount;
        //設(shè)置GIFLoopCount
        NSDictionary *gifProperties = @{(__bridge NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)};
        properties[(__bridge NSString *)kCGImagePropertyGIFDictionary] = gifProperties;
        //設(shè)置GIF圖片的屬性
        CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties);
        
        for (size_t i = 0; i < frames.count; i++) {
            SDImageFrame *frame = frames[i];
            float frameDuration = frame.duration;
            CGImageRef frameImageRef = frame.image.CGImage;
            NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
        }
    }
    // Finalize the destination.
    //結(jié)果
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        // Handle failure.
        imageData = nil;
    }
    
    CFRelease(imageDestination);
    
    return [imageData copy];
}

在圖片格式的獲取或者轉(zhuǎn)化成NSData時都用到了 - NSData+ImageContentType

//判斷data數(shù)據(jù)獲取image的格式
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    uint8_t c;
    //將我們的data轉(zhuǎn)化為data
    //獲取data中第一個字節(jié)狡刘,這里存放著圖片的類型
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) {
                //RIFF....WEBP
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
                //....ftypmif1 ....ftypmsf1
                if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
                    return SDImageFormatHEIF;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}
//NSData中對應(yīng)的圖片類型
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format {
    CFStringRef UTType;
    switch (format) {
        case SDImageFormatJPEG:
            UTType = kUTTypeJPEG;
            break;
        case SDImageFormatPNG:
            UTType = kUTTypePNG;
            break;
        case SDImageFormatGIF:
            UTType = kUTTypeGIF;
            break;
        case SDImageFormatTIFF:
            UTType = kUTTypeTIFF;
            break;
        case SDImageFormatWebP:
            UTType = kSDUTTypeWebP;
            break;
        case SDImageFormatHEIC:
            UTType = kSDUTTypeHEIC;
            break;
        case SDImageFormatHEIF:
            UTType = kSDUTTypeHEIF;
            break;
        default:
            // default is kUTTypePNG
            UTType = kUTTypePNG;
            break;
    }
    return UTType;
}

SDDiskCache

SDDiskCache中定義的方法和SDMemoryCache中的方法類似,都是設(shè)置緩存困鸥,獲取緩存嗅蔬。

(1)、對磁盤數(shù)據(jù)進(jìn)行增(set)疾就、刪(remove)澜术、查(objectForKey),如果有磁盤緩存了當(dāng)前數(shù)據(jù)猬腰,需要進(jìn)行數(shù)據(jù)更新鸟废,以保證后臺磁盤中的修改時間得到更新

(2)、放到磁盤中其實是對key(url+其他image額外的信息)進(jìn)行16位的MD5加密

(3)姑荷、刪除過期的磁盤數(shù)據(jù)盒延,首先按照時間進(jìn)行排序缩擂,然后刪除

增刪查的處理

- (BOOL)containsDataForKey:(NSString *)key {
    NSParameterAssert(key);
    //查詢文件路徑
    NSString *filePath = [self cachePathForKey:key];
    BOOL exists = [self.fileManager fileExistsAtPath:filePath];

    if (!exists) {
        exists = [self.fileManager fileExistsAtPath:filePath.stringByDeletingPathExtension];
    }
    
    return exists;
}

- (NSData *)dataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    // 可能沒有 path extension
    data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    return nil;
}

- (void)setData:(NSData *)data forKey:(NSString *)key {
    NSParameterAssert(data);
    NSParameterAssert(key);
    if (![self.fileManager fileExistsAtPath:self.diskCachePath]) {
        //假如文件不存在,那么生成一個文件夾添寺,
        [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    //拼接url字符串生成key
    NSString *cachePathForKey = [self cachePathForKey:key];
    // transform to NSUrl
    //文件地址
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    
    //不包含iiCloud
    if (self.config.shouldDisableiCloud) {
        // ignore iCloud backup resource value error
        
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

- (void)removeDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    [self.fileManager removeItemAtPath:filePath error:nil];
}

- (void)removeAllData {
    [self.fileManager removeItemAtPath:self.diskCachePath error:nil];
    [self.fileManager createDirectoryAtPath:self.diskCachePath
            withIntermediateDirectories:YES
                             attributes:nil
                                  error:NULL];
}

MD5進(jìn)行加密

static inline NSString * _Nonnull SDDiskCacheFileNameForKey(NSString * _Nullable key) {
    //KEY中除了url信息之外胯盯,還有我們的context中的key比如 url-SDImageRoundCornerTransformer(400.000000,1计露,10.000000博脑,#ff0000ff)
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    //16位的MD5值
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    //對key進(jìn)行MD5加密處理
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    // File system has file name length limit, we need to check if ext is too long, we don't add it to the filename
    if (ext.length > SD_MAX_FILE_EXTENSION_LENGTH) {
        ext = nil;
    }
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}

當(dāng)內(nèi)存警告、進(jìn)入后臺票罐、殺死進(jìn)程叉趣,根據(jù)緩存時間清理過期磁盤緩存

- (void)removeExpiredData {
    //disk路徑
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    
    //獲取Content的內(nèi)容修改日期key
    NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
    //默認(rèn)是修改日期 SDImageCacheConfigExpireTypeModificationDate
    switch (self.config.diskCacheExpireType) {
        case SDImageCacheConfigExpireTypeAccessDate:
            cacheContentDateKey = NSURLContentAccessDateKey;
            break;
            
        case SDImageCacheConfigExpireTypeModificationDate:
            cacheContentDateKey = NSURLContentModificationDateKey;
            break;
            
        default:
            break;
    }
    
    NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
    
    //獲取當(dāng)前diskCacheURL路徑下,所有的resourceKeys對應(yīng)的值胶坠,并且過濾隱藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];
    
    //默認(rèn)是一周君账,獲取到現(xiàn)在為止過期時間節(jié)點
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
    NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
    NSUInteger currentCacheSize = 0;
    
    //移除比過期時間節(jié)點還老的文件
    NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        //獲取url路徑下文件的修改日期
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        //記錄當(dāng)前磁盤中的文件大小
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }
    
    //開始移除指定路徑下的文件
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }
    
    //如果磁盤占用的大小超過了磁盤占用的上線maxDiskSize,又會對磁盤清理
    NSUInteger maxDiskSize = self.config.maxDiskSize;
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
     
        //按照修改順序?qū)ξ募M(jìn)行排序
        //oldest first
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        //刪除舊文件
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
}
//磁盤總大小
- (NSUInteger)totalSize {
    NSUInteger size = 0;
    //文件的地址數(shù)組
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
        //獲取當(dāng)前文件地址下的attributes屬性字典
        NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
        size += [attrs fileSize];
    }
    return size;
}
//總的文件個數(shù)
- (NSUInteger)totalCount {
    NSUInteger count = 0;
    //文件的地址數(shù)組
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    count = fileEnumerator.allObjects.count;
    return count;
}

SDWebImageDownloader

SDWebImageDownloader是一個單例
(1)、定義了下載隊列NSOperationQueue *downloadQueue沈善,以及根據(jù)下載的優(yōu)先級添加到NSOperation *lastAddedOperation

(2)、生成SDWebImageDownloaderOperation下載的實例對象椭蹄,放到下載隊列中闻牡,并實現(xiàn)相應(yīng)的NSURLSessionDataDelegate 代理方法,接收數(shù)據(jù)绳矩,并將數(shù)據(jù)傳輸?shù)较鄳?yīng)的operation類中處理

(3)罩润、請求header的添加接口、取消下載任務(wù)等

//下載枚舉值
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    /**
     //下載任務(wù)的優(yōu)先級
     */
    SDWebImageDownloaderLowPriority = 1 << 0,
    
    /**
     //瀏覽器樣式的圖片下載
     */
    SDWebImageDownloaderProgressiveLoad = 1 << 1,

    /**
     *默認(rèn)情況下翼馆,http請求阻止使用NSURLCache對象割以。如果設(shè)置了這個標(biāo)記,則NSURLCache會被http請求使用应媚。
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /**
     *如果image/imageData是從NSURLCache返回的严沥。則completion這個回調(diào)會返回nil
     */
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    
    /**
     *如果app進(jìn)入后臺模式,是否繼續(xù)下載中姜。這個是通過在后臺申請時間來完成這個操作消玄。如果指定的時間范圍內(nèi)沒有完成,則直接取消下載丢胚。
     */
    SDWebImageDownloaderContinueInBackground = 1 << 4,

    /**
     * 處理緩存在`NSHTTPCookieStore`對象里面的cookie翩瓜。通過設(shè)置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`來實現(xiàn)的。
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,

    /**
     * 允許非信任的SSL證書請求携龟。
     * 在測試的時候很有用兔跌。但是正式環(huán)境要小心使用。
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    /**
     * 默認(rèn)情況下峡蟋,圖片加載的順序是根據(jù)加入隊列的順序加載的坟桅。但是這個標(biāo)記會把任務(wù)加入隊列的最前面相满。
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
    
    /**
     * 默認(rèn)情況下,圖片會按照他的原始大小來解碼顯示桦卒。這個屬性會調(diào)整圖片的尺寸到合適的大小根據(jù)設(shè)備的內(nèi)存限制立美。
     * 如果`SDWebImageProgressiveDownload`標(biāo)記被設(shè)置了,則這個flag不起作用方灾。
     */
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
    
    /**
     * 加載圖片時建蹄,圖片的解壓縮操作再子線程處理轉(zhuǎn)化成bitmap再進(jìn)行繪制
     */
    SDWebImageDownloaderAvoidDecodeImage = 1 << 9,
    
    /**
     * 只截取第一幀
     */
    SDWebImageDownloaderDecodeFirstFrameOnly = 1 << 10,
    
    /**
     * 默認(rèn)情況下,對于“SDAnimatedImage”裕偿,我們在渲染時解碼動畫圖像幀洞慎,以減少內(nèi)存使用。但是嘿棘,當(dāng)大量imageViews共享動畫圖像時劲腿,可以指定將所有幀預(yù)加載到內(nèi)存中,以減少CPU使用鸟妙。這將在后臺隊列中觸發(fā)“preloadAllAnimatedImageFrames”(僅限磁盤緩存和下載)
     */
    SDWebImageDownloaderPreloadAllFrames = 1 << 11
};

FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadFinishNotification;

typedef SDImageLoaderProgressBlock SDWebImageDownloaderProgressBlock;
typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;

/**
 *異步下載
 */
@interface SDWebImageDownloader : NSObject

/**
 * 下載的配置焦人,包括最大并發(fā)、超時時間等
 */
@property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config;

/**
* `SDWebImageContextDownloadRequestModifier` context修改原始的下載request
 */
@property (nonatomic, strong, nullable) id<SDWebImageDownloaderRequestModifier> requestModifier;

/**
 * 定義NSURLSession的configuration
 */
@property (nonatomic, readonly, nonnull) NSURLSessionConfiguration *sessionConfiguration;

/**
 *下載隊列的suspension狀態(tài)
 */
@property (nonatomic, assign, getter=isSuspended) BOOL suspended;

/**
 * 當(dāng)前的下載任務(wù)數(shù)量
 */
@property (nonatomic, assign, readonly) NSUInteger currentDownloadCount;

/**
 *  初始化當(dāng)前的下載
 */
@property (nonatomic, class, readonly, nonnull) SDWebImageDownloader *sharedDownloader;

/**
* 依據(jù)SDWebImageDownloaderConfig初始化
 */
- (nonnull instancetype)initWithConfig:(nullable SDWebImageDownloaderConfig *)config NS_DESIGNATED_INITIALIZER;

/**
* 添加一個HTTP header
 */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;

/**
 * 返回一個 HTTP 頭部信息
 */
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;

/**
 *創(chuàng)建一個異步下載任務(wù)
 *在代理方法中會暴露給外界圖片下載完成或者報錯 
 *
 *
 * @param url            url
 * @param options        下載的options
 * @param context        之前講過的context字典重父,key可以有很多種業(yè)務(wù)場景花椭,value是我們之前規(guī)定好的幾種類型 
 * @param progressBlock 當(dāng)下載過程中會根據(jù)progress的刷新間隔來設(shè)置progressBlock回調(diào)間隔
 *                       @并不是在主隊列,需要放到主隊列刷新
 * @param completedBlock 下載完成的回調(diào)block
 *
 * @return  token (SDWebImageDownloadToken) 可以取消當(dāng)前的下載operation任務(wù)
 */
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

/**
 * 取消所有的下載任務(wù)
 */
- (void)cancelAllDownloads;

@end

.m中的主要方法實現(xiàn)

//設(shè)置header中的key value
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
    if (!field) {
        return;
    }
    SD_LOCK(self.HTTPHeadersLock);
    [self.HTTPHeaders setValue:value forKey:field];
    SD_UNLOCK(self.HTTPHeadersLock);
}
//回去當(dāng)前header中的value
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
    if (!field) {
        return nil;
    }
    SD_LOCK(self.HTTPHeadersLock);
    NSString *value = [self.HTTPHeaders objectForKey:field];
    SD_UNLOCK(self.HTTPHeadersLock);
    return value;
}
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    
    //添加信號量
    SD_LOCK(self.operationsLock);
    //獲取當(dāng)前的operation
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
    //如果發(fā)現(xiàn)operation為nil/完成/取消房午,但是并沒有從當(dāng)前的urlOperations中移除
    if (!operation || operation.isFinished || operation.isCancelled) {
        //創(chuàng)建一個當(dāng)前任務(wù)的operation請求
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        if (!operation) {
            SD_UNLOCK(self.operationsLock);
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
                completedBlock(nil, nil, error, YES);
            }
            return nil;
        }
        @weakify(self);
        operation.completionBlock = ^{
            @strongify(self);
            if (!self) {
                return;
            }
            SD_LOCK(self.operationsLock);
            //請求完成之后再當(dāng)前的URLOperations中移除
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self.operationsLock);
        };
        self.URLOperations[url] = operation;
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        //添加到隊列中
        [self.downloadQueue addOperation:operation];
    }
    else if (!operation.isExecuting) {
        //還沒有執(zhí)行矿辽,那么修改其優(yōu)先級
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        } else {
            operation.queuePriority = NSOperationQueuePriorityNormal;
        }
    }
    SD_UNLOCK(self.operationsLock);
    
    //包含進(jìn)度和complete的字典
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    
    //包裝token,包含url郭厌、request袋倔、downloader、downloadOperationCancelToken等
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    token.downloader = self;
    
    return token;
}

/**
 創(chuàng)建一個SDWebImageDownloaderOperation對象來做下載操作折柠,指定緩存策略宾娜、cookie策略、自定義請求頭域等

 @param url url
 @param options 下載options
 @param context 自定義的context
 @return 返回一個SDWebImageDownloaderOperation液走,管理一個下載請求
 */
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {
    NSTimeInterval timeoutInterval = self.config.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 15.0;
    }
    
    // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
    //請求的緩存策略
    //如果設(shè)置了SDWebImageDownloaderUseNSURLCache那么NSURLRequestUseProtocolCachePolicy
    //否則忽略request中的緩存 NSURLRequestReloadIgnoringLocalCacheData
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    mutableRequest.HTTPShouldUsePipelining = YES;
    //請求頭的設(shè)置
    SD_LOCK(self.HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(self.HTTPHeadersLock);
    
    //獲取外部提供的requestModifier
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
        requestModifier = self.requestModifier;
    }
    
    NSURLRequest *request;
    if (requestModifier) {
        //返回一個外部設(shè)置好的request信息
        NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
        // If modified request is nil, early return
        if (!modifiedRequest) {
            return nil;
        } else {
            request = [modifiedRequest copy];
        }
    } else {
        request = [mutableRequest copy];
    }
    
    //獲取SDWebImageDownloaderOperation類對象
    Class operationClass = self.config.operationClass;
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
        // Custom operation class
    } else {
        operationClass = [SDWebImageDownloaderOperation class];
    }
    //獲取一個operation對象
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    
    if ([operation respondsToSelector:@selector(setCredential:)]) {
        //配置驗證信息
        if (self.config.urlCredential) {
            //ssl驗證
            operation.credential = self.config.urlCredential;
        } else if (self.config.username && self.config.password) {
            //用戶名密碼驗證
            operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
        }
    }
        
    if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
        //progress的時間間隔
        operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
    }
    
    //operation的優(yōu)先級
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    
    if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
        //如果是LIFO這種模式碳默,則讓前面的operation依賴于最新添加的operation
        [self.lastAddedOperation addDependency:operation];
        self.lastAddedOperation = operation;
    }
    
    return operation;
}
//取消某一個請求
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
    NSURL *url = token.url;
    if (!url) {
        return;
    }
    SD_LOCK(self.operationsLock);
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    if (operation) {
        //NSOperation取消下載任務(wù)
        BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
        if (canceled) {
            [self.URLOperations removeObjectForKey:url];
        }
    }
    SD_UNLOCK(self.operationsLock);
}

在內(nèi)部還有一些網(wǎng)絡(luò)請求的代理方法,這些代理方法在接收數(shù)據(jù)之后缘眶,在當(dāng)前的operations中進(jìn)行查找當(dāng)前的下載任務(wù)嘱根,查找到之后放到當(dāng)前SDWebImageDownloaderOperation中進(jìn)行處理這些請求到的數(shù)據(jù),代碼就不顯示了,大家可以自己看一下比較簡單

SDWebImageDownloaderConfig

在配置類中設(shè)置相應(yīng)的網(wǎng)絡(luò)請求配置,默認(rèn)生成的類是一個單例巷懈,包含超時時間该抒、最大并發(fā)數(shù)、ssl證書等網(wǎng)絡(luò)請求的配置項顶燕,都保存到當(dāng)前類中凑保。

/**
 //單例冈爹,默認(rèn)下載配置最多6個并發(fā),15s超時
 */
@property (nonatomic, class, readonly, nonnull) SDWebImageDownloaderConfig *defaultDownloaderConfig;

/**
 //下載的最大并發(fā)數(shù)6個
 */
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;

/**
 //下載的超時時間 默認(rèn)15s
 */
@property (nonatomic, assign) NSTimeInterval downloadTimeout;

/**
 //最小界面progress的刷新間隔
 //當(dāng)設(shè)置progressive decoding feature欧引,我們需要展示進(jìn)度频伤,該屬性表示刷新間隔
 */
@property (nonatomic, assign) double minimumProgressInterval;

/**
 //NSURLSession的默認(rèn)配置,當(dāng)下載開始之后不支持動態(tài)修改
 */
@property (nonatomic, strong, nullable) NSURLSessionConfiguration *sessionConfiguration;

/**
 //SDWebImageDownloaderOperation下載類
 */
@property (nonatomic, assign, nullable) Class operationClass;

/**
 //圖片的下載執(zhí)行順序芝此,默認(rèn)是先進(jìn)先出憋肖,SDWebImageDownloaderFIFOExecutionOrder
 */
@property (nonatomic, assign) SDWebImageDownloaderExecutionOrder executionOrder;

/**
 //默認(rèn)是nil,為圖片加載request設(shè)置一個ssl證書對象
 */
@property (nonatomic, copy, nullable) NSURLCredential *urlCredential;

/**
 //默認(rèn)是nil婚苹,為http的Basic 認(rèn)證設(shè)置用戶名
 */
@property (nonatomic, copy, nullable) NSString *username;

/**
 //默認(rèn)是nil岸更,為http的Basic 認(rèn)證設(shè)置密碼
 */
@property (nonatomic, copy, nullable) NSString *password;

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation繼承自NSOperation

(1)、并行執(zhí)行NSOperation,管理executing膊升,finished等各種屬性的處理怎炊,手動觸發(fā)KVO。

(2)廓译、重寫start评肆、cancel方法用來觸發(fā)網(wǎng)絡(luò)請求以及取消請求

(3)、在NSURLSessionDataDelegate等代理方法中對數(shù)據(jù)進(jìn)行加載责循,并驗證當(dāng)前的https請求是否有效糟港,獲取數(shù)據(jù)之后的數(shù)據(jù)拼接處理并回調(diào)block,發(fā)送請求數(shù)據(jù)狀態(tài)的通知

- (void)setFinished:(BOOL)finished {
    //手動觸發(fā)finish
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    //手動觸發(fā)isExecuting
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

/并行處理的Operation需要重寫這個方法院仿,在這個方法里具體的處理
- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

#if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        //如果進(jìn)入后臺,
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak typeof(self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                //后臺執(zhí)行結(jié)束之后,做取消
                [wself cancel];
            }];
        }
#endif
        //如果在downloader中的session被設(shè)置為nil速和,需要我們生成一個ownSession
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        
        //獲得當(dāng)前的圖片緩存歹垫,為以后的數(shù)據(jù)核驗是否需要緩存更新作準(zhǔn)備
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check
            NSURLCache *URLCache = session.configuration.URLCache;
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            NSCachedURLResponse *cachedResponse;
            // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }

    //設(shè)置當(dāng)前的任務(wù)優(yōu)先級
    if (self.dataTask) {
        if (self.options & SDWebImageDownloaderHighPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityHigh;
        } else if (self.options & SDWebImageDownloaderLowPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityLow;
        }
        //執(zhí)行當(dāng)前的任務(wù)
        [self.dataTask resume];
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        __block typeof(self) strongSelf = self;
        //開始下載
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
        });
    } else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        [self done];
    }
}
//取消任務(wù)
- (void)cancel {
    @synchronized (self) {
        [self cancelInternal];
    }
}

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];

    if (self.dataTask) {
        [self.dataTask cancel];
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
        });

        // As we cancelled the task, its callback won't be called and thus won't
        // maintain the isFinished and isExecuting flags.
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }

    [self reset];
}

- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

- (void)reset {
    SD_LOCK(self.callbacksLock);
    [self.callbackBlocks removeAllObjects];
    SD_UNLOCK(self.callbacksLock);
    
    @synchronized (self) {
        self.dataTask = nil;
        
        if (self.ownedSession) {
            [self.ownedSession invalidateAndCancel];
            self.ownedSession = nil;
        }
        
#if SD_UIKIT
        if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
            // If backgroundTaskId != UIBackgroundTaskInvalid, sharedApplication is always exist
            //結(jié)束app的后臺任務(wù)
            UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
            [app endBackgroundTask:self.backgroundTaskId];
            self.backgroundTaskId = UIBackgroundTaskInvalid;
        }
#endif
    }
}

看一下代理方法中的執(zhí)行邏輯,首先判斷當(dāng)前的https證書是否通過驗證,以及接受到請求之后是否允許當(dāng)前的網(wǎng)絡(luò)請求颠放,如果允許那么開始接收數(shù)據(jù)(頻繁調(diào)用)排惨,最終數(shù)據(jù)請求結(jié)束

#pragma mark NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    //總長度
    NSInteger expected = (NSInteger)response.expectedContentLength;
    expected = expected > 0 ? expected : 0;
    self.expectedSize = expected;
    self.response = response;
    //狀態(tài)碼
    NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
    BOOL valid = statusCode >= 200 && statusCode < 400;
    if (!valid) {
        self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
    }
    //'304 Not Modified' is an exceptional one
    //URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check
    //如果返回304,但是沒有緩存數(shù)據(jù)碰凶,返回錯誤
    if (statusCode == 304 && !self.cachedData) {
        valid = NO;
        self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
    }
    
    if (valid) {
        //如果是有效的數(shù)據(jù)暮芭,那么返回當(dāng)前進(jìn)度
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
    } else {
        // Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
        disposition = NSURLSessionResponseCancel;
    }
    //接收到數(shù)據(jù)的通知
    __block typeof(self) strongSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf];
    });
    
    //允許接收數(shù)據(jù)
    if (completionHandler) {
        completionHandler(disposition);
    }
}
//接收到下載的網(wǎng)絡(luò)數(shù)據(jù)之后的處理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    if (!self.imageData) {
        self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
    }
    //收集圖片nsdata
    [self.imageData appendData:data];
    
    self.receivedSize = self.imageData.length;
    if (self.expectedSize == 0) {
        //假如不知道期望圖片的size,立刻返回
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
        }
        return;
    }
    
    // Get the finish status
    //接收的數(shù)據(jù)大于等于期望的大小欲低,說明獲取圖片成功
    BOOL finished = (self.receivedSize >= self.expectedSize);
    // Get the current progress
    //獲取當(dāng)前的進(jìn)度
    double currentProgress = (double)self.receivedSize / (double)self.expectedSize;
    double previousProgress = self.previousProgress;
    double progressInterval = currentProgress - previousProgress;
    // Check if we need callback progress
    //假如沒有下載完成&&下載進(jìn)度小于最小進(jìn)度
    if (!finished && (progressInterval < self.minimumProgressInterval)) {
        return;
    }
    //以前的進(jìn)度就等于當(dāng)前進(jìn)度
    self.previousProgress = currentProgress;
   
    //并且當(dāng)前是按照SDWebImageDownloaderProgressiveLoad來展示圖片
    if (self.options & SDWebImageDownloaderProgressiveLoad) {
        // Get the image data
        NSData *imageData = [self.imageData copy];
        
        // progressive decode the image in coder queue
        //進(jìn)行異步解壓縮操作
        dispatch_async(self.coderQueue, ^{
            //解壓縮過程中可能會產(chǎn)生很多的中間變量辕宏,消耗內(nèi)存所以放到自動釋放池,runloop到before waiting清理
            @autoreleasepool {
                //解壓縮
                UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
                if (image) {
                    // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
                    
                    [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
                }
            }
        });
    }
    
    for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
        progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    
    NSCachedURLResponse *cachedResponse = proposedResponse;
    
    if (!(self.options & SDWebImageDownloaderUseNSURLCache)) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    //根據(jù)request選項砾莱,決定是否緩存NSCachedURLResponse
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

#pragma mark NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    @synchronized(self) {
        self.dataTask = nil;
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:strongSelf];
            }
        });
    }
    
    // make sure to call `[self done]` to mark operation as finished
    //標(biāo)記完成了
    if (error) {
        // custom error instead of URLSession error
        if (self.responseError) {
            error = self.responseError;
        }
        [self callCompletionBlocksWithError:error];
        [self done];
        
    } else {
        if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
            //下載完成瑞筐,將本地的imageData置為nil,防止下次進(jìn)入數(shù)據(jù)出錯
            NSData *imageData = [self.imageData copy];
            self.imageData = nil;
            if (imageData) {
                /**  if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
                 *  then we should check if the cached data is equal to image data
                 */
                //假如只用緩存的數(shù)據(jù)腊瑟,并且緩存數(shù)據(jù)等于當(dāng)前imagedata那么返回error
                //這一步是判斷是否進(jìn)行緩存刷新的關(guān)鍵
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
                    self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
                    // call completion block with not modified error
                    [self callCompletionBlocksWithError:self.responseError];
                    [self done];
                } else {
                    // decode the image in coder queue
                    //在coderQueue中處理圖片的異步
                    dispatch_async(self.coderQueue, ^{
                        @autoreleasepool {
                            UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
                            CGSize imageSize = image.size;
                            if (imageSize.width == 0 || imageSize.height == 0) {
                                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                            } else {
                                [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
                            }
                            [self done];
                        }
                    });
                }
            } else {
                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                [self done];
            }
        } else {
            [self done];
        }
    }
}
//驗證https證書
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    //使用可以信任證書頒發(fā)機構(gòu)的證書
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        //如果設(shè)置了SDWebImageDownloaderAllowInvalidSSLCertificates聚假,則信任任何證書块蚌,不需要驗證
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        //自己生成的證書
        if (challenge.previousFailureCount == 0) {
            if (self.credential) {
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    //返回驗證結(jié)果
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

歡迎關(guān)注我的公眾號,專注iOS開發(fā)膘格、大前端開發(fā)峭范、跨平臺技術(shù)分享。


iOS開發(fā)之家
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘪贱,一起剝皮案震驚了整個濱河市纱控,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌政敢,老刑警劉巖其徙,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喷户,居然都是意外死亡唾那,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門褪尝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闹获,“玉大人,你說我怎么就攤上這事河哑”芊蹋” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵璃谨,是天一觀的道長沙庐。 經(jīng)常有香客問我,道長佳吞,這世上最難降的妖魔是什么拱雏? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮底扳,結(jié)果婚禮上幔嫂,老公的妹妹穿的比我還像新娘见咒。我一直安慰自己鲁捏,他們只是感情好朋凉,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阱冶,像睡著了一般刁憋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熙揍,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天职祷,我揣著相機與錄音,去河邊找鬼。 笑死有梆,一個胖子當(dāng)著我的面吹牛是尖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泥耀,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼饺汹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痰催?” 一聲冷哼從身側(cè)響起兜辞,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夸溶,沒想到半個月后逸吵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡缝裁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年扫皱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捷绑。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡韩脑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粹污,到底是詐尸還是另有隱情段多,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布壮吩,位于F島的核電站进苍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸭叙。R本人自食惡果不足惜琅捏,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望递雀。 院中可真熱鬧,春花似錦蚀浆、人聲如沸缀程。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杨凑。三九已至,卻和暖如春摆昧,著一層夾襖步出監(jiān)牢的瞬間撩满,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伺帘,地道東北人昭躺。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像伪嫁,于是被迫代替她去往敵國和親领炫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 下載 下載管理器 SDWebImageDownLoader作為一個單例來管理圖片的下載操作张咳。圖片的下載是放在一個N...
    wind_dy閱讀 1,489評論 0 1
  • 技術(shù)無極限帝洪,從菜鳥開始,從源碼開始脚猾。 由于公司目前項目還是用OC寫的項目葱峡,沒有升級swift 所以暫時SDWebI...
    充滿活力的早晨閱讀 12,656評論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,111評論 1 32
  • 項目中一直都有使用SDWebImage,對這個框架有一定的了解龙助,但是體系卻未能貫通砰奕,因此特地整理下,主要參考: i...
    林大鵬閱讀 1,471評論 2 13
  • 今天吃過晚飯媽媽帶我還有鄰居家的哥哥去體育場玩了,到了廣場人好多懊诓巍脆淹!有跳舞的,舞劍的沽一,打太極的盖溺,還有跑步的,我在想...
    衡越閱讀 387評論 0 0