iOS圖片解析與YYImage源碼學習

基礎知識

像素

圖像的基本元素。舉個例子:將一張圖片放到PS中儘可能的放大,那麼我們可以看到一個個的小格子,其中每個小格子就是一個像素點,每個像素點有且僅有一個顏色。
像素由四種不同的向量組成,即我們熟悉的RGBA(red,green,blue,alpha)。

位圖

位圖就是一個像素數組,數組中的每個像素都代表圖片中的一個點。我們經常用到的JPEG和PNG圖片就是位圖。(壓縮過的圖片格式)。

幀緩衝區

幀緩衝區(顯存):是由像素組成的二維數組,每一個存儲單元對應屏幕上的一個像素,整個幀緩衝對應一幀圖像即當前屏幕畫面。我們知道iOS設備屏幕是一秒刷新60次,如果幀緩衝區的內容有改變,那麼我們看到的屏幕顯示內容就會改變。

圖片處理的過程知識

從圖片文件把 圖片數據的像素拿出來(RGBA), 對像素進行操作, 進行一個轉換(Bitmap (GPU))
修改完之後,還原(圖片的屬性 RGBA,RGBA (寬度,高度,色值空間,拿到寬度和高度,每一個畫多少個像素,畫多少行))

iOS圖片顯示的流程

一張圖片從磁盤中顯示到屏幕上過程大致如下:從磁盤加載圖片信息、解碼二進制圖片數據爲位圖、通過 CoreAnimation 框架處理最終繪製到屏幕上

  1. 假設我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個時候的圖片並沒有解壓縮;
  2. 然後將生成的 UIImage 賦值給 UIImageView ;
  3. 接着一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化;
  4. 在主線程的下一個 run loop 到來時,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進行 copy 操作,而受圖片是否字節對齊等因素的影響,這個 copy 操作可能會涉及以下部分或全部步驟:
  5. 分配內存緩衝區用於管理文件 IO 和解壓縮操作;
  6. 將文件數據從磁盤讀到內存中;
  7. 將壓縮的圖片數據解碼成未壓縮的位圖形式,這是一個非常耗時的 CPU 操作;
  8. 最後 Core Animation 使用未壓縮的位圖數據渲染 UIImageView 的圖層。
    在上面的步驟中,我們提到了圖片的解壓縮是一個非常耗時的 CPU 操作,並且它默認是在主線程中執行的。
加載優化

對於加載過程,若文件過大或加載頻繁影響了幀率(比如列表展示大圖),可以使用異步方式加載圖片,減少主線程的壓力,代碼大致如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"testImage" ofType:@"jpeg"]];
dispatch_async(dispatch_get_main_queue(), ^{
//業務
});
});

ImageIO 核心

ImageIO框架提供了讀取與寫入圖片數據的基本方法,使用它可以直接獲取到圖片文件的內容數據,ImageIO框架中包含6個頭文件,其中完成主要功能的是前兩個頭文件中定義的方法:

1.CGImageSource.h:負責讀取圖片數據。

2.CGImageDestination.h:負責寫入圖片數據。

3.CGImageMetadata.h:圖片文件元數據類。

4.CGImageProperties:定義了框架中使用的字符串常量和宏。

5.ImageIOBase.h:預處理邏輯,無需關心。

  1. CGImageSource.h:負責讀取圖片數據。

CGImageSource類的主要作用是用來讀取圖片數據,在平時開發中,關於圖片我們使用的最多的可能是UIImage類,UIImage是iOS系統UI系統中用於構建圖像對象的類,但是其中只有圖像數據,實際上一個圖片文件中存儲的除了圖片數據外,還有一些地理位置、設備類型、時間等信息,除此之外,一個圖片文件中可能存儲的也不只一張圖像(例如gif文件)。CGImageSource就是這樣的一個抽象圖片數據示例,從其中可以獲取到我們所關心的所有數據。
讀取圖片文件數據,並將其展示在視圖的簡單代碼示例如下:

