国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

源碼閱讀:SDWebImage

2019-11-14 18:22:19
字體:
來源:轉載
供稿:網友

前言

SDWebImage是一個開源的iOS第三方庫,主要用于下載并緩存網絡圖片,在我的博文iOS網絡資源緩存ZCLURLCache·上篇提到過SDWebImage。它提供了UIImageViewMKAnnotationViewUIButton的categories(分類),支持網絡圖片的加載與緩存,其中UIImageView應該是使用最廣泛的。

本文從源碼的角度討論SDWebImage的下載和緩存的實現。

流程

在SDWebImage的使用例子中,給UIImageView設置圖片的代碼是:

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:[_objects objectAtIndex:indexPath.row]]                      placeholderImage:[UIImage imageNamed:@"placeholder"] options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];

可以看出,SDWebImage的使用非常簡單,只用這一行代碼,就可實現網絡圖片的加載與緩存。在這行代碼的背后,會執行下列操作:

  1. UIImageView+WebCachecategories會從管理類SDWebImageManager找圖片,并刷新UIImageView
  2. SDWebImageManager向從緩存類SDImageCache找URL對應的圖片緩存,如果沒找到,起用SDWebImageDownloader下載圖片。
  3. 緩存類SDImageCache會先在內存NSCache中找圖片,如果內存中沒有,就在磁盤上找,在磁盤上找到了,把圖片放入內存。
  4. SDWebImageDownloader會創建一個SDWebImageDownloaderOperation操作隊列下載圖片,下載完后緩存在內存和磁盤上(可選)。
  5. SDWebImageDownloaderOperation操作隊列使用NSURLConnection在后臺發起請求,下載圖片,反饋進度和下載結果。

源碼中有幾個關鍵的類,分別是:

  1. SDImageCache,緩存類,在內存和磁盤上緩存圖片,并對圖片編碼。
  2. SDWebImageDownloader,下載管理類,下載圖片。
  3. SDWebImageDownloaderOperation,下載操作隊列,繼承自NSOperation,在后臺發起HTTP請求并下載圖片。
  4. SDWebImageManager,管理類,協調緩存類與下載類。
  5. categories(分類),擴展視圖。

SDImageCache

