Skip to content

Instantly share code, notes, and snippets.

@soffes
Forked from calebd/Readme.markdown
Created July 3, 2018 23:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soffes/10096159b309ca14ddd8dab035f33293 to your computer and use it in GitHub Desktop.
Save soffes/10096159b309ca14ddd8dab035f33293 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.


Updated for Swift 4

public final class RunLoopSource {
// MARK: - Properties
private lazy var source: CFRunLoopSource = {
var context = CFRunLoopSourceContext()
context.info = Unmanaged.passUnretained(self).toOpaque()
context.perform = _perform
return CFRunLoopSourceCreate(nil, self.order, &context)
}()
private let subscription: (loop: CFRunLoop, mode: CFRunLoopMode)
private let order: CFIndex
fileprivate let perform: () -> Void
// MARK: - Initializers
public init(loop: CFRunLoop = CFRunLoopGetCurrent(), mode: CFRunLoopMode = .commonModes, order: CFIndex = 0, perform: @escaping () -> Void) {
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: UnsafeMutableRawPointer?) {
unsafeBitCast(info, to: 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