Skip to content

Instantly share code, notes, and snippets.

@domjtalbot
Created May 9, 2019 17:44
Show Gist options
  • Save domjtalbot/30fa3616b7bd63c36174590e6b44eef2 to your computer and use it in GitHub Desktop.
Save domjtalbot/30fa3616b7bd63c36174590e6b44eef2 to your computer and use it in GitHub Desktop.
PromiseKit Operation & Queue
import Foundation
import PromiseKit
public class PromiseOperation<T>: Operation {
public enum State: String {
case executing
case finished
case ready
fileprivate var keyPath: String {
return "is" + rawValue.capitalized
}
}
override public var isExecuting: Bool {
return self.state == .executing
}
override public var isFinished: Bool {
return self.state == .finished
}
private let promise: () -> Promise<T>
public var state = State.ready {
willSet {
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
init(_ promise: @escaping () -> Promise<T>) {
self.promise = promise
}
public convenience init(
_ name: String,
_ qualityOfService: QualityOfService = .default,
promise: @escaping () -> Promise<T>
) {
self.init(promise)
self.name = name
self.qualityOfService = qualityOfService
}
public override func main() {
if self.isCancelled {
print("Operation cancelled")
self.state = .finished
return
}
print("\n Operation \(self.name ?? "") - main")
self.state = .executing
firstly {
after(seconds: 2)
}.then { _ in
self.runAndWait()
}.done { _ in
print(" Operation \(self.name ?? "") - done")
}.catch { error in
print("Operation \(self.name ?? "") - error: \(error)")
}.finally {
self.state = .finished
}
}
/// Run the operation.
///
/// - Returns: The `Promise` returned by the operation.
public func run() -> Promise<T> {
print(" Operation \(self.name ?? "") - run")
return self.promise()
}
/// Run the operation and block until it finishes.
///
/// - Returns: The `Promise` returned by the operation.
public func runAndWait() -> Guarantee<[Result<T>]> {
print(" Operation \(self.name ?? "") - runAndWait")
return when(resolved: [self.promise()])
}
}
import Dispatch
import PromiseKit
/// A queue that regulates the execution of promise operations.
///
/// An operation queue executes its queued Operation objects based
/// on their priority and readiness. After being added to an operation
/// queue, an operation remains in its queue until it reports that
/// it is finished with its task. You can’t directly remove an
/// operation from a queue after it has been added.
///
/// - note:
/// Operation queues retain operations until they're finished,
/// and queues themselves are retained until all operations are finished.
/// Suspending an operation queue with operations that aren't finished
/// can result in a memory leak.
public class PromiseQueue: OperationQueue {
// MARK: - Properties
/// Elapsed time in seconds since queue was started
public var elapsedDuration: TimeInterval {
if let startTime = self.startTime {
let endTime = self.endTime ?? Date()
return endTime.timeIntervalSince(startTime)
}
return 0
}
/// Time queue finised
private var endTime: Date?
/// Observation token to observe running queue
private var isRunningToken: NSKeyValueObservation?
/// Time queue was started
private var startTime: Date?
// MARK: - Functions
/// Initialize Queue
///
///
/// - parameters:
/// - name: String - Name of Queue
/// - concurrently: Int? - How many operations to run concurrently
/// - qualityOfService: QualityOfService - Used to indicate the nature and importance of work to the system. Work with higher quality of service classes receive more resources than work with lower quality of service classes whenever there is resource contention.
public convenience init(
name: String,
concurrently: Int? = nil,
qualityOfService: QualityOfService = .default
) {
self.init()
self.name = name
if let concurrently = concurrently {
self.maxConcurrentOperationCount = concurrently
}
self.qualityOfService = qualityOfService
self.suspendAll()
}
/// Resume all operations in queue
public func resumeAll() {
self.isSuspended = false
}
/// Suspend all operations in queue
public func suspendAll() {
self.isSuspended = true
}
/// Start the queue
private func start() {
print(" Starting Queue \(self.name ?? "") count: \(self.operationCount)")
self.startTime = Date()
self.resumeAll()
}
/// Start queue and wait for all operations to complete before continuing
///
///
/// - returns: Fulfilled promise when queue is finished
public func startAndWait() -> Promise<Void> {
return Promise { promise in
self.isRunningToken = self.observe(\.operationCount, options: [.new, .old]) { object, change in
if change.newValue == 0 {
self.isRunningToken?.invalidate()
self.endTime = Date()
promise.fulfill_()
}
}
self.start()
}
}
}
import PromiseKit
func test() {
print("Start")
print("\nCreating Queue")
var testQueue = PromiseQueue(
name: "Test Queue",
concurrently: 1
)
print("\nCreating promise operations")
var a = PromiseOperation<Void>("A") {
return after(seconds: 1).done { _ in
print(" ~ Hello A")
}
}
var b = PromiseOperation<Void>("B") {
return after(seconds: 1).done { _ in
print(" ~ Hello B")
}
}
var c = PromiseOperation<Void>("C") {
return after(seconds: 1).done { _ in
print(" ~ Hello C")
}
}
print("\nAdd promise to Queue")
testQueue.addOperation(a)
testQueue.addOperation(b)
testQueue.addOperation(c)
after(seconds: 3).get {
print("\nStarting Queue")
} .then { _ in
testQueue.startAndWait()
}.done { _ in
print("\nEnd - elapsedDuration: \(testQueue.elapsedDuration)s")
}
}
test()
// Log result:
/*
Start
Creating Queue
Creating promise operations
Add promise to Queue
Starting Queue
Starting Queue Test Queue count: 3
Operation A - main
Operation A - runAndWait
~ Hello A
Operation A - done
Operation B - main
Operation B - runAndWait
~ Hello B
Operation B - done
Operation C - main
Operation C - runAndWait
~ Hello C
Operation C - done
End - elapsedDuration: 9.57923698425293s
*/
@qalvapps
Copy link

awesome mate. was coding exactly this when found your working solution. thanks dude !

@domjtalbot
Copy link
Author

@qalvapps thats awesome to hear! I'm happy it helped! 🖖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment