1.圖片加載原理
? ? 1.磁盤上的圖片文件大小和加載到imageView上的圖片大小的關(guān)系
如圖所示 當(dāng)沒有加載圖片的時(shí)候此時(shí)內(nèi)存消耗為17.8M
當(dāng)我們點(diǎn)擊Push按鈕加載圖片到imageView中時(shí)浩考,此時(shí)內(nèi)存的占用缺達(dá)到了35M木张,比未加載圖片時(shí)多出了17.2M贰镣,但是我們的圖片如圖1所示只有126KB粟判,這又是這回事呢
總結(jié):磁盤上的圖片文件大小和加載到imageView上的圖片大小沒有關(guān)系豌习。
問題:加載到imageView上的圖片大小和什么有關(guān)系呢尾膊?
回到:由兩方面決定:1:圖片的尺寸大小 2:Color Profile(P3廣色域斩松,display3瞻凤,aRGB憨攒,sRGB)
圖片加載到imageView上實(shí)際的大小 1080*1920*4(aRGB) = 8294400b具體是不是這樣的呢
通過我們的打印得到的結(jié)果和我們的猜想完全對(duì)應(yīng),由于這里img和imgView的原因圖片被加載了2次所有內(nèi)存會(huì)多出17.2M阀参,同時(shí)我們也驗(yàn)證了磁盤上的圖片文件大小和加載到imageView上的圖片大小沒有關(guān)系肝集。
同時(shí)我們也可以通過Allocations來驗(yàn)證
通過圖片是否加載前后兩次的快照的內(nèi)存增長占用情況也可以得出相同的結(jié)果
? ? 2.imageNamed:這個(gè)方法到底做了什么呢?
在圖片加載時(shí)在內(nèi)存中創(chuàng)建一個(gè)Data Buffer(磁盤圖片加載進(jìn)內(nèi)存中的一個(gè)緩沖區(qū))儲(chǔ)存圖片壓縮之后的元數(shù)據(jù)(png蛛壳,jpg等)通過解碼Decode之后在內(nèi)存中創(chuàng)建Image Buffer儲(chǔ)存圖片的像素信息杏瞻,拿到像素信息后就交給GPU,GPU經(jīng)過計(jì)算放到當(dāng)前的Frame Buffer中衙荐,最后通過硬件把當(dāng)前的Frame Buffer中的信息渲染到屏幕中
1.Data Buffer:內(nèi)存中存儲(chǔ)圖片的原始信息(jpg捞挥,png等)
2.Image Buffer:內(nèi)存中儲(chǔ)存圖片的像素信息(大小和當(dāng)前圖片大小正比 寬*高*Color Profile系數(shù))
3.Frame Buffer:顯存中(Video RAM)
解碼過程:Data Buffer ->生成Image Buffer -> 上傳給GPU -> Frame Buffer -> V-sync 每秒60/120次更新屏幕
2.圖片解碼方式
在iOS中除了通過imageNamed:這種隱式解碼還有其他的方式可以進(jìn)行解碼
1.隱式解碼
????1.imageNamed:
2.主動(dòng)解碼
? ? 1.Core Graphics
????2.ImageIO
? ? 3.CGContext
通過Core Graphics就把在主線程解碼的操作放到了子線程中,下面我們利用Timer Profiler驗(yàn)證下
在主線程中我們沒有發(fā)現(xiàn)有圖片解碼的操作忧吟,這跟我們預(yù)想的一樣
相較于其他使用CGBitmapContextCreate函數(shù)解碼最高
CGBitmapContextCreate(<#void * _Nullable data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef? _Nullable space#>, <#uint32_t bitmapInfo#>)參數(shù)所代表的意義
data:所需要的內(nèi)存空間砌函,如果不需要操作這片內(nèi)存空間可以直接傳nil
heigh/width : 高和寬
bitsPerComponent:像素點(diǎn)RGB(8bit)
bytesPerRow:每一行使用的字節(jié)數(shù)( 4 * width)最后是64的整數(shù)倍---字節(jié)對(duì)齊
space:顏色空間
bitmapInfo:RGBA順序, 大小端的模式
3.YYImage實(shí)現(xiàn)源碼解析
1.YYImage繼承自UIImage,并遵守了YYAnimatedImage協(xié)議
2.重寫了UIimage加載圖片的方法
????+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!
????+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;
????+ (nullable YYImage *)imageWithData:(NSData *)data;
????+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
3.添加了一些屬性
? ? 1.animatedImageType:圖片類型
????2.animatedImageData:原圖片Data
? ? 3.animatedImageMemorySize:圖片占用的內(nèi)存空間(多幀圖片使用)
? ? 4.preloadAllAnimatedImageFrames:將所有幀圖像預(yù)加載到內(nèi)存讹俊。
通過圖片的名字在Bundle中獲取到圖片的路徑并把圖片轉(zhuǎn)成NSData傳入到下個(gè)方法中
首先初始化了一個(gè)信號(hào)量垦沉,接著把接下來的所生成的對(duì)象加入到了自動(dòng)釋放池中
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
獲取ImageSource
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
對(duì)圖片進(jìn)行手動(dòng)解碼,并生成了Image Buffer(第一幀)
如果是多幀圖計(jì)算了內(nèi)存占用量否則直接返回
1.對(duì)傳入的data判空處理
2.Image解碼器的初始化
3.手動(dòng)解碼?- (BOOL)updateData:(NSData *)data final:(BOOL)final
4.判斷解碼是否成功
這里的代碼就很簡單
1.加了一把遞歸鎖 ----- 防止 漸進(jìn)式解碼重復(fù)進(jìn)入同一線程
? ? 漸進(jìn)式解碼:
? ??CGImageSourceCreateIncremental(<#CFDictionaryRef? _Nullable options#>)
? ??CGImageSourceUpdateData(<#CGImageSourceRef? _Nonnull isrc#>, <#CFDataRef? _Nonnull data#>, <#bool final#>)
2.調(diào)用一個(gè)私有函數(shù)仍劈,返回是否獲取imageSource成功
1.YYImageType type = YYImageDetectType((__bridge CFDataRef)data);獲取圖片的格式(png厕倍,jpg等)
2.- (void)_updateSource:獲取ImageSource
根據(jù)圖片格式的不同,分別采用不同的解碼方式
1.判斷源是否為nil贩疙,如果為空創(chuàng)建輸入源 這里創(chuàng)建輸入源用了兩種方式绑青,在前文中已經(jīng)介紹過了
2.判斷圖片是否為GIF圖片
根據(jù)前文中獲取到的圖片有多少幀進(jìn)行一個(gè)循環(huán),獲取每一幀的圖片信息
接下來我們來到關(guān)鍵的地方解碼圖片:YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
這里跟前文中?- (BOOL)updateData:(NSData *)data final:(BOOL)final類似加了一把遞歸鎖屋群,這里就不做贅述闸婴,繼續(xù)往下走
1.首先判斷了圖片是否需要混合
2.調(diào)用了一個(gè)私有函數(shù)獲取到ImageRef ?CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});
3.調(diào)用YYCGImageCreateDecodedCopy(imageRef, YES);函數(shù)
這這里我們就看到了一些比較熟悉的函數(shù)調(diào)用,原來它也是通過調(diào)用原生底層的函數(shù)對(duì)圖片進(jìn)行解碼
這個(gè)方法里也是這樣
總結(jié):
YYImage
1.創(chuàng)建了YYImageDecoder(解碼操作)
? ? 1.獲取圖片的元數(shù)據(jù) (_updateSource)
? ? 2.獲取YYImageDecoderFrame (圖片信息)
2.frameAtIndex: decodeForDisplay:(解碼操作)線程安全芍躏,此時(shí)已經(jīng)獲取到了解碼后數(shù)據(jù)
4.YYAnimatedImageView解析
1.YYAnimatedImageView繼承自UIImageView 用于展示動(dòng)圖
2.autoPlayAnimatedImage:默認(rèn)YES 自動(dòng)播放動(dòng)圖
3.currentAnimatedImageIndex:當(dāng)前展示的圖片是動(dòng)圖的第幾幀
4.currentIsPlayingAnimation:當(dāng)前是否在播放中
5.runloopMode:當(dāng)前runloop的模式
6.maxBufferSize:最大容積
首先我們找到這個(gè)類的入口函數(shù)
1.設(shè)置了runloopMode:NSRunLoopCommonModes邪乍,為了播放動(dòng)圖時(shí)不受其他事件的影響
2.把a(bǔ)utoPlayAnimatedImage設(shè)置為YES
重寫了Image的set方法作為下一步的入口
1.停止動(dòng)畫
2.調(diào)用resetAnimated重置動(dòng)畫
3.imageChanged:圖片改變
1.詢問當(dāng)前的協(xié)議[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)])
2.獲取當(dāng)前動(dòng)圖的下一幀
3.已經(jīng)圖片的幀
4.[self setNeedsDisplay];在下一個(gè)runloop到來時(shí)對(duì)圖層樹進(jìn)行更改
5.[self didMoved]
判斷當(dāng)前圖片是否需要自動(dòng)播放
1.創(chuàng)建一個(gè)定時(shí)器用于播放動(dòng)圖
注:為什么使用CADisplayLink而不使用NSTimer,CADisplayLink根據(jù)屏幕的刷新頻率來執(zhí)行事件对竣,而NSTimer在預(yù)先設(shè)置的時(shí)間點(diǎn)上執(zhí)行事件庇楞,CADisplayLink比NSTimer跟精準(zhǔn)
2.在后臺(tái)現(xiàn)場(chǎng)異步釋放對(duì)象的策略
1.當(dāng)緩存區(qū)命中時(shí)進(jìn)行播放圖片
2.當(dāng)未命中緩沖區(qū)時(shí),證明需要進(jìn)行解碼
總結(jié):YYAnimatedImageView
1.動(dòng)圖的播放 -> 定時(shí)器的實(shí)現(xiàn)
2.根據(jù)index讀取下一幀nextIndex
3.去緩沖區(qū)里面取數(shù)據(jù)(_timer到了當(dāng)前幀的時(shí)間)
4.setNeedsDisplay 更新視圖
5.異步子線程進(jìn)行解碼操作