Skip to content

Instantly share code, notes, and snippets.

@alemar11
Forked from calebd/Readme.markdown
Created November 14, 2020 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alemar11/fac90107883c91522c060d7703e3637d to your computer and use it in GitHub Desktop.
Save alemar11/fac90107883c91522c060d7703e3637d 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