//獲取圖片文件路徑
NSString * path = [[NSBundle mainBundle]pathForResource:@"timg" ofType:@"jpeg"];
NSURL * url = [NSURL fileURLWithPath:path];
CGImageRef myImage = NULL;
CGImageSourceRef myImageSource;
//通過文件路徑創建CGImageSource對象
myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
//獲取第一張圖片
myImage = CGImageSourceCreateImageAtIndex(myImageSource,
0,
NULL);
CFRelease(myImageSource);
UIImageView * image = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
image.image = [UIImage imageWithCGImage:myImage];
[self.view addSubview:image];

上面的示例代碼採用的是本地的一個素材文件,當然通過網絡圖片鏈接也是可以創建CGImageSource獨享的。除了通過URL鏈接的方式創建對象,ImageIO框架中還提供了兩種方法,解析如下:

//通過數據提供器創建CGImageSource對象
/*
CGDataProviderRef是CoreGraphics框架中的一個數據讀取類,其也可以通過Data數據,URL和文件名來創建
*/
CGImageSourceRef __nullable CGImageSourceCreateWithDataProvider(CGDataProviderRef __nonnull provider, CFDictionaryRef __nullable options);
//通過Data數據創建CGImageSource對象
CGImageSourceRef __nullable CGImageSourceCreateWithData(CFDataRef __nonnull data, CFDictionaryRef __nullable options);
  1. CGImageDestination.h:負責寫入圖片數據
    CGImageSource是圖片文件數據的抽象對象,而CGImageDestination的作用則是將抽象的圖片數據寫入指定的目標中。將圖片寫成文件示例如下:
//創建存儲路徑
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *newPath = [paths.firstObject stringByAppendingPathComponent:[NSString stringWithFormat:@"image.png"]];
CFURLRef URL =  CFURLCreateWithFileSystemPath (
kCFAllocatorDefault,
(CFStringRef)newPath,
kCFURLPOSIXPathStyle, 
false);
//創建CGImageDestination對象
CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL(URL,CFSTR("public.png"), 1, NULL);
UIImage * image = [UIImage imageNamed:@"timg.jpeg"];
//寫入圖片
CGImageDestinationAddImage(myImageDest, image.CGImage, NULL);
CGImageDestinationFinalize(myImageDest);
CFRelease(myImageDest);

更多詳情查看ImageIO更多的詳情

YYImage 結構

通過 YYImage 源碼可以按照其與 UIKit 的對應關係劃分爲三個層級:

層級: UIKit YYImage
圖像層 UIImage YImage,YYFrameImage,YYSpriteSheetImage
視圖層 UIImageView YYAnimatedImageView
編/解碼層 ImageIO.framework YYImageCoder
  • 圖像層,把不同類型的圖像信息封裝成類並提供初始化和其他便捷接口。
  • 視圖層,負責圖像層內容的顯示(包含動態圖像的動畫播放)工作。
  • 編/解碼層,提供圖像底層支持,使整個框架得以支持市場主流的圖片格式。
YYImage 結構圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8we4MVk4-1572924259122)(evernotecid://DC8466E2-282C-4B26-91B9-7D010D5B0CAB/appyinxiangcom/6357986/ENResource/p873)]

YYImage 類

該類對UIImage進行拓展,支持 WebP、APNG、GIF 格式的圖片解碼,爲了避免產生全局緩存,重載了imageNamed:方法:

+ (YYImage *)imageNamed:(NSString *)name {
...
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
NSArray *scales = _NSBundlePreferredScales();
for (int s = 0; s < scales.count; s++) {
scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
for (NSString *e in exts) {
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
if (path) break;
}
if (path) break;
}
...
return [[self alloc] initWithData:data scale:scale];
}

initWithData 核心代碼

YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];  //用來獲取圖片組裏每個圖片的屬性:每張圖片停留的時間等其他屬性、循環次數、圖片方向、圖片寬、高,並保存到frames這麼個數組裏

YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];  //圖片解壓
YYImageCoder 編解碼

該文件中主要包含了YYImageFrame圖片幀信息的類、YYImageDecoder解碼器、YYImageEncoder編碼器。

1、解碼核心代碼

GImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
...
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
if (!context) return NULL;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef newImage = CGBitmapContextCreateImage(context);
CFRelease(context);
return newImage;
...
}

解碼核心代碼是將CGImageRef數據轉化爲位圖數據:

