欲知前事如何弟蚀,且看上回分解: iOS性能優(yōu)化(初級(jí))
小試牛刀
通過(guò)對(duì)性能初級(jí)優(yōu)化秘籍一段時(shí)間的練習(xí),少俠應(yīng)該對(duì)性能優(yōu)化有了一定的了解,在日常開(kāi)發(fā)編碼中有了些性能優(yōu)化的意識(shí)排苍,當(dāng)產(chǎn)品小師妹提出一個(gè)新的交互的時(shí)候枝笨,想必也定難不倒少俠了袁铐。
就列表來(lái)說(shuō)揭蜒,icon、大標(biāo)題剔桨、小標(biāo)題屉更、內(nèi)容,一般APP的很多時(shí)候就是這幾個(gè)元素洒缀,排版不同瑰谜,細(xì)致效果不同。這些對(duì)于少俠來(lái)說(shuō)都已經(jīng)不是問(wèn)題了树绩,無(wú)聲無(wú)息中APP已經(jīng)如絲般順滑萨脑。看著產(chǎn)品小師妹那敬仰的眼神饺饭,牛心潮澎湃渤早,花前月下,海誓山盟馬上就要脫口而出瘫俊。
折戟沉沙
但鹊杖,江湖風(fēng)云變幻,折戟沉沙你早有準(zhǔn)備扛芽。
只是沒(méi)想到那一天來(lái)的這么快骂蓖。
那一天產(chǎn)品小師妹提出了一個(gè)新的需求,除了之前的icon川尖、大標(biāo)題登下、小標(biāo)題之外,現(xiàn)在要加上標(biāo)簽空厌,標(biāo)簽有多個(gè)用于各種活動(dòng)運(yùn)營(yíng)庐船,標(biāo)簽的位置要根據(jù)標(biāo)題內(nèi)容的位置來(lái)定,標(biāo)簽要做成圓角加邊框嘲更,同時(shí)列表每一行的高度要根據(jù)各項(xiàng)內(nèi)容來(lái)最終確定筐钟,內(nèi)容多就高,內(nèi)容少就矮赋朦,還有icon要圓形加邊框順便帶點(diǎn)陰影篓冲,巴拉巴拉巴拉巴拉巴拉巴拉。
產(chǎn)品小師妹一口氣說(shuō)了很多宠哄,說(shuō)的你眼冒金星壹将,氣息紊亂,差點(diǎn)走火入魔毛嫉,口吐鮮血诽俯,但看著小師妹那一如既往的欣喜加期待的眼神,只好暗暗運(yùn)力承粤,穩(wěn)住陣腳暴区,一口答應(yīng)小師妹的需求闯团。
伊人遠(yuǎn)去,看著小師妹遠(yuǎn)去的身影仙粱,你瘋狂編碼房交,但總有那么一個(gè)點(diǎn)無(wú)法突破,流暢性始終無(wú)法達(dá)到要求伐割,不禁陷入了沉思候味。
臥薪嘗膽
初級(jí)性能優(yōu)化秘籍,只能應(yīng)對(duì)初級(jí)的性能優(yōu)化問(wèn)題隔心。但當(dāng)前的需求白群,效果多,子視圖多济炎,排版更新頻繁川抡,高度每行不一樣辐真。初級(jí)秘籍已經(jīng)不能很好的湊效须尚,這可如何是好。
少俠莫慌侍咱,老夫看你已經(jīng)熟練了初級(jí)性能優(yōu)化秘籍耐床,基礎(chǔ)已經(jīng)打牢,現(xiàn)在就將性能優(yōu)化中級(jí)秘籍傳授與你罷楔脯。
工欲善其事必先利其器撩轰,想要戰(zhàn)勝對(duì)手,你要有趁手的兵器昧廷。
在APP里直接的觀察看FPS數(shù)據(jù):
KMCGeigerCounter
也可以根據(jù) CADisplayLink 自己寫一個(gè)簡(jiǎn)易好用的堪嫂,CADisplayLink 是一個(gè)定時(shí)器,而且這個(gè)定時(shí)器的調(diào)用頻率跟屏幕刷新頻率相同木柬。
頂級(jí)法寶皆串,當(dāng)屬 Instrument:
若想熟練使用此項(xiàng)法寶,需注意兩個(gè)地方
- 用release模式眉枕,貼近最真是的使用環(huán)境恶复,才能獲得最準(zhǔn)確的數(shù)據(jù)。
- 用真機(jī)測(cè)試速挑,模擬器再厲害也還是在模擬谤牡,祭出不同型號(hào)的真機(jī),才能針對(duì)優(yōu)化姥宝。
打開(kāi)方式: Xcode -> Product -> Profile -> Core Animation 配合TimeProfile 一起使用
查看FPS的同時(shí)翅萤,還能查看到哪些操作比較耗時(shí),有此傍身腊满,再厲害的敵人也會(huì)露出破綻套么。
百步穿楊
性能優(yōu)化的步驟:
修改 -> Instrument查看 -> 修改 -> Instrument查看 —> 修改.....
重復(fù)以上動(dòng)作直到性能達(dá)到要求
CPU的耗時(shí)操作可以在Instrument里查看到流纹,并定位修改優(yōu)化,但GPU的優(yōu)化要怎么進(jìn)行呢违诗?
XCode9之后可以Xcode -> Debug > View Debugging > Rendering 下看到優(yōu)化的各個(gè)選項(xiàng)漱凝,模擬器時(shí)無(wú)法勾選,只有真機(jī)的情況下才能勾選诸迟。
- Color Blended Layers — 出現(xiàn)圖層混合的地方會(huì)標(biāo)注為紅色茸炒,沒(méi)有圖層混合的地方會(huì)顯示為綠色,方向是紅色越少越好阵苇,綠色越多越好壁公。
- Color Hits Green and Misses Red — 當(dāng)使用光柵化渲染(shouldRasterize)的時(shí)候,如果圖層是綠色绅项,表示這些緩存被復(fù)用紊册,如果圖層是紅色表示緩存沒(méi)有被復(fù)用會(huì)重復(fù)創(chuàng)建,這時(shí)候會(huì)造成性能問(wèn)題快耿。
Color Copied Images — 如果GPU不支持當(dāng)前圖片格式囊陡,那么圖片會(huì)交給CPU進(jìn)行預(yù)先處理,這張圖片會(huì)顯示為藍(lán)色掀亥。
Color Misaligned Images — 檢測(cè)圖片是否被拉伸撞反,當(dāng)圖片色實(shí)際大小跟ImageView的大小不相同時(shí),就會(huì)發(fā)生搪花,顯示為黃色遏片,這種操作會(huì)比較消耗CPU資源。
Color Offscreen-rendered Yellow — GPU的渲染有兩種撮竿,On-screen Rendering當(dāng)前屏幕渲染吮便,是指GPU的渲染在當(dāng)前屏幕的緩沖區(qū)內(nèi)進(jìn)行。off-screen Rendering是指在GPU的渲染發(fā)生在當(dāng)前屏幕之外新開(kāi)辟的緩沖區(qū)幢踏。開(kāi)辟新的緩沖區(qū)髓需,切換緩沖區(qū)等會(huì)對(duì)性能有較大的影響。
觸發(fā)離屏渲染有以下幾種行為:
- cornerRadius以及masksToBounds同時(shí)使用時(shí)會(huì)觸發(fā)離屏渲染惑折,單獨(dú)使用時(shí)不會(huì)觸發(fā)授账。
- 設(shè)置shadow,而且shodowPath = nil時(shí)會(huì)觸發(fā)惨驶。
- mask 設(shè)置蒙版會(huì)觸發(fā)白热。
- layer.shouldRasterize的不適當(dāng)使用會(huì)觸發(fā)離屏渲染。
- layer.allowsGroupOpacity iOS7以后默認(rèn)開(kāi)啟粗卜;當(dāng)layer.opacity != 1.0且有subLayer或者背景圖時(shí)會(huì)觸發(fā)屋确。
- layer.allowsEdgeAntialiasing 在iOS8以后的系統(tǒng)里可能已經(jīng)做了優(yōu)化,并不會(huì)觸發(fā)離屏渲染,不會(huì)對(duì)性能造成影響攻臀。
- 重寫了drawRect焕数。
少俠熟讀了以上招式,便能快速找出對(duì)手的破綻刨啸。
無(wú)堅(jiān)不摧
找出了敵人的破綻堡赔,少俠還要制定詳細(xì)的應(yīng)對(duì)策略,瞅準(zhǔn)時(shí)機(jī)设联,方能一招制敵善已。
老夫這就給你展示制敵之道:
-
Color Blended Layers:
- UIView的backgroundColor不要設(shè)置為clearColor,最好設(shè)置的和superView的backgroundColor顏色一樣离例。
- 圖片避免使用帶alpha通道的圖片换团,無(wú)論是本地圖片還是后臺(tái)返回圖片。什么宫蛆,設(shè)計(jì)妹子不同意艘包,少俠這就要靠你的魅力啦。
Color Hits Green and Misses Red: 在初級(jí)性能優(yōu)化中耀盗,適當(dāng)使用shouldRasterize中有詳細(xì)講解想虎。
Color Copied Images: 開(kāi)發(fā)過(guò)程中注意圖片格式
Color Misaligned Images: 盡量把圖片大小設(shè)置的和UIImageView相同大小。
Color Offscreen-rendered Yellow: 這是性能優(yōu)化的要點(diǎn)袍冷,針對(duì)引起離屏渲染的各種情況需要逐一應(yīng)對(duì)
重點(diǎn)來(lái)了磷醋,離屏渲染的優(yōu)化招式,少俠看仔細(xì)了
- 設(shè)置圓角cornerRadius:
UIView: 如果view.layer.contents 為空胡诗,直接通過(guò)設(shè)置view.layer.cornerRadius 以及 view.backgroundColor或者view.layer.border即可設(shè)置圓角,不需要設(shè)置masksToBounds為YES淌友,此時(shí)不會(huì)產(chǎn)生離屏渲染煌恢。
//設(shè)置圓角邊框
view.layer.cornerRadius = 3.0
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 1.0
// 設(shè)置帶背景
view.layer.cornerRadius = 3.0
view.backgroundColor = UIColor.green
//或者相同效果的
view.layer.backgroundColor = UIColor.green.cgColor
UILabel: 設(shè)置和UIView差不多,有一個(gè)區(qū)別就是設(shè)置label.backgroundColor和layer.cornerRadius不會(huì)起效果震庭,需要設(shè)置label.layer.backgroundColor和layer.cornerRadius才會(huì)起效果瑰抵。
//設(shè)置圓角邊框
view.layer.cornerRadius = 3.0
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 1.0
UITextField: 自帶圓角效果,設(shè)置不同style即可達(dá)到效果器联。
UITextView: 和UIView的設(shè)置方法相同二汛。
UIImageView: UIImageView的情況比較特殊,上面的幾種方法不能實(shí)現(xiàn)圓角拨拓,必須要layer.cornerRadius和layer.masksToBounds = YES肴颊,才能實(shí)現(xiàn)圓角。但這個(gè)操作必定會(huì)產(chǎn)生離屏渲染渣磷,為了避免離屏渲染婿着,常用的優(yōu)化方法有:
-
重繪圖片,生成一張帶圓角的圖片,然后設(shè)置到UIImageView上竟宋。
func redrawImage(originImage: UIImage, rectSize: CGSize, cornerRadius: CGFloat) -> UIImage? { UIGraphicsBeginImageContextWithOptions(rectSize, false, UIScreen.main.scale) if let context = UIGraphicsGetCurrentContext() { let rect = CGRect(origin: CGPoint.zero, size: rectSize) let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) context.addPath(path.cgPath) context.clip() originImage.draw(in: rect) context.drawPath(using: .fillStroke) let roundedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return roundedImage } return nil } DispatchQueue.global(qos: .default).async { //在子線程調(diào)用redrawImage生成圖片 DispatchQueue.main.async { //在主線程設(shè)置圖片 } }
-
在UIImageView上遮蓋一張部分透明的提完,部分遮擋的圖片,蓋在原來(lái)的UIImageView上丘侠,曲線實(shí)現(xiàn)圖片圓角功能徒欣。
//生成中間透明 周圍遮擋的圖片 func getRundedCornerImage(radius: CGFloat, rectSize: CGSize, fillColor: UIColor) -> UIImage? { UIGraphicsBeginImageContextWithOptions(rectSize, false, UIScreen.main.scale) if let currentContext = UIGraphicsGetCurrentContext() { let rect = CGRect(origin: .zero, size: rectSize) let outerPath = UIBezierPath(rect: rect) let innerPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius)) currentContext.setBlendMode(.normal) fillColor.setFill() outerPath.fill() currentContext.setBlendMode(.normal) innerPath.fill() let roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return roundedCornerImage } return nil } //將生成的圖片 加到需要圓角的圖片的上方
-
設(shè)置陰影shadow:
設(shè)置shadowPath,可以解決離屏渲染問(wèn)題蜗字。
self.shadowView.layer.shadowColor = UIColor.gray.cgColor self.shadowView.layer.shadowOpacity = 0.2 self.shadowView.layer.shadowRadius = 3.0 self.shadowView.layer.shadowOffset = CGSize(width: 1, height: 1) self.shadowView.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPath
當(dāng)然和圓角的解決辦法一樣帚称,可以使用一張帶陰影的圖來(lái)曲線解決問(wèn)題。
func getRundedCornerShadowImage(originImage: UIImage, rectSize: CGSize, roundedRadius: CGFloat, shadowColor: UIColor, shadowOffset: CGSize, insetX: CGFloat, insetY: CGFloat) -> UIImage? { UIGraphicsBeginImageContextWithOptions(rectSize, false, UIScreen.main.scale) if let currentContext = UIGraphicsGetCurrentContext() { let rect = CGRect(origin: .zero, size: rectSize) let shadowPath = UIBezierPath(roundedRect: rect.insetBy(dx: insetX, dy: insetY), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: roundedRadius, height: roundedRadius)) currentContext.setShadow(offset: shadowOffset, blur: roundedRadius, color: shadowColor.cgColor) currentContext.addPath(shadowPath.cgPath) shadowPath.fill() let imagePath = UIBezierPath(roundedRect: rect.insetBy(dx: insetX, dy: insetY), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: roundedRadius, height: roundedRadius)) currentContext.addPath(imagePath.cgPath) currentContext.clip() originImage.draw(in: rect.insetBy(dx: insetX, dy: insetY)) currentContext.strokePath() let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } return nil }
-
設(shè)置蒙版mask:
設(shè)置mask必定會(huì)觸發(fā)離屏渲染秽澳。
mask的過(guò)程大致來(lái)看是和視圖混合相反的過(guò)程闯睹,例如有一張圖片,中間有一個(gè)圓形空間是透明的担神,邊緣部分是白色楼吃,如果視圖直接疊加在一張頭像上,會(huì)呈現(xiàn)出圓形頭型的效果妄讯,但如果使用mask則會(huì)顯示出中間白邊緣透明的效果孩锡。
所以性能敏感的界面中,可以不使用mask亥贸,而使用視圖混合這種對(duì)性能影響更小的方式進(jìn)行操作躬窜。
-
layer.allowsGroupOpacity、layer.allowsEdgeAntialiasing:
這兩個(gè)操作對(duì)性能并不會(huì)造成比較大的影響炕置。
-
drawRect:
drawRect會(huì)造成較大的內(nèi)存消耗荣挨,并會(huì)造成離屏渲染,應(yīng)盡量避免重寫朴摊。
爐火純青
以上招式默垄,少俠可看好了,日后定當(dāng)好好練習(xí)甚纲,獲得伊人芳心指日可待口锭。
快去找小師妹去罷。
欲知后事如何介杆,且看下回分解: iOS性能優(yōu)化(中級(jí)+): 異步繪制