如何用RunLoop檢測iOS App 卡頓

首先本文的思路來自於網上的各類資料。而後搜了半天發現沒有swift版的,因而擼了一個。swift

其實具體的思路很是的簡單:async

  1. 首先建立一個runloop的observer對象:oop

    let info = Unmanaged<Monitor>.passUnretained(self).toOpaque()
     var context: CFRunLoopObserverContext = CFRunLoopObserverContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil)
     self.runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, runLoopCallBack(), &context)
    複製代碼
  2. 而後將這個觀察對象添加到runloop的common modes中spa

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.runLoopObserver, CFRunLoopMode.commonModes)
    複製代碼

    ps: 由於common modes是會一直存在於runloop中的,不會被中斷,因此講檢測的observer對象放到這個modes裏去。code

  3. 檢測CFRunLoopActivityserver

    CFRunLoopActivity這個結構有很是多的狀態吧,咱們須要判斷的是:對象

    beforeSources: 進入睡眠前
     afterWaiting: 喚醒後的狀態
    複製代碼

    若是runloop返回的activity的值是上述的兩個,那麼就能夠認爲出現了卡頓的現象ip

  4. 這裏用了dispatch的信號機制it

    self.dispatchSemaphore?.wait(timeout: DispatchTime.now() + 1 / 50)io

    這段代碼認定,若是每秒的幀數少於50,那麼就認爲發生了卡頓的現象

實現的邏輯就是這麼四步,下面貼上所有的代碼:

import Foundation

class Monitor {
  
  static let shared = Monitor()
  
  private var runLoopObserver: CFRunLoopObserver?
  private var dispatchSemaphore: DispatchSemaphore?
  private var runLoopActivity: CFRunLoopActivity?
  
  init() {}
  
  func beginMonitor() {
    guard self.runLoopObserver == nil else { return }
    
    self.dispatchSemaphore = DispatchSemaphore(value: 0)

    let info = Unmanaged<Monitor>.passUnretained(self).toOpaque()
    var context: CFRunLoopObserverContext = CFRunLoopObserverContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil)
    self.runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, runLoopCallBack(), &context)
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.runLoopObserver, CFRunLoopMode.commonModes)
    
    DispatchQueue.global().async {
      // 若是少於50每幀, 則認爲卡頓
      while true {
        guard let sem = self.dispatchSemaphore?.wait(timeout: DispatchTime.now() + 1 / 50) else { return }
        if case DispatchTimeoutResult.timedOut = sem {
          guard let _ = self.runLoopObserver else {
            self.dispatchSemaphore = nil
            self.runLoopActivity = nil
            return
          }
          
          // beforeSources: 進入睡眠前
          // afterWaiting: 喚醒後的狀態
          if (self.runLoopActivity == CFRunLoopActivity.beforeSources || self.runLoopActivity == CFRunLoopActivity.afterWaiting) {
            print("symbo: \(Thread.callStackSymbols)")
            print("打印卡頓堆棧...")
          }
        }
      }
    }
  }
  
  func endMonitor() {
    if self.runLoopObserver != nil {
      return
    }
    
    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), self.runLoopObserver, CFRunLoopMode.commonModes)
    self.runLoopObserver = nil
  }
  
}

extension Monitor {
  func runLoopCallBack() -> CFRunLoopObserverCallBack {
    return { (observer, activity, context) -> Void in
      let weakSelf = Unmanaged<Monitor>.fromOpaque(context!).takeUnretainedValue()
      weakSelf.runLoopActivity = activity
      weakSelf.dispatchSemaphore?.signal()
    }
  }
}複製代碼
相關文章
相關標籤/搜索