使用CGBitmapContextCreate()創建圖片上下文。

使用CGContextDrawImage()將圖片繪製到上下文中。

使用CGBitmapContextCreateImage()通過上下文生成圖片。

APNG的處理

PNG的文件結構

PNG文件結構很簡單,主要有數據塊(Chunk Block)組成,最少包含4個數據塊。PNG標識符 PNG數據塊(IHDR) PNG數據塊(其他類型數據塊) … PNG結尾數據塊(IEND)

PNG標識符,其文件頭位置總是由位固定的字節來描述的:
十進制數
137 80 78 71 13 10 26 10
十六進制數
89 50 4E 47 0D 0A 1A 0A

一個標準的PNG文件結構應該如下:

內容 內容 內容 內容
PNG文件標誌 PNG數據塊 …… PNG數據塊

PNG數據塊 …… PNG數據塊

PNG文件格式中的數據塊

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-r09lBk40-1572924259124)(evernotecid://DC8466E2-282C-4B26-91B9-7D010D5B0CAB/appyinxiangcom/6357986/ENResource/p871)]

PNG 由 4 部分組成,首先以 PNG Signature(PNG簽名塊)開頭,緊接着一個 IHDR(圖像頭部塊),然後是一個或多個的 IDAT(圖像數據塊),最終以 IEND(圖像結束塊)結尾。

數據塊結構

PNG文件中,每個數據塊由4個部分組成,如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tiUFHiic-1572924259131)(evernotecid://DC8466E2-282C-4B26-91B9-7D010D5B0CAB/appyinxiangcom/6357986/ENResource/p872)]

APNG 的組成

APNG 規範引入了三個新大塊,分別是:acTL(動畫控制塊)、fcTL(幀控制塊)、fdAT(幀數據塊),下圖是三個獨立的 PNG 文件組成 APNG 的示意圖。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9s0RXo9u-1572924259132)(evernotecid://DC8466E2-282C-4B26-91B9-7D010D5B0CAB/appyinxiangcom/6357986/ENResource/p870)]

  • acTL 塊必須在第一個 IDAT 塊之前,用於告訴解析器這是一個動畫 PNG,包含動畫幀總數和循環次數的信息
  • fcTL 塊是每一幀都必須的,出現在 IDAT 或 fdAT 之前,包含順序號、寬高、幀位置、延時等信息
  • fdAT 塊與 IDAT 塊有着相同的結構,除了 fcTL 中的順序號

從圖中可以發現第一幀與後面兩幀不同,那是因爲第一幀 APNG 文件存儲的爲一個正常的 PNG 數據塊,對於不支持 APNG 的瀏覽器或軟件,只會顯示 APNG 文件的第一幀,忽略後面附加的動畫塊,這也是爲什麼 APNG 能向下兼容 PNG 的原因。

APNG

更多詳細的請參考
http://web.jobbole.com/88847/

APNG 代碼邏輯

通過以下方法對圖片數據進行解壓獲取apng的信息
yy_png_info *apng = yy_png_info_create(_data.bytes, (uint32_t)_data.length); //data 圖片壓縮的數據
首先讀取apng的信息

// parse png chunks
uint32_t offset = 8;
uint32_t chunk_num = 0;  //數據塊數量
uint32_t chunk_capacity = chunk_realloc_num;  //內存區域容量
uint32_t apng_loop_num = 0;   //循環次數
int32_t apng_sequence_index = -1;   //序號
int32_t apng_frame_index = 0;   //frame的編號
int32_t apng_frame_number = -1;   //frame的數量

然後遍歷所有的數據塊,只針對IDAT、fcTL、acTL、FdAT數據塊進行處理,最終這個for 循環得出了info->apng_frames(這麼一個指針),它指向所有的frame數據.

for (int32_t i = 0; i < info->chunk_num; i++) {  
yy_png_chunk_info *chunk = info->chunks + i;
switch (chunk->fourcc) {
case YY_FOUR_CC('I', 'D', 'A', 'T'): {
if (info->apng_shared_insert_index == 0) {
info->apng_shared_insert_index = i;
}
if (first_frame_is_cover) {
yy_png_frame_info *frame = info->apng_frames + frame_index;
frame->chunk_num++;
frame->chunk_size += chunk->length + 12;
}
} break;
case YY_FOUR_CC('a', 'c', 'T', 'L'): {
} break;
case YY_FOUR_CC('f', 'c', 'T', 'L'): {
frame_index++;
yy_png_frame_info *frame = info->apng_frames + frame_index;
frame->chunk_index = i + 1;
yy_png_chunk_fcTL_read(&frame->frame_control, data + chunk->offset + 8);
} break;
case YY_FOUR_CC('f', 'd', 'A', 'T'): {
yy_png_frame_info *frame = info->apng_frames + frame_index;
frame->chunk_num++;
frame->chunk_size += chunk->length + 12;
} break;
default: {
*shared_chunk_index = i;
shared_chunk_index++;
info->apng_shared_chunk_size += chunk->length + 12;
info->apng_shared_chunk_num++;
} break;
}
}

通過YYImageDecoder 來讀取圖片組裏每個圖片的屬性:每張圖片停留的時間等其他屬性、循環次數、圖片方向、圖片寬、高,並保存到frames這麼個數組裏 通過CGImageSourceRef 解壓出圖片數據生成UIImage

YYAnimatedImageView

YYAnimatedImageView類通過YYImage、YYFrameImage、YYSpriteSheetImage實現的協議方法拿到幀圖片數據和相關信息進行動畫展示。

1.初始化流程

該類重寫了一系列方法讓它們都走自定義配置:

- (void)setImage:(UIImage *)image {
if (self.image == image) return;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
- (void)setHighlightedImage:(UIImage *)highlightedImage {
if (self.highlightedImage == highlightedImage) return;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}

setImage:withType:方法就是將這些圖片數據賦值給super.image等,該方法最後會走imageChanged方法,這纔是主要的初始化配置:

- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
... //省略判斷是否是 SpriteSheet 類型來源

/*1、若上一次是 SpriteSheet 類型而當前顯示的圖片不是,
歸位 self.layer.contentsRect */
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;

/*2、SpriteSheet 類型時,通過`setContentsRect:forImage:`方法
配置self.layer.contentsRect */
if (hasContentsRect) {
CGRect rect = [((UIImage*) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}

/*3、若是多幀的圖片,通過`resetAnimated`方法初始化顯示多幀動畫需要的配置;
然後拿到第一幀圖片調用`setNeedsDisplay `繪製出來 */
if (newImageFrameCount > 1) {
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
[self didMoved];
}
2.動畫啓動和結束的時機
- (void)didMoved {
if (self.autoPlayAnimatedImage) {
if(self.superview && self.window) {
[self startAnimating];
} else {
[self stopAnimating];
}
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self didMoved];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self didMoved];
}

在didMoveToWindow和didMoveToSuperview週期方法中嘗試啓動或結束動畫,不需要在組件內部特意的去調用就能實現自動的播放和停止。而didMoved方法中判斷是否開啓動畫寫了個self.superview && self.window,意味着YYAnimatedImageView光有父視圖還不能開啓動畫,還需要展示在window上纔行。

3.異步解壓

YYAnimatedImageView有個隊列_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;變量NSOperationQueue *_requestQueue;

_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;

可以看出_requestQueue是一個串行的隊列,用於處理解壓任務。

YAnimatedImageViewFetchOperation繼承自NSOperation,重寫了main方法自定義解壓任務。它是結合變量_requestQueue;來使用的:

- (void)main {
...
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
...
if (miss) {
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}

關鍵代碼中,animatedImageFrameAtIndex方法便會調用解碼,後面yy_imageByDecoded屬性是對解碼成功的第二重保證,view->_buffer[@(idx)] = img是做緩存。

可以看到作者經常使用if ([self isCancelled]) break(return);判斷返回,因爲在執行NSOperation任務的過程中該任務可能會被取消。
for循環中使用@autoreleasepool避免同一 RunLoop 循環中堆積過多的局部變量。
由此,基本可以保證解壓過程是在_requestQueue串行隊列執行的,不會影響主線程。

4.緩存機制

YYAnimatedImageView有如下幾個變量:

NSMutableDictionary *_buffer; ///< frame buffer
BOOL _bufferMiss; ///< whether miss frame on last opportunity
NSUInteger _maxBufferCount; ///< maximum buffer count
NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step)

_buffter就是緩存池,在_YYAnimatedImageViewFetchOperation私有類的main函數中有給_buffer賦值,作者還限制了最大緩存數量。

緩存限制計算
- (void)calcMaxBufferCount {
int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;
if (bytes == 0) bytes = 1024;

int64_t total = _YYDeviceMemoryTotal();
int64_t free = _YYDeviceMemoryFree();
int64_t max = MIN(total * 0.2, free * 0.6);
max = MAX(max, BUFFER_SIZE);
if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
double maxBufferCount = (double)max / (double)bytes;
if (maxBufferCount < 1) maxBufferCount = 1;
else if (maxBufferCount > 512) maxBufferCount = 512;
_maxBufferCount = maxBufferCount;
}

該方法並不複雜,通過_YYDeviceMemoryTotal()拿到內存總數乘以 0.2,通過_YYDeviceMemoryFree()拿到剩餘的內存乘以 0.6,然後取它們最小值;之後通過最小的緩存值BUFFER_SIZE和用戶自定義的_maxBufferSize屬性綜合判斷。

動畫的核心方法

該類使用CADisplayLink做計時任務,顯示系統每幀回調都會觸發,所以默認大致是 60 次/秒。CADisplayLink的特性決定了它非常適合做和幀率相關的 UI 邏輯。

- (void)step:(CADisplayLink *)link {

UIImage <YYAnimatedImage> *image = _curAnimatedImage;
NSMutableDictionary *buffer = _buffer;
UIImage *bufferedImage = nil;
NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
BOOL bufferIsFull = NO;

if (!image) return;
if (_loopEnd) { // view will keep in last frame
[self stopAnimating];
return;
}

NSTimeInterval delay = 0;
if (!_bufferMiss) {  //下一張圖片缺失,那麼此時_bufferMiss=YES
_time += link.duration;
delay = [image animatedImageDurationAtIndex:_curIndex];  //第一張圖片的停留時間

if (_time < delay) return;  //如果累積時間小於停留時間 啥也不做,返回

_time -= delay;  //累積時間大於停留時間了,那麼換下一張圖片;

if (nextIndex == 0) {
_curLoop++;
if (_curLoop >= _totalLoop && _totalLoop != 0) {
_loopEnd = YES;
[self stopAnimating];
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
return; // stop at last frame
}
}
delay = [image animatedImageDurationAtIndex:nextIndex];  //獲取到下一幀的圖片停留時間

if (_time > delay) _time = delay; // do not jump over frame  下一幀圖片的停留時間小於累積時間
}
LOCK(

//獲取到下一張圖片
bufferedImage = buffer[@(nextIndex)];
if (bufferedImage) {
if ((int)_incrBufferCount < _totalFrameCount) {
[buffer removeObjectForKey:@(nextIndex)];
}
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = nextIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
if (_curImageHasContentsRect) {
_curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex]; //sprite sheet image裏的contentsRect數組裏的第_curIndex個數據
[self setContentsRect:_curContentsRect forImage:_curFrame];
}
nextIndex = (_curIndex + 1) % _totalFrameCount;
_bufferMiss = NO;
if (buffer.count == _totalFrameCount) {
bufferIsFull = YES;
}
} else {
_bufferMiss = YES;  //下一張圖片缺少了
}
)//LOCK

//若圖片存在
if (!_bufferMiss) {
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
}

//此時線程池裏沒有線程開啓
if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation];
}
}

具體思路

  1. 創建一個CADisplayLink 定時器,並添加到RunloopCommonMode下, 跟屏幕刷新相關 調用時間:1/60S = 16.7ms,到了時間點就調用一個方法:step方法
  2. 看CADisplayLink時間的累積,是不是達到臨界值,達到了我就可以更新下一張圖片(開闢一個線程,獲取到下一張解壓縮的圖片)
  3. 更新圖片的時候(獲取到下一張圖片,圖片不存在,開啓線程來獲取,直到獲取到,然後展示),只要把2步驟的解壓縮圖片賦值到界面上,就能展示出來了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章