Skip to content

Instantly share code, notes, and snippets.

@calebd
Last active September 7, 2022 16:45
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save calebd/f79c4f16af72c212d113 to your computer and use it in GitHub Desktop.
Save calebd/f79c4f16af72c212d113 to your computer and use it in GitHub Desktop.
Run Loop Source

CFRunLoopSource is cool. It lets you build behavior similar to the mechanisms that drive setNeedsLayout and setNeedsDisplay in UIKit.

I found myself in need of something like this a couple of times. It's great to know that no matter how many times I say I need to update something, I will get a single callback at the end of the run loop that gives me a chance to perform my work.

Here is a little Swift wrapper that makes the API easier to deal with.

import CoreFoundation
public final class RunLoopSource {
// MARK: - Properties
private lazy var source: CFRunLoopSource = {
var context = CFRunLoopSourceContext()
context.info = UnsafeMutablePointer(unsafeAddressOf(self))
context.perform = _perform
return CFRunLoopSourceCreate(nil, self.order, &context)
}()
private let subscription: (CFRunLoop, CFString)
private let order: CFIndex
private let perform: () -> ()
// MARK: - Initializers
public init(loop: CFRunLoop = CFRunLoopGetCurrent(), mode: CFString = kCFRunLoopCommonModes, order: CFIndex = 0, perform: () -> ()) {
self.subscription = (loop, mode)
self.perform = perform
self.order = order
CFRunLoopAddSource(loop, source, mode)
}
deinit {
let (loop, mode) = subscription
CFRunLoopRemoveSource(loop, source, mode)
}
// MARK: - Public
public func signal() {
CFRunLoopSourceSignal(source)
}
}
private func _perform(info: UnsafeMutablePointer<Void>) {
unsafeBitCast(info, RunLoopSource.self).perform()
}
///
/// Here is an example class that demonstrates how you might use
/// `RunLoopSource`. I know this isn't really how UIView works.
///
class View {
private var needsLayout = false
private lazy var layoutSource: RunLoopSource = {
RunLoopSource(perform: { [weak self] in
self?.layoutIfNeeded()
})
}()
final func setNeedsLayout() {
needsLayout = true
layoutSource.signal()
}
final func layoutIfNeeded() {
if needsLayout {
layoutSubviews()
}
needsLayout = false
}
func layoutSubviews() {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment