Skip to content

Instantly share code, notes, and snippets.

@thecoolwinter
Last active May 23, 2022 16:21
Show Gist options
  • Save thecoolwinter/a0666738a783a094c6822344070c2533 to your computer and use it in GitHub Desktop.
Save thecoolwinter/a0666738a783a094c6822344070c2533 to your computer and use it in GitHub Desktop.
DelayedTask.swift
/// The `DelayedTask` waits to execute an action, and will only call it's `onEnd` completion if the
/// `action` has been performed.
///
/// This allows for situations where you may `await` for a cache item that could take a small amount of time,
/// but it could also require a network call. An example of such a situation is below:
/// ```swift
/// Task {
/// self.showLoadingIndicator()
/// let data = await ResourceManager.getData()
/// self.removeLoadingIndicator()
/// }
/// ```
///
/// In this situation we may show the loading indicator even though we may not have to show it. That
/// `await ResourceManager.getData()` might only take a few ms.
/// In that case we would want to wait just a little bit before showing the loading indicator, and when
/// we do show it we need a way to remove it afterwards. That's where `DelayedTask` comes in:
///
/// ```swift
/// Task {
/// let task = DelayedTask(duration: 0.2) {
/// self.showLoadingIndicator()
/// } onEnd: {
/// self.removeLoadingIndicator()
/// }
/// let data = await ResourceManager.getData()
/// task.end()
/// }
/// ```
/// Using this, we'll only show the loading indicator if the `await ResourceManager.getData()`
/// method takes more than `0.2` seconds.
///
/// This actor also provides a `queue` parameter that defaults to the main queue. This can be changed
/// to a different queue if needed, but makes UI changes easier using this actor.
class DelayedTask {
private let action: (() -> Void)
private let onEnd: (() -> Void)
private var timer: Timer? = nil
private let queue: DispatchQueue
/// Initializes the `DelayedTask` and begins wating the given duration immeditately.
/// To end the task call the `end()` method on this actor.
/// - Parameters:
/// - duration: The duration to wait before executing the `action`
/// - action: The action to perform after the given duration
/// - onEnd: The completion to call when the `end()` method is called. *only executed if the `action` has already been called*
/// - queue: The queue to call `action` and `onEnd` on. Defaults to `DispatchQueue.main`
init(duration: Double, action: @escaping (() -> Void), onEnd: @escaping (() -> Void), queue: DispatchQueue = .main) async {
self.action = action
self.onEnd = onEnd
self.queue = queue
timer = Timer(timeInterval: duration, repeats: false, block: { [weak self] _ in
self?.performAction()
})
}
/// Ends the delayed task. If the timer isn't already finished
/// this will not execute the `onEnd` completion.
public func end() {
if timer != nil {
timer?.invalidate()
timer = nil
} else {
queue.async {
self.onEnd()
}
}
}
private func performAction() {
queue.async {
self.action()
}
timer?.invalidate()
timer = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment