Firstly, the idea of this paper comes from various materials on the Internet. Then I searched around and found no swift version, so I masturbated.

In fact, the specific idea is very simple:

  1. First create an observer of runloop:

     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)
     
    Copy the code
  2. This observation object is then added to the Common modes of runloop

     CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.runLoopObserver, CFRunLoopMode.commonModes)
     
    Copy the code

    Ps: Because common modes are always present in the runloop and will not be interrupted, put the observer objects detected in this mode.

  3. Detect CFRunLoopActivity

    The CFRunLoopActivity structure has a lot of states, and we need to determine:

    BeforeSources: afterWaitingCopy the code

    If the runloop returns two of the above values for the activity, then it is considered to be stalling

  4. The signal mechanism of dispatch is used here

    self.dispatchSemaphore? Wait (timeout: DispatchTime. Now () + 0.05)

    The code determines that if you exceed 0.088 seconds five times in a row, a stalling is considered to have occurred

The logic of the implementation is this four steps, pasted below all the code:

import Foundation private let START_MONITOR_RATE = 88 class DJMonitor { static let shared = DJMonitor() var isMoniting =  false var timeoutCount = 0 var runLoopActivity: CFRunLoopActivity = .entry var dispatchSemaphore: DispatchSemaphore? = DispatchSemaphore(value: 0) var runloopObserver: CFRunLoopObserver? func start() { guard ! isMoniting else { return } self.runloopObserver = buildRunLoopObserver() if self.runloopObserver == nil { Print (" create listener failed..." ) return } isMoniting = true CFRunLoopAddObserver(CFRunLoopGetMain(), runloopObserver, CFRunLoopMode.commonModes) DispatchQueue.global().async { while true { let wait = self.dispatchSemaphore?.wait(timeout: DispatchTime. Now () + 0.05) if DispatchTimeoutResult. TimedOut = = wait {guard self. RunloopObserver! = nil else { self.dispatchSemaphore = nil self.runLoopActivity = .entry return } if self.runLoopActivity == .beforeSources || self.runLoopActivity == .afterWaiting { if self.timeoutCount < 5 { self.timeoutCount += 1 continue } DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async { print("check the cpu info..." ) } } } self.timeoutCount = 0 } } } func end() { guard self.runloopObserver ! = nil else { return } self.isMoniting = false CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.runloopObserver, CFRunLoopMode.commonModes) self.runloopObserver = nil } private func buildRunLoopObserver() -> CFRunLoopObserver? { let info = Unmanaged<DJMonitor>.passUnretained(self).toOpaque() var context = CFRunLoopObserverContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, runLoopObserverCallback(), &context) return observer } func runLoopObserverCallback() -> CFRunLoopObserverCallBack { return { observer, activity, info in guard let info = info else { return } let weakSelf = Unmanaged<DJMonitor>.fromOpaque(info).takeUnretainedValue()  weakSelf.runLoopActivity = activity weakSelf.dispatchSemaphore?.signal() } } }Copy the code