iOS性能優化(中級)

欲知前事如何,且看上回分解: iOS性能優化(初級) git

小試牛刀

經過對性能初級優化祕籍一段時間的練習,少俠應該對性能優化有了必定的瞭解,在平常開發編碼中有了些性能優化的意識,當產品小師妹提出一個新的交互的時候,想必也定難不倒少俠了。github

就列表來講,icon、大標題、小標題、內容,通常APP的不少時候就是這幾個元素,排版不一樣,細緻效果不一樣。這些對於少俠來講都已經不是問題了,無聲無息中APP已經如絲般順滑。看着產品小師妹那敬仰的眼神,牛心潮澎湃,花前月下,海誓山盟立刻就要脫口而出。緩存

折戟沉沙

但,江湖風雲變幻,折戟沉沙你早有準備。性能優化

只是沒想到那一天來的這麼快。bash

那一天產品小師妹提出了一個新的需求,除了以前的icon、大標題、小標題以外,如今要加上標籤,標籤有多個用於各類活動運營,標籤的位置要根據標題內容的位置來定,標籤要作成圓角加邊框,同時列表每一行的高度要根據各項內容來最終肯定,內容多就高,內容少就矮,還有icon要圓形加邊框順便帶點陰影,巴拉巴拉巴拉巴拉巴拉巴拉。
產品小師妹一口氣說了不少,說的你眼冒金星,氣息紊亂,差點走火入魔,口吐鮮血,但看着小師妹那一如既往的欣喜加期待的眼神,只好暗暗運力,穩住陣腳,一口答應小師妹的需求。
伊人遠去,看着小師妹遠去的身影,你瘋狂編碼,但總有那麼一個點沒法突破,流暢性始終沒法達到要求,不由陷入了沉思。異步

臥薪嚐膽

初級性能優化祕籍,只能應對初級的性能優化問題。但當前的需求,效果多,子視圖多,排版更新頻繁,高度每行不同。初級祕籍已經不能很好的湊效,這可如何是好。async

少俠莫慌,老夫看你已經熟練了初級性能優化祕籍,基礎已經打牢,如今就將性能優化中級祕籍傳授與你罷。post

工欲善其事必先利其器,想要打敗對手,你要有趁手的兵器。性能

在APP裏直接的觀察看FPS數據:測試

KMCGeigerCounter 也能夠根據 CADisplayLink 本身寫一個簡易好用的,CADisplayLink 是一個定時器,並且這個定時器的調用頻率跟屏幕刷新頻率相同。

頂級法寶,當屬 Instrument:

若想熟練使用此項法寶,需注意兩個地方

  1. 用release模式,貼近最真是的使用環境,才能得到最準確的數據。
  2. 用真機測試,模擬器再厲害也仍是在模擬,祭出不一樣型號的真機,才能針對優化。

打開方式: Xcode -> Product -> Profile -> Core Animation 配合TimeProfile 一塊兒使用 查看FPS的同時,還能查看到哪些操做比較耗時,有此傍身,再厲害的敵人也會露出破綻。

查看FPS

百步穿楊

性能優化的步驟:
修改 -> Instrument查看 -> 修改 -> Instrument查看 —> 修改.....
重複以上動做直到性能達到要求

CPU的耗時操做能夠在Instrument裏查看到,並定位修改優化,但GPU的優化要怎麼進行呢?

XCode9以後能夠Xcode -> Debug > View Debugging > Rendering 下看到優化的各個選項,模擬器時沒法勾選,只有真機的狀況下才能勾選。

查看優化選項

  • Color Blended Layers — 出現圖層混合的地方會標註爲紅色,沒有圖層混合的地方會顯示爲綠色,方向是紅色越少越好,綠色越多越好。

圖層混合

  • Color Hits Green and Misses Red — 當使用光柵化渲染(shouldRasterize)的時候,若是圖層是綠色,表示這些緩存被複用,若是圖層是紅色表示緩存沒有被複用會重複建立,這時候會形成性能問題。

光柵化

  • Color Copied Images — 若是GPU不支持當前圖片格式,那麼圖片會交給CPU進行預先處理,這張圖片會顯示爲藍色。

  • Color Misaligned Images — 檢測圖片是否被拉伸,當圖片色實際大小跟ImageView的大小不相同時,就會發生,顯示爲黃色,這種操做會比較消耗CPU資源。

  • Color Offscreen-rendered Yellow — GPU的渲染有兩種,On-screen Rendering當前屏幕渲染,是指GPU的渲染在當前屏幕的緩衝區內進行。off-screen Rendering是指在GPU的渲染髮生在當前屏幕以外新開闢的緩衝區。開闢新的緩衝區,切換緩衝區等會對性能有較大的影響。

觸發離屏渲染有如下幾種行爲:

  1. cornerRadius以及masksToBounds同時使用時會觸發離屏渲染,單獨使用時不會觸發。
  2. 設置shadow,並且shodowPath = nil時會觸發。
  3. mask 設置蒙版會觸發。
  4. layer.shouldRasterize的不適當使用會觸發離屏渲染。
  5. layer.allowsGroupOpacity iOS7之後默認開啓;當layer.opacity != 1.0且有subLayer或者背景圖時會觸發。
  6. layer.allowsEdgeAntialiasing 在iOS8之後的系統裏可能已經作了優化,並不會觸發離屏渲染,不會對性能形成影響。
  7. 重寫了drawRect。

少俠熟讀了以上招式,便能快速找出對手的破綻。

無堅不摧

找出了敵人的破綻,少俠還要制定詳細的應對策略,瞅準時機,方能一招制敵。

老夫這就給你展現制敵之道:

  • Color Blended Layers:

    • UIView的backgroundColor不要設置爲clearColor,最好設置的和superView的backgroundColor顏色同樣。
    • 圖片避免使用帶alpha通道的圖片,不管是本地圖片仍是後臺返回圖片。什麼,設計妹子不一樣意,少俠這就要靠你的魅力啦。
  • Color Hits Green and Misses Red: 在初級性能優化中,適當使用shouldRasterize中有詳細講解。

  • Color Copied Images: 開發過程當中注意圖片格式

  • Color Misaligned Images: 儘可能把圖片大小設置的和UIImageView相同大小。

  • Color Offscreen-rendered Yellow: 這是性能優化的要點,針對引發離屏渲染的各類狀況須要逐一應對

重點來了,離屏渲染的優化招式,少俠看仔細了

  • 設置圓角cornerRadius:

UIView: 若是view.layer.contents 爲空,直接經過設置view.layer.cornerRadius 以及 view.backgroundColor或者view.layer.border便可設置圓角,不須要設置masksToBounds爲YES,此時不會產生離屏渲染。

//設置圓角邊框
view.layer.cornerRadius = 3.0
view.layer.borderColor =  UIColor.red.cgColor
view.layer.borderWidth = 1.0
// 設置帶背景
view.layer.cornerRadius = 3.0
view.backgroundColor = UIColor.green
//或者相同效果的
view.layer.backgroundColor = UIColor.green.cgColor
複製代碼

UILabel: 設置和UIView差很少,有一個區別就是設置label.backgroundColor和layer.cornerRadius不會起效果,須要設置label.layer.backgroundColor和layer.cornerRadius纔會起效果。

//設置圓角邊框
view.layer.cornerRadius = 3.0
view.layer.borderColor =  UIColor.red.cgColor
view.layer.borderWidth = 1.0
複製代碼

UITextField: 自帶圓角效果,設置不一樣style便可達到效果。

UITextView: 和UIView的設置方法相同。

UIImageView: UIImageView的狀況比較特殊,上面的幾種方法不能實現圓角,必需要layer.cornerRadius和layer.masksToBounds = YES,才能實現圓角。但這個操做一定會產生離屏渲染,爲了不離屏渲染,經常使用的優化方法有:

  • 重繪圖片,生成一張帶圓角的圖片,而後設置到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 {
     	    //在子線程調用redrawImage生成圖片
     	    DispatchQueue.main.async {
     	        //在主線程設置圖片
     	    }
     	}
    複製代碼
  • 在UIImageView上遮蓋一張部分透明的,部分遮擋的圖片,蓋在原來的UIImageView上,曲線實現圖片圓角功能。

    //生成中間透明 周圍遮擋的圖片
    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
    }
      
    //將生成的圖片 加到須要圓角的圖片的上方
    複製代碼
  • 設置陰影shadow:

    設置shadowPath,能夠解決離屏渲染問題。

    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
    複製代碼

    固然和圓角的解決辦法同樣,可使用一張帶陰影的圖來曲線解決問題。

    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
    }  
    複製代碼
  • 設置蒙版mask:

    設置mask一定會觸發離屏渲染。
    mask的過程大體來看是和視圖混合相反的過程,例若有一張圖片,中間有一個圓形空間是透明的,邊緣部分是白色,若是視圖直接疊加在一張頭像上,會呈現出圓形頭型的效果,但若是使用mask則會顯示出中間白邊緣透明的效果。
    因此性能敏感的界面中,能夠不使用mask,而使用視圖混合這種對性能影響更小的方式進行操做。

  • layer.allowsGroupOpacity、layer.allowsEdgeAntialiasing:

    這兩個操做對性能並不會形成比較大的影響。

  • drawRect:

    drawRect會形成較大的內存消耗,並會形成離屏渲染,應儘可能避免重寫。

爐火純青

以上招式,少俠可看好了,往後定當好好練習,得到伊人芳心指日可待。

快去找小師妹去罷。

欲知後事如何,且看下回分解: iOS性能優化(中級+): 異步繪製

相關文章
相關標籤/搜索