Observe activities from eventloop runloop

Updated
Apr 19, 2022 11:09 AM
Created
Dec 22, 2021 6:54 PM
Tags
SwiftiOS
Attributes

public enum RunLoopActivityObserver {

  public struct Subscription {
    let observer: CFRunLoopObserver?
  }

  public static func addObserver(acitivity: CFRunLoopActivity, callback: @escaping () -> Void) -> Subscription {

    let o = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, acitivity.rawValue, true, Int.max, { observer, activity in
      callback()
    });

    CFRunLoopAddObserver(CFRunLoopGetMain(), o, CFRunLoopMode.defaultMode);

    return .init(observer: o)
  }

  public static func remove(_ subscription: Subscription) {
    subscription.observer.map {
      CFRunLoopRemoveObserver(CFRunLoopGetMain(), $0, CFRunLoopMode.defaultMode);
    }
  }

}

CFRunLoop.c

private final class RunloopQueue {
  
  private var items: ContiguousArray<() -> Void> = .init()
  
  private let runloop: RunLoop = .main
    
  init() {
    
    let activity = CFRunLoopActivity.beforeWaiting
    let cfRunloop = runloop.getCFRunLoop()
    let mode: CFRunLoopMode = .defaultMode
    
    let observer = CFRunLoopObserverCreateWithHandler(
      kCFAllocatorDefault,
      activity.rawValue,
      true,
      0,
      { [weak self] observer, activity in
        
        guard let self = self else { return }
        
        self.drain()

    });
    
    CFRunLoopAddObserver(cfRunloop, observer, mode);
    
    var sourceContext = CFRunLoopSourceContext()
    sourceContext.perform = { _ in
      
    }
    sourceContext.info = Unmanaged.passUnretained(self).toOpaque()
    
    let source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext)
    
    CFRunLoopAddSource(cfRunloop, source, mode)

  }
  
  func perfom(_ closure: @escaping () -> Void) {
    runloop.perform(inModes: [.common]) { [weak self] in
      guard let self = self else { return }
      self.items.append(closure)
    }
  }
  
  func drain() {
    
    guard items.isEmpty == false else { return }
    
    let processedItems = items
    items.removeAll()
    
    for item in processedItems {
      item()
    }
    
  }
  
}