個人博客地址:Lixuzong's Blog
我們所在屏幕上看到的都是Core Animation框架提供的,所以這并不是一個只關(guān)于動畫的框架,他包含了我們在屏幕上所能看到的一切東西渡紫。這里首先說一下CALayer和UIView的關(guān)系峰档,UIView是CALayer的管理者窒百,CALayer是我們在屏幕上所能看到的UIView說呈現(xiàn)的。用過Photoshop的都知道训措,圖片可以是很多圖層的疊加計算的結(jié)果,與之類似,我們在屏幕上所看到的也是CALayer層疊加的結(jié)果隙弛,UIView就管理著這個相互疊加的過程架馋。再者,UIView還管理著手勢的響應(yīng)全闷。如果只從視圖的角度來看的話叉寂,UIView只是對CALayer的一層封裝。
首先先放上本書的鏈接总珠,看的過程中有些翻譯不是非常的準(zhǔn)確屏鳍,所以是結(jié)合著原版的一起看的。
關(guān)于Layer的知識點
圖層樹
關(guān)于圖層樹局服,我們看到的圖層與UIView的層級是對應(yīng)的钓瞭,而UIView的層級與CALayer是對應(yīng)的,其中CALayer tree又分為呈現(xiàn)樹(presentation layer tree)和模型樹(model layer tree)淫奔,因為layer默認(rèn)是帶有隱式動畫的山涡,但是我們直接改變layer屬性的時候是立即生效的,也就是說layer的屬性改變是立即執(zhí)行的唆迁,但是界面上還是依然反應(yīng)有一個動畫的過程鸭丛,所以這個時候就分成了兩個tree,一個是界面上所呈現(xiàn)的層級(presentation layer tree)唐责,一個是我們修改的層級(model layer tree)鳞溉。
Layer的contents
contents是一個id類型的對象,但是接收的類型應(yīng)該是Core Foundation框架的類型鼠哥,這里是因為在MAC OS系統(tǒng)上CGImage和NSImage類型值都是可以起作用的熟菲。在iOS上的話就是用bridge將CGImage轉(zhuǎn)成Core Foundation的id類型就可以了。下面具體看一下contens的屬性朴恳。
相關(guān)屬性
contentsGravity
我們操作UIView屬性的時候抄罕,會有一個contentMode屬性定義怎么與圖層的邊界對其,與之對應(yīng)的CALayer屬性就是contentsGravity屬性于颖,并且其根本上操作的還是對應(yīng)的Layer的屬性贞绵。contentsScale
iOS的屏幕的單位并不是直接使用像素,而是直接使用點來作為距離單位恍飘,這樣的話方便兼容不同分辨率的屏幕榨崩。scale就是一個點包含幾個像素,視網(wǎng)膜屏就是2.maskToBounds
默認(rèn)情況下章母,UIView會繪制超過邊界的內(nèi)容或者子視圖母蛛,CALayer下也是這樣的。UIView有一個叫做clipsToBounds的屬性可以用來決定是否顯示超出邊界的內(nèi)容乳怎,CALayer對應(yīng)的屬性叫做maskToBounds彩郊。contentsRect
這個屬性是用來給圖片截圖的前弯,配合maskToBoundds可以確定顯現(xiàn)image的局部信息。contentsCenter
是用來確定contents變大情況下的拉伸情況秫逝。
custom drawing
是在不設(shè)置contents屬性為image的情況下直接畫一個恕出,在UIView里面有drawRect:,雖然drawRect是UIView的方法违帆,但是在底層還是通過CALayer安排重繪工作和保存產(chǎn)生的圖片的浙巫。繪制用到的是CADelegate提供的兩個方法,分別是drawLayer:(CALayer)layer inContext:(CGContext)ctx調(diào)用這個方法之前CALayer會自動生成一個空的圖像(由bounds和contentsSacle決定)和一個Core Graphics的繪制上下文環(huán)境刷后,為繪制圖做準(zhǔn)備的畴。調(diào)用的時候與UIView方法setNeedDisplay類似,CALayer的方法是調(diào)用displayLayer來觸發(fā)重繪操作尝胆。
圖層幾何
圖層幾何是看圖層內(nèi)部是如何根據(jù)父圖層和兄弟圖層來控制位置和尺寸的丧裁。另外我們也會涉及如何管理圖層的幾何結(jié)構(gòu),以及它是如何被自動調(diào)整和自動布局影響的含衔。
布局
UIView有三個比較重要的布局屬性:frame\bounds\center煎娇,CALayer對應(yīng)的叫做frame\bounds\position,其中center和position是相對于父圖層anchorPoint(錨點)所在的位置贪染。frame的值和bounds并不是嚴(yán)格的寬高對應(yīng)的缓呛,當(dāng)一個視圖發(fā)生旋轉(zhuǎn)之后,其bounds是不便的抑进,而frame的值是包含bounds的矩形强经。這里就可以看出frame是一個虛擬屬性睡陪,是根據(jù)bounds寺渗、position和transform計算出來的,所以改變其中一個值都會使frame發(fā)生改變兰迫。錨點
contentsRect和contentsCenter屬性類似信殊,anchorPoint用單位坐標(biāo)來描述,也就是圖層的相對坐標(biāo)汁果,圖層左上角是{0, 0}涡拘,右下角是{1, 1},因此默認(rèn)坐標(biāo)是{0.5, 0.5}据德。anchorPoint可以通過指定x和y值小于0或者大于1鳄乏,使它放置在圖層范圍之外。改變錨點也會影響到frame的值棘利。坐標(biāo)系
和視圖一樣橱野,圖層在圖層樹當(dāng)中也是相對于父圖層按層級關(guān)系放置,一個圖層的position依賴于它父圖層的bounds善玫,如果父圖層發(fā)生了移動水援,它的所有子圖層也會跟著移動。與UIView相似,也有改變坐標(biāo)系的方法蜗元。與UIView不同的是CALayer是三維空間的構(gòu)圖或渤,可以改變zPosition和anchorPointZ來改變z軸上的順序。比如layer重疊順序奕扣。Hit Testing
CALayer并不關(guān)心任何響應(yīng)鏈?zhǔn)录金校圆荒苤苯犹幚碛|摸事件或者手勢。但是它有一系列的方法幫你處理事件:-containsPoint:和-hitTest:成畦。第一個方法可以判定點擊的點是否在這個layer的區(qū)域內(nèi)距芬,但是要注意的是坐標(biāo)系的轉(zhuǎn)換,第二個方式是直接判斷響應(yīng)這個點的圖層循帐,所以沒必要轉(zhuǎn)換坐標(biāo)系框仔,結(jié)果是返回這個響應(yīng)的圖層。layout
與UIView的另一個不同點拄养,layer是沒有autoLayout的屬性的离斩,所以這也是要使用UIView而不是使用CALayer的原因之一。如果想控制layer的布局瘪匿,這里提供了一個CALayerDelegate的方法.
-(void)layoutSublayersOfLayer:(CALayer*)layer
當(dāng)圖層的bounds改變或者圖層 -setNeedsLayout 方法被調(diào)用的時候跛梗,這個函數(shù)就會執(zhí)行。在這個方法里面可以重新擺放或者調(diào)整子圖層的大小棋弥。
Layer視覺效果
圓角
圓角主要是使用cornerRadius屬性核偿,直接設(shè)置半徑就會出現(xiàn)圓角的效果。同時設(shè)置圓角會觸發(fā)離屏渲染顽染。圖層邊框
繪制邊框使用的是borderColor和borderWidth屬性漾岳,其中borderColor是CGColorRef類型的,所以不是Cocoa框架的對象粉寞,即便CGColorRef是強引用也只能聲明稱assign尼荆。陰影
給shadowOpacity屬性一個大于默認(rèn)值(也就是0)的值,陰影就可以顯示在任意圖層之下唧垦。shadowOpacity是一個必須在0.0(不可見)和1.0(完全不透明)之間的浮點數(shù)捅儒。如果設(shè)置為1.0,將會顯示一個有輕微模糊的黑色陰影稍微在圖層之上振亮。若要改動陰影的表現(xiàn)巧还,你可以使用CALayer的另外三個屬性:shadowColor,shadowOffset和shadowRadius坊秸。另外陰影也會有性能隱患麸祷。蒙版
蒙版的作用就是覆蓋一層Layer,將這個layer的輪廓顯示出來妇斤,填充內(nèi)容是contents的內(nèi)容摇锋。拉伸過濾
最后就是minificationFilter(縮械ふ)和magnificationFilter(放大)屬性。這兩個屬性主要是使用在要展示非圖像原始大小情況下使用荸恕,遵循怎樣的算法進行拉伸和縮放乖酬。組透明
當(dāng)我們給一個layer tree添加透明度屬性的時候,往往是里面的layer的透明度會小一些融求,因為里面的layer是父layer與其透明度的一個混合咬像,所以會出現(xiàn)兩個layer混合之后的效果不是我們想要的效果,這個時候就需要組透明來解決生宛。這里有兩種解決方案县昂,一種是將layer光柵化成一個圖層,這樣的話透明度就一致了陷舅,另一個方法就是通過設(shè)置Info.plist文件中的UIViewGroupOpacity為YES來達到這個效果倒彰,但是會對整個App造成影響。
CGAffineTransform
首先莱睁,所有的變換都是基于CGPoint的待讳,仿射變換之后的矩形兩對邊是相互平行的。
CGTransform3D
3D變換主要是配置一個矩陣來實現(xiàn)每個點的變化仰剿,從而達到整個layer實現(xiàn)3D變換的效果创淡。要想做到滅點相同的話,必須要將layer都放在view的中心南吮,然后在平移到指定的位置琳彩,改變layer的frame的話滅點也會發(fā)生改變。還有一個簡單的辦法就是使用sublayerTransform屬性部凑。我們可以任意的放置子layer的位置露乏,這樣的話也會共享一個滅點。
專用圖層
CAShapeLayer
CAShapeLayer
是一個通過矢量圖而不是通過bitmap來繪制的圖層子類砚尽。指定線寬和顏色等屬性施无,用CGPath來定義想要繪制的圖形辉词,最后CAShapeLayer就會自動的渲染出來必孤。與直接向CALayer繪制路徑相比,CAShapeLayer的優(yōu)點如下:
1瑞躺、渲染快速敷搪。
CAShapeLayer
使用了硬件加速,繪制同一圖形會比用Core Graphics快很多幢哨。
2赡勘、高效使用內(nèi)存。一個CAShapeLayer
不需要像普通layer一樣創(chuàng)建一個contents圖形捞镰,所以無論多大都不會占用過多的內(nèi)存闸与。
3毙替、不會被圖層邊界裁掉。一個CAShapeLayer
可以在邊界之外繪制,不會像普通的layer被裁減掉践樱。
4厂画、不會出現(xiàn)像素化。當(dāng)你給CAShapeLayer做3D變換的時候拷邢,他不像一個contents普通圖層一樣變得像素化袱院。
使用CAShapeLayer主要就是設(shè)置path屬性,path是CGPathRef類型瞭稼,但是為了方便管理內(nèi)存忽洛,使用UIKit框架的UIBezierPath來創(chuàng)建,然后轉(zhuǎn)成CGPathRef類型賦值給path屬性环肘,這樣的話就會渲染出一個path形狀的圖層欲虚。利用之前的蒙版的功能,我們可以直接將contents裁剪成任意形狀悔雹。例如可以讓兩個角圓角苍在,另外兩個角是正常的,這些都是很容易實現(xiàn)的荠商。
CATextLayer
它以圖層的形式包含了UILabel幾乎所有的繪制特性寂恬,另外還提供了一些新的額外的特性。在ios6之前UILabel是用WebKit來實現(xiàn)的莱没,而CATextLayer是用Core Text支持的初肉,所以渲染速度要快很多,但是iOS7之后文字渲染交給了Text Kit來實現(xiàn)饰躲,其底層還是使用Core Text牙咏,所以性能上的差距應(yīng)該沒有這么明顯了(自己認(rèn)為的)。CATextLayer可以使用普通文本和富文本嘹裂。
CATransformLayer
這個屬性沒有自己的contents屬性妄壶,而是管理子layer的Transform變化,主要是為了解決CALayer會將其子layer平面化寄狼,而CATransformLayer不會平面化其子layer丁寄。這樣的話就可以在一個layer里面構(gòu)造兩種不同視角的3D物體。
CAGradientLayer
CAGradientLayer是用來生成兩種或更多顏色平滑漸變的泊愧。用Core Graphics復(fù)制一個CAGradientLayer并將內(nèi)容繪制到一個普通圖層的寄宿圖也是有可能的伊磺,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。
CAReplicatorLayer
其目的是高效生成許多相似的圖層删咱。它會繪制一個或多個圖層的子圖層屑埋,并且在每個復(fù)制體上應(yīng)用不同的變換。使用的時候就是將layer加入到CARelicatorLayer類型的layer中痰滋,然后可以在CARelicatorLayer中設(shè)置復(fù)制的數(shù)量和變換摘能⌒拢可以高效的來做反射的效果。
CAScrollLayer
Core Animation并不處理用戶輸入团搞,所以CAScrollLayer并不負(fù)責(zé)將觸摸事件轉(zhuǎn)換為滑動事件袜刷,既不渲染滾動條,也不實現(xiàn)任何iOS指定行為例如滑動反彈莺丑。用法就是直接重寫+ (Class)layerClass
方法著蟹,將UIView的根layer返回成CAScrollLayer類型,這樣的話其中的子layer都是可以滑動的梢莽。
CATiledLayer
有些時候你可能需要繪制一個很大的圖片萧豆,常見的例子就是一個高像素的照片或者是地球表面的詳細(xì)地圖。iOS應(yīng)用通暢運行在內(nèi)存受限的設(shè)備上昏名,所以讀取整個圖片到內(nèi)存中是不明智的涮雷。載入大圖可能會相當(dāng)?shù)芈切δ憧瓷先ケ容^方便的做法(在主線程調(diào)用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法)將會阻塞你的用戶界面轻局,至少會引起動畫卡頓現(xiàn)象洪鸭。
能高效繪制在iOS上的圖片也有一個大小限制。所有顯示在屏幕上的圖片最終都會被轉(zhuǎn)化為OpenGL紋理仑扑,同時OpenGL有一個最大的紋理尺寸(通常是2048/2048览爵,或4096/4096,這個取決于設(shè)備型號)镇饮。如果你想在單個紋理中顯示一個比這大的圖蜓竹,即便圖片已經(jīng)存在于內(nèi)存中了,你仍然會遇到很大的性能問題储藐,因為Core Animation強制用CPU處理圖片而不是更快的GPU(見第12章『速度的曲調(diào)』俱济,和第13章『高效繪圖』,它更加詳細(xì)地解釋了軟件繪制和硬件繪制)钙勃。
CATiledLayer為載入大圖造成的性能問題提供了一個解決方案:將大圖分解成小片然后將他們單獨按需載入.
CAEmitterLayer
是一個高性能的粒子引擎蛛碌,被用來創(chuàng)建實時例子動畫如:煙霧,火辖源,雨等等這些效果蔚携。
CAEAGLLayer
它是CALayer的一個子類,用來顯示任意的OpenGL圖形同木。
AVPlayerLayer
AVPlayerLayer是有別的框架(AVFoundation)提供的浮梢,它和Core Animation緊密地結(jié)合在一起跛十,提供了一個CALayer子類來顯示自定義的內(nèi)容類型彤路。AVPlayerLayer是用來在iOS上播放視頻的。他是高級接口例如MPMoivePlayer的底層實現(xiàn)芥映,提供了顯示視頻的底層控制洲尊。AVPlayerLayer的使用相當(dāng)簡單:你可以用+playerLayerWithPlayer:方法創(chuàng)建一個已經(jīng)綁定了視頻播放器的圖層远豺,或者你可以先創(chuàng)建一個圖層,然后用player屬性綁定一個AVPlayer實例坞嘀。
動畫
implicit animation
隱式動畫就是系統(tǒng)會自動的給layer做動畫躯护,layer位置、顏色等的改變會引起一個0.25秒的動畫過程丽涩。core animation是根據(jù)什么來判斷動畫類型和時間的那棺滞,看起來是自動的設(shè)置,實際上是有事務(wù)來進行管理的矢渊。
事務(wù)實際上是Core Animation用來包含一系列屬性動畫集合的機制继准,任何用指定事務(wù)去改變可以做動畫的圖層屬性都不會立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時候開始用一個動畫過渡到新值矮男。事務(wù)是通過CATransaction類來做管理移必,這個類的設(shè)計有些奇怪,不像你從它的命名預(yù)期的那樣去管理一個簡單的事務(wù)毡鉴,而是管理了一疊你不能訪問的事務(wù)崔泵。CATransaction沒有屬性或者實例方法,并且也不能用+alloc和-init方法創(chuàng)建它猪瞬。但是可以用+begin和+commit分別來入椖鹧或者出棧权她。即使不顯示的調(diào)用begin方法,在一次runloop循環(huán)中
我們也可以手動的關(guān)閉動畫,layer tree中贝椿,作為UIView的根layer的動畫是被關(guān)上的,并不是通過layer的開關(guān)關(guān)閉的狐血,首先看一下CALayer的代理方法脖镀。
- 圖層首先檢測它是否有委托,并且是否實現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法线婚。如果有遏弱,直接調(diào)用并返回結(jié)果。
- 如果沒有委托塞弊,或者委托沒有實現(xiàn)-actionForLayer:forKey方法漱逸,圖層接著檢查包含屬性名稱對應(yīng)行為映射的actions字典。
- 如果actions字典沒有包含對應(yīng)的屬性游沿,那么圖層接著在它的style字典接著搜索屬性名饰抒。
- 最后,如果在style里面也找不到對應(yīng)的行為诀黍,那么圖層將會直接調(diào)用定義了每個屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法袋坑。
于是這就解釋了UIKit是如何禁用隱式動畫的:每個UIView對它關(guān)聯(lián)的圖層都扮演了一個委托,并且提供了-actionForLayer:forKey的實現(xiàn)方法眯勾。對于改變可動畫的屬性枣宫,當(dāng)其不在一個動畫塊的實現(xiàn)中婆誓,UIView對所有圖層行為返回nil,但是在動畫block范圍之內(nèi)也颤,它就返回了一個非空值洋幻。
explicit aniamtion
顯示動畫是使用CAAnimation做的動畫,我們來看一下CAAniamtion的子類翅娶。
CAPropertyAniamtion(屬性動畫)
CABasicAniamtion
CABasicAnimation是設(shè)置Layer的屬性的fromValue和toValue文留,然后有一個漸變的過程,這個其實是和隱式動畫相似竭沫,只是多了一個設(shè)置fromValue厂庇。CAKeyframeAnimation
CAKeyframeAnimation是另一種UIKit沒有暴露出來但是功能強大的類,與CABasicAniamtion相似输吏,也是ACPropertyAniamtion的子類权旷,也是只能作用于一個屬性,但是和basic不同的是贯溅,它不限制于設(shè)置一個起始和最終值拄氯,而是可以根據(jù)一系列任意的值來做動畫。一種方式是根據(jù)values數(shù)組來給出關(guān)鍵幀它浅,core animation會在這些關(guān)鍵幀之間自動的插入動畫译柏,但是這種方式并不直觀,還有一種方式就是使用CGPath姐霍,這是一種直觀的方式鄙麦,使用CoreGraphics函數(shù)定義運動序列來繪制動畫。虛擬屬性做動畫
虛擬屬性指的是layer沒用暴露的屬性镊折,比如rotation胯府,transfrom等,我們可以指定 keyPath = @“transform.rotation” 來對角度做動畫恨胚。動畫組(CAAnimationGroup)
可以將動畫放到CAAnimationGroup里面骂因,將不同的動畫組合進行
過渡動畫(CATransition)
對于沒有辦法做動畫的屬性,或者交換一段文本和圖片赃泡,或者用一段網(wǎng)格來替換寒波,這個時候?qū)傩詣赢嬍菦]用作用的。屬性動畫只對圖層可動畫屬性起作用升熊,所以如果要改變一個不能動畫的屬性(比如圖片俄烁,因為CoreAnimation不知道什么時候插入圖片),又或者成層級關(guān)系中添加或者移除圖層级野,屬性動畫將不起作用页屠,于是就有了過度的概念。
過度并不像屬性動畫那樣平滑的在兩個值之間做動畫,而是影響到整個圖層的變化卷中。過度動畫首先展示之前的圖層外觀矛双,然后通過一個過度變換到新的外觀渊抽。
我們使用CATransition來管理過度效果蟆豫,和別的子類不同,CATransition有一個type和subType來標(biāo)識變換效果懒闷。type是一個NSString類型十减,提供了四種過度類型,分別是:
kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
過渡動畫和之前的屬性動畫或者動畫組添加到圖層上的方式一致愤估,都是通過-addAnimation:forKey:方法帮辟。但是和屬性動畫不同的是,對指定的圖層一次只能使用一次CATransition玩焰,因此由驹,無論你對動畫的鍵設(shè)置什么值,過渡動畫都會對它的鍵設(shè)置成“transition”昔园,也就是常量kCATransition蔓榄。
CATransision可以對圖層任何變化平滑過渡的事實使得它成為那些不好做動畫的屬性圖層行為的理想候選。蘋果當(dāng)然意識到了這點默刚,并且當(dāng)設(shè)置了CALayer的content屬性的時候甥郑,CATransition的確是默認(rèn)的行為。但是對于視圖關(guān)聯(lián)的圖層荤西,或者是其他隱式動畫的行為澜搅,這個特性依然是被禁用的,但是對于你自己創(chuàng)建的圖層邪锌,這意味著對圖層contents圖片做的改動都會自動附上淡入淡出的動畫勉躺。
圖層樹的動畫
CATransition并不作用于指定的圖層屬性,這就是說你可以在即使不能準(zhǔn)確得知改變了什么的情況下對圖層做動畫觅丰,例如赂蕴,在不知道UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它舶胀,或者在不知道UIViewController內(nèi)部的視圖層級的情況下對兩個不同的實例做過渡動畫概说。因為它們不僅涉及到圖層的屬性,而且是整個圖層樹的改變--我們在這種動畫的過程中手動在層級關(guān)系中添加或者移除圖層嚣伐。要確保CATransition添加到的圖層在過渡動畫發(fā)生時不會在樹狀結(jié)構(gòu)中被移除糖赔,否則CATransition將會和圖層一起被移除。一般來說轩端,你只需要將動畫添加到被影響圖層的superlayer放典。
自定義動畫
蘋果通過UIView +transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:方法提供了Core Animation的過渡特性。但是這里的可用的過渡選項和CATransition的type屬性提供的常量完全不同
動畫的時間、緩沖
model layer & presentation layer
我們直接改變layer的position奋构,可以發(fā)現(xiàn)position屬性會直接發(fā)生變化壳影,但是屏幕上我們看到的layer卻是有一個漸變的動畫,而不是根據(jù)我們設(shè)置的屬性實時的發(fā)生變化弥臼,所以宴咧,詳細(xì)的劃分一下,這里的layer就是model layer径缅,其屬性表示的是動畫結(jié)束之后layer的屬性掺栅,而屏幕上我們直接看到的就是presentation layer,layer實時在屏幕上的位置纳猪。
性能調(diào)優(yōu)
性能陷阱
軟件繪圖不僅效率低氧卧,還會消耗可觀的內(nèi)存。CALayer只需要一些與自己相關(guān)的內(nèi)存:只有它的寄宿圖會消耗一定的內(nèi)存空間氏堤。即使直接賦給contents屬性一張圖片沙绝,也不需要增加額外的照片存儲大小。如果相同的一張圖片被多個圖層作為contents屬性鼠锈,那么他們將會共用同一塊內(nèi)存闪檬,而不是復(fù)制內(nèi)存塊。
但是一旦你實現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實就是前者的包裝方法)脚祟,圖層就創(chuàng)建了一個繪制上下文谬以,這個上下文需要的大小的內(nèi)存可從這個算式得出:圖層寬圖層高4字節(jié),寬高的單位均為像素由桌。對于一個在Retina iPad上的全屏圖層來說为黎,這個內(nèi)存量就是 204815264字節(jié),相當(dāng)于12MB內(nèi)存行您,圖層每次重繪的時候都需要重新抹掉內(nèi)存然后重新分配铭乾。
加載圖片消耗內(nèi)存和占用CPU的原因
一旦圖片文件被加載就必須要進行解碼,解碼過程是一個相當(dāng)復(fù)雜的任務(wù)娃循,需要消耗非常長的時間炕檩。解碼后的圖片將同樣使用相當(dāng)大的內(nèi)存。
用于加載的CPU時間相對于解碼來說根據(jù)圖片格式而不同捌斧。對于PNG圖片來說笛质,加載會比JPEG更長,因為文件可能更大捞蚂,但是解碼會相對較快妇押,而且Xcode會把PNG圖片進行解碼優(yōu)化之后引入工程。JPEG圖片更小姓迅,加載更快敲霍,但是解壓的步驟要消耗更長的時間俊马,因為JPEG解壓算法比基于zip的PNG算法更加復(fù)雜。
圖片加載的性能優(yōu)化(TableView優(yōu)化)
- 在后臺線程中加載圖片肩杈,會有效果柴我,但是并不是性能的瓶頸。
- iOS為了節(jié)省內(nèi)存延遲解壓,解壓會消耗很長的時間扩然,這里帶來的問題就是在使用的時候進行解壓艘儒,就會造成界面的卡頓[UIImage imageWithContentsOfFile:]會延遲圖片的解壓,[UIimage imageName:]會立即解壓圖片与学,另外兩種方式也會直接解壓圖片彤悔,一種是作為layer的contents屬性的時候嘉抓,還有一種就是作為UIImageView的image屬性的時候索守,但是這兩種方法必須在主線程中才有效。第四種方式是不用UIKit框架抑片,直接使用ImageIO框架來實現(xiàn):
NSInteger index = indexPath.row;
NSURL *imageURL = [NSURL fileURLWithPath:self.imagePaths[index]];
NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES};
CGImageSourceRef source = CGImageSourceCreateWithURL(
(__bridge CFURLRef)imageURL, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,
(__bridge CFDictionaryRef)options);
UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef);
CFRelease(source);
這里還有第五種方式依然使用UIKit框架來實現(xiàn)立即解壓圖片卵佛,以為繪制圖片之前會解壓圖片,那么就直接將圖片畫到CGContext里面敞斋,從而實現(xiàn)立即解壓截汪,這有個好處就是不是必須在主線程中實現(xiàn)
- 中間插一個離屏渲染導(dǎo)致的性能問題,前面有說過設(shè)置Layer的conerRadius屬性會觸發(fā)離屏渲染植捎,導(dǎo)致內(nèi)存增加衙解。另外maskToBounds屬性是結(jié)合conerRadius使用的,所以這個屬性也是需要注意的焰枢。
離屏渲染影響性能的原因:「直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū)蚓峦,然后渲染到紋理中,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價很多济锄。因為這其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)暑椰,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))〖鼍」觸發(fā)離屏渲染后這種轉(zhuǎn)換發(fā)生在每一幀一汽,在界面的滾動過程中如果有大量的離屏渲染發(fā)生時會嚴(yán)重影響幀率。
使用圖片的大小最好是直接適配最終的大小低滩,這樣的話imageView不用再調(diào)整大小之后再使用召夹。
緩存圖片也是可以實現(xiàn)優(yōu)化,[UIImage imageNamed:]方法會直接緩存之后的圖片恕沫,但是我們并不能手動控制监憎,而且也不知道什么時候圖片是被緩存的,那么就要使用NSCache來實現(xiàn)緩存昏兆,并且緩存的是解壓之后的圖片枫虏,使用UIKit的話就是要在CGContext里面實現(xiàn)妇穴。這里是處理好的例子:
#import "ViewController.h"
@interface ViewController()
@property (nonatomic, copy) NSArray *imagePaths;
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad
{
//set up data
self.imagePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"Vacation Photos"];
//register cell class
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imagePaths count];
}
- (UIImage *)loadImageAtIndex:(NSUInteger)index
{
//set up cache
static NSCache *cache = nil;
if (!cache) {
cache = [[NSCache alloc] init];
}
//if already cached, return immediately
UIImage *image = [cache objectForKey:@(index)];
if (image) {
return [image isKindOfClass:[NSNull class]]? nil: image;
}
//set placeholder to avoid reloading image multiple times
[cache setObject:[NSNull null] forKey:@(index)];
//switch to background thread
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//load image
NSString *imagePath = self.imagePaths[index];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
//redraw image using device context
UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//set image for correct image view
dispatch_async(dispatch_get_main_queue(), ^{ //cache the image
[cache setObject:image forKey:@(index)];
//display the image
NSIndexPath *indexPath = [NSIndexPath indexPathForItem: index inSection:0]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
UIImageView *imageView = [cell.contentView.subviews lastObject];
imageView.image = image;
});
});
//not loaded yet
return nil;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
//add image view
UIImageView *imageView = [cell.contentView.subviews lastObject];
if (!imageView) {
imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:imageView];
}
//set or load image for this index
imageView.image = [self loadImageAtIndex:indexPath.item];
//preload image for previous and next index
if (indexPath.item < [self.imagePaths count] - 1) {
[self loadImageAtIndex:indexPath.item + 1]; }
if (indexPath.item > 0) {
[self loadImageAtIndex:indexPath.item - 1]; }
return cell;
}
@end