SDImageCache實現了圖片的緩存機制,使用NSCache作為內存緩存,默認以com.hackemist.SDWebImageCache.default為磁盤的緩存命名空間,程序運行后,可以在應用程序的文件夾Library/Caches/default/com.hackemist.SDWebImageCache.default下看到一些緩存文件。當然,也可以指定其它命名空間初始化:

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory{    if ((self = [super init]))    {        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];        // initialise PNG signature data        kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];        // Create IO serial queue        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);        // Init default values        _maxCacheAge = kDefaultCacheMaxCacheAge;        // Init the memory cache        _memCache = [[AutoPurgeCache alloc] init];        _memCache.name = fullNamespace;        // Init the disk cache        if (directory != nil) {            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];        } else {            NSString *path = [self makeDiskCachePath:ns];            _diskCachePath = path;        }        // Set decomPRession to YES        _shouldDecompressImages = YES;        // memory cache enabled        _shouldCacheImagesInMemory = YES;        // Disable iCloud        _shouldDisableiCloud = YES;        dispatch_sync(_ioQueue, ^{            _fileManager = [NSFileManager new];        });#if TARGET_OS_ipHONE        // Subscribe to app events        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(clearMemory)                                                     name:UIapplicationDidReceiveMemoryWarningNotification                                                   object:nil];        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(cleanDisk)                                                     name:UIApplicationWillTerminateNotification                                                   object:nil];        [[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(backgroundCleanDisk)                                                     name:UIApplicationDidEnterBackgroundNotification                                                   object:nil];#endif    }    return self;}

SDImageCache會在系統發出低內存警告時釋放內存,并且在程序進入UIApplicationWillTerminateNotification時,清理磁盤緩存,清理磁盤的機制是:

  1. 刪除過期的圖片,默認7天過期,可以通過maxCacheAge修改過期天數。
  2. 如果緩存的數據大小超過設置的最大緩存maxCacheSize,則繼續刪除緩存,優先刪除最老的圖片,可以通過修改maxCacheSize來改變最大緩存大小。

緩存中取圖片

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock{    if (!doneBlock){        return nil;    }    if (!key){        doneBlock(nil, SDImageCacheTypeNone);        return nil;    }    // First check the in-memory cache...    UIImage *image = [self imageFromMemoryCacheForKey:key];    if (image){        doneBlock(image, SDImageCacheTypeMemory);        return nil;    }    NSOperation *operation = [NSOperation new];    dispatch_async(self.ioQueue, ^{        if (operation.isCancelled){            return;        }        @autoreleasepool {            UIImage *diskImage = [self diskImageForKey:key];            if (diskImage && self.shouldCacheImagesInMemory){                NSUInteger cost = SDCacheCostForImage(diskImage);                [self.memCache setObject:diskImage forKey:key cost:cost];            }            dispatch_async(dispatch_get_main_queue(), ^{                doneBlock(diskImage, SDImageCacheTypeDisk);            });        }    });    return operation;}

傳人的Block定義是:

typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);

先從內存中取圖片,內存中沒有的時候再從磁盤中取,通過Block返回取到的圖片和獲取圖片的方式,SDImageCacheType的定義如下:

typedef NS_ENUM(NSInteger, SDImageCacheType){    /**     * The image wasn't available the SDWebImage caches, but was downloaded from the web.     */    SDImageCacheTypeNone,    /**     * The image was obtained from the disk cache.     */    SDImageCacheTypeDisk,    /**     * The image was obtained from the memory cache.     */    SDImageCacheTypeMemory};

當然,也可能磁盤也沒有緩存,此時doneBlock中的diskImage的值時nil,處理方式doneBlock將在SDWebImageManager講到。

把圖片存入緩存

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk{    if (!image || !key){        return;    }    // if memory cache is enabled    if (self.shouldCacheImagesInMemory) {        NSUInteger cost = SDCacheCostForImage(image);        [self.memCache setObject:image forKey:key cost:cost];    }    if (toDisk) {        dispatch_async(self.ioQueue, ^ {            NSData *data = imageData;            if (image && (recalculate || !data))            {#if TARGET_OS_IPHONE                // We need to determine if the image is a PNG or a JPEG                // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)                // The first eight bytes of a PNG file always contain the following (decimal) values:                // 137 80 78 71 13 10 26 10                // If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)                // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||                                  alphaInfo == kCGImageAlphaNoneSkipLast);                BOOL imageIsPng = hasAlpha;                // But if we have an image data, we will look at the preffix                if ([imageData length] >= [kPNGSignatureData length]){                    imageIsPng = ImageDataHaspNGPreffix(imageData);                }                if (imageIsPng){                    data = UIImagePNGRepresentation(image);                }                else{                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);                }#else                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];#endif            }            if (data){                if (![_fileManager fileExistsAtPath:_diskCachePath]){                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];                }                // get cache Path for image key                NSString *cachePathForKey = [self defaultCachePathForKey:key];                // transform to NSUrl                NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];                // disable iCloud backup                if (self.shouldDisableiCloud){                    [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];                }            }        });    }}

如果需要存入內存,則先存入內存,toDisk標識是否存入磁盤,為Yes的時候,即要存入磁盤,需要先對圖片編碼,再存入磁盤。

SDWebImageDownloader

SDWebImageDownloader是下載管理類,是一個單例類,圖片的下載在一個NSOperationQueue隊列中完成。

@property (strong, nonatomic) NSOperationQueue *downloadQueue;

默認情況下,隊列最多并發數為6,可以通過修改maxConcurrentOperationCount來改變最多并發數。

- (id)init{    if ((self = [super init])){        ···        _downloadQueue.maxConcurrentOperationCount = 6;                ···    }    return self;}- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;}

下載圖片的消息是:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageDownloaderOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

該方法會創建一個SDWebImageDownloaderOperation操作隊列來執行下載操作,傳入的兩個Block用于網絡下載的回調,progressBlock為下載進度回調,completedBlock為下載完成回調,回調信息存儲在URLCallbacks中,為保證只有一個線程操作URLCallbacksSDWebImageDownloader把這些操作放入了一個barrierQueue隊列中。

_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback{    ···    dispatch_barrier_sync(self.barrierQueue, ^{        BOOL first = NO;        if (!self.URLCallbacks[url]){            self.URLCallbacks[url] = [NSMutableArray new];            first = YES;        }        // Handle single download of simultaneous download request for the same URL        NSMutableArray *callbacksForURL = self.URLCallbacks[url];        NSMutableDictionary *callbacks = [NSMutableDictionary new];        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];        [callbacksForURL addObject:callbacks];        self.URLCallbacks[url] = callbacksForURL;        if (first){            createCallback();        }    });}

SDWebImageDownloader還提供了兩種下載任務調度方式(先進先出和后進先出):

typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder){    /**     * Default value. All download operations will execute in queue style (first-in-first-out).     */    SDWebImageDownloaderFIFOExecutionOrder,    /**     * All download operations will execute in stack style (last-in-first-out).     */    SDWebImageDownloaderLIFOExecutionOrder};

通過修改executionOrder可改變下載方式:

@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock{        ···        [wself.downloadQueue addOperation:operation];        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder)        {            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency            [wself.lastAddedOperation addDependency:operation];            wself.lastAddedOperation = operation;        }    }];    ···}

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation是下載操作隊列,繼承自NSOperation,并采用了SDWebImageOperation協議,該協議只有一個cancel方法。只暴露了一個方法:

- (id)initWithRequest:(NSURLRequest *)request              options:(SDWebImageDownloaderOptions)options             progress:(SDWebImageDownloaderProgressBlock)progressBlock            completed:(SDWebImageDownloaderCompletedBlock)completedBlock            cancelled:(SDWebImageNoParamsBlock)cancelBlock;

該方法的progressBlockcompletedBlockBlock與SDWebImageDownloader下載管理類對應。

SDWebImageDownloaderOperation使用startdone來控制狀態,而不是使用main。圖片的下載使用NSURLConnection,在協議中接收數據并回調Block通知下載進度和下載完成。

SDWebImageManager

SDWebImageManager是一個單例管理類,負責協調圖片緩存和圖片下載,是對 SDImageCacheSDWebImageDownloader的封裝。

@property (strong, nonatomic, readwrite) SDImageCache *imageCache;@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;

在一般的使用中,我們并不直接使用SDImageCacheSDWebImageDownloader,而是使用 SDWebImageManagerSDWebImageManager的核心方法是:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

代碼有點長,這里就不貼出來了,代碼控制了圖片的緩存和下載的整個流程,同樣兩個Block與前面的也是一一對應。

Categories(分類)

Categories目錄下實現了多個分類,實現方式都是一致的。使用最多的是UIImageView+WebCache,針對UIImageView擴展了一些方法。在使用的時候調用的方法是:

objective-c - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock

該方法依賴于SDWebImageManager,從SDWebImageManager管理類中獲取到圖片并刷新顯示,至于圖片是從緩存中得到的還是從網絡上下載的對UIImageView是透明的。

其它的categories就不多說了,當然還可以創建自己的分類。

結語

本文是我讀SDWebImage的源代碼的一點理解,主要集中在圖片的下載和緩存,不包括WebP、GIF和圖片編碼的討論。涉及到得技術有:

本文討論并不完整,更多的東西還靠以后慢慢發掘。

原文鏈接: http://zh.5long.me/2015/source-sdwebImage/


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 陈巴尔虎旗| 雷州市| 宜阳县| 加查县| 巴青县| 湄潭县| 临西县| 宣威市| 清苑县| 武乡县| 垫江县| 普洱| 水富县| 彭泽县| 灌云县| 塔河县| 乌拉特后旗| 武隆县| 同心县| 阿拉善左旗| 枣阳市| 上栗县| 黔西| 故城县| 永济市| 兖州市| 云浮市| 望都县| 保定市| 定襄县| 磐石市| 涪陵区| 汉川市| 论坛| 巫溪县| 内乡县| 板桥市| 潜江市| 山丹县| 双牌县| 深泽县|