Instruments 之 Energy Log

對於生活離不開手機的我們來說,手機的電量就是一條重要的生命線,一般來說,當電量低於 20% 的時候,我們的心總是那麼揪着。作爲一個開發者來說,我們應該爲用戶的手機省電,讓用戶有限的電量能夠更長時間的使用我們開發的 APP,對用戶,對我們開發者來說是兩全其美的方案。所以 APP 的電量消耗也應該是性能優化的點。

案例

還是以 raywenderlich 的 Catstagram APP 作爲分析案例。該案例是一個帶有圖片的列表。

案例截圖


值的注意的是在我的開發環境下 Energy 需要運行在真機設備上,我的開發環境是 Xcode 8.3.2 , iPhone 6 (10.3.1)。

 

使用 Energy Impact

Energy Impact 是 Xcode 自帶的一個用於查看設備電量開銷概況的工具。

Energy Impact 圖

 

如上圖所示,點擊 Xcode 左邊的 Energy Impact 欄目就可以看到設備上正在運行的 APP 的電量消耗水平。

i指標

看圖左邊有 CPU ,Network , Location , GPU, Background 五個指標,這 5 個 指標也是能耗大戶,右邊的表格中的若是被灰色填充,那麼就意味着在那個時刻,該指標是活躍的。比如圖上所示 CPU 和 Network 一直都是被灰色填充,那麼就意味着 CPU 和 Network 一直處於活躍狀態。頂部有藍色和紅色的柱形圖,紅色是Overhead指標,表示除這個 APP 外,系統的其他電量消耗,藍色是Cost,表示這個 APP 的電量消耗。關於更多的 Energy Impact 信息可以參考 Apple 的官方文檔 https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/MonitorEnergyWithXcode.html 。這裏就不再累贅。

高能耗

APP 運行一段時間,滑動幾次列表之後, APP 的能耗就變的非常高,從圖中就可以看出,APP 在靜止狀態的電量高消耗情況肯定是不正常的,Energy Impact 只能看出是否有問題,而不能指出哪裏可能有問題。那麼這個時候就要祭出 Instruments 利器了。

Instruments 之 Energy

Command + I 運行 Instruments 選擇 Energy Log 模板。

選擇 Energy Log 模板

Energy Log 指標

 

看左邊的 Energy Log 的指標有 Energy,CPU,Network等等應有盡有。

點擊開始按鈕,錄製 APP 運行情況

 

APP 運行情況

從圖中可以看出整個 APP 的能量消耗情況,但是存在一個問題,這個問題就是我們已經知道了APP 的這些能量消耗情況,但是怎麼知道要去修改哪裏的代碼呢?這個時候我們需要 Time Profiler 工具。

添加 Time Profiler 工具

如上圖所示,我們添加了 Time Profiler 工具用來記錄 APP 在某個時間段的代碼運行情況。

萬事具備之後,我們重新開始錄製 APP 的運行情況。

APP 的運行情況

Timer Profiler

Energy Log 結合 Timer Profiler 的使用,避免干擾我們隱藏系統庫內容,顯示我們的代碼調用。

image.png

 

按照代碼執行時間的權重比,找到了 CatPhotoTableViewCell 的 panImage(with yRotation: CGFloat) 方法。通過代碼追溯,我們找到了 CatFeedViewController.swift 文件的 viewDidLoad() 方法,找到了 panImage(with yRotation: CGFloat) 方法被頻繁調用的地方

 

 motionManager.startDeviceMotionUpdates(to: .main, withHandler:{ deviceMotion, error in
            guard let deviceMotion = deviceMotion else { return }
            
            self.lastY = deviceMotion.rotationRate.y
            
            let xRotationRate = CGFloat(deviceMotion.rotationRate.x)
            let yRotationRate = CGFloat(deviceMotion.rotationRate.y)
            let zRotationRate = CGFloat(deviceMotion.rotationRate.z)
            
            print("y \(yRotationRate) and x \(xRotationRate) and z\(zRotationRate)")
            
            if abs(yRotationRate) > (abs(xRotationRate) + abs(zRotationRate)) {
                for cell in self.tableView.visibleCells as! [CatPhotoTableViewCell] {
                    cell.panImage(with: yRotationRate)
                }
            }
        })

這段代碼的關鍵在於 self.lastY = deviceMotion.rotationRate.y 這個語句,無論 deviceMotion.rotationRate.y 變化多大,都執行後面的代碼,正常應該是 deviceMotion.rotationRate.y 的變化範圍超過多少的時候才執行後面的代碼,所以優化如下

 

  motionManager.startDeviceMotionUpdates(to: .main, withHandler:{ deviceMotion, error in
            guard let deviceMotion = deviceMotion else { return }
            guard abs(self.lastY - deviceMotion.rotationRate.y) > 0.1 else { return }
            
            let xRotationRate = CGFloat(deviceMotion.rotationRate.x)
            let yRotationRate = CGFloat(deviceMotion.rotationRate.y)
            let zRotationRate = CGFloat(deviceMotion.rotationRate.z)
            
            print("y \(yRotationRate) and x \(xRotationRate) and z\(zRotationRate)")
            
            if abs(yRotationRate) > (abs(xRotationRate) + abs(zRotationRate)) {
                for cell in self.tableView.visibleCells as! [CatPhotoTableViewCell] {
                    cell.panImage(with: yRotationRate)
                }
            }
        })

修改 self.lastY = deviceMotion.rotationRate.y 的邏輯爲 guard abs(self.lastY - deviceMotion.rotationRate.y) > 0.1 else { return }
。當 deviceMotion.rotationRate.y 變化範圍超過 0.1 的時候才執行後面的代碼邏輯。修改完代碼之後進行驗證修改效果。

驗證修改

使用 Energy Impact 進行驗證之後,發現能耗還是非常高,降不下來。那麼接下來就繼續使用 Instruments 查找原因。

使用 Time Profiler

發現 CatFeedViewController 的 sendLogs() 也是佔用了大量的 CPU 時間,接下來使用 Xcode 查看代碼。通過代碼追溯,找到了 CatFeedViewController 的init() 方法。

 

 init() {
        super.init(nibName: nil, bundle: nil)
        navigationItem.title = "Catstagram"
        
        tableView.autoresizingMask = UIViewAutoresizing.flexibleWidth;
        tableView.delegate = self
        tableView.dataSource = self
        
        let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(CatFeedViewController.sendLogs), userInfo: nil, repeats: true)
        RunLoop.main.add(timer, forMode: .commonModes)
    }

在這個init() 方法裏面發現了一個驚人的代碼,有一個定時器每隔 1 s 發起一次 sendlog 的網絡請求。不用懷疑了,肯定就是這個坑爹的代碼消耗了大量的電量。正常的發送 log 操作應該是在 APP 啓動完成的時候發送上次的 log 或者在 APP 進入 applicationWillResignActive 的回調方法發送 log。我們的修改方案是在 APP 進入 applicationWillResignActive 的回調方法發送 log。打開 AppDelegate.swift 文件,添加如下代碼同時刪除 CatFeedViewController 的init() 方法裏面的 sendlog 定時器。

 

func applicationWillResignActive(_ application: UIApplication) {
        rootVC.sendLogs()
    }

接下來就是驗證修改效果了,使用 Energy Impact 這個工具來驗證,對於 驗證 APP 的能耗概況來說, Energy Impact 工具足以滿足需求。

低能耗

 

現在 APP 的能耗是處於低水平,並且 Network 斌不是一直處於活躍狀態。

暫時高能耗

將 APP 退到後臺,再進入前臺,觸發 APP 的 sendlog 操作。這個時候 APP 的能耗進入高等級,但是隨後下降到低等級能耗。這個是 APP 的正常表現。

總結

APP 性能優化中,能耗優化決定了用戶在同樣的電量消耗情況下能使用你的 APP 多長時間。能耗優化的一般步驟如下
1、使用 Energy Impact 查看 APP 能耗概況
2、若是存在高能耗情況,使用 Instruments 的 Energy Log 模板進行細緻驗證,並配合 Time Profiler 模板抓取代碼的運行細節。
3、根據代碼的運行細節,判斷潛在的問題點,然後修改代碼
4、驗證修改效果,若是無效,那麼重複 2 - 4 步驟

參考

本文是 raywenderlich 的課程筆記,內容參考 Practical Instruments 課程
1、demo下載地址 https://files.betamax.raywenderlich.com/attachments/videos/789/9560e62e-96d3-47e5-b604-5d20c72bf9ee.zip
2、 Energy Log 課程地址
https://videos.raywenderlich.com/courses/74-practical-instruments/lessons/7
3、Energy Impact 官方文檔
https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/MonitorEnergyWithXcode.html



作者:要上班的斌哥
鏈接:https://www.jianshu.com/p/d7091ec727a7
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章