Skip to content

Commit

Permalink
Merge pull request #1 from shakurocom/II-15-use_toolbox_task_manager
Browse files Browse the repository at this point in the history
Ii 15 use toolbox task manager
  • Loading branch information
sergeyyypopov authored May 24, 2021
2 parents 8961a1e + 54dc28a commit c4c96bf
Show file tree
Hide file tree
Showing 33 changed files with 1,083 additions and 1,135 deletions.
147 changes: 75 additions & 72 deletions Source/BaseOperation.swift → Source/Operation/BaseOperation.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//
// Copyright (c) 2018 Shakuro (https://shakuro.com/)
// Copyright (c) 2018-2020 Shakuro (https://shakuro.com/)
// Sergey Laschuk
//

import Foundation
import Shakuro_CommonTypes

open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOperation<ResultType>, DependencyProtocol, PriorityProtocol, DependentOperation {
open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOperation<ResultType>, DependencyProtocol, DependentOperation {

private indirect enum State { // idle -> executing -> finished : always that way
case idle
Expand All @@ -17,8 +17,8 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
final public let options: OptionsType

private var state: State
private let accessLock: NSRecursiveLock
private var callbacks: [OperationCallback<ResultType>]
private var internalCallback: OperationCallback<ResultType>?
private var strongDependencies: [Operation]
private let internalOperationHash: String

Expand All @@ -27,19 +27,16 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
public required init(options aOptions: OptionsType) {
options = aOptions
state = .idle
accessLock = NSRecursiveLock()
accessLock.name = "\(type(of: self)).accessLock"
callbacks = []
internalCallback = nil
strongDependencies = []
internalOperationHash = "\(type(of: self))-\(options.optionsHash())"
super.init()
}

// MARK: - Properties

/**
See `Operation.isExecuting` for description.
*/
/// See `Operation.isExecuting` for description.
final public override var isExecuting: Bool {
switch state {
case .idle,
Expand All @@ -50,9 +47,7 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
}
}

/**
See `Operation.isFinidhed` for description.
*/
/// See `Operation.isFinidhed` for description.
final public override var isFinished: Bool {
switch state {
case .idle,
Expand All @@ -63,11 +58,9 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
}
}

/**
Result of the operation. Returns nil if operation is not yet finished.
*/
/// Result of the operation. Returns nil if operation is not yet finished.
public final override var operationResult: CancellableAsyncResult<ResultType>? {
let result = accessLock.execute({ () -> CancellableAsyncResult<ResultType>? in
let result = performProtected({ () -> CancellableAsyncResult<ResultType>? in
switch state {
case .idle,
.executing:
Expand All @@ -79,27 +72,26 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
return result
}

/**
Unique identyfier of operation, including it's options.
*/
/// Unique identyfier of operation, including it's options.
final public override var operationHash: String {
return internalOperationHash
}

// MARK: - Operation actions

final public override func cancel() {
accessLock.execute({ () -> Void in
public final override func cancel() {
performProtected({ () -> Void in
if !self.isCancelled {
super.cancel()
internalCancel()
}
})
}

final public override func onComplete(queue: DispatchQueue?, closure: @escaping (_ result: CancellableAsyncResult<ResultType>) -> Void) {
let newCallback = OperationCallback(callbackQueue: queue, callback: closure)
accessLock.execute({ () -> Void in
public final override func onComplete(queue: DispatchQueue?, closure: @escaping (_ result: CancellableAsyncResult<ResultType>) -> Void) {
let newCallback = OperationCallback(callbackQueue: queue ?? DispatchQueue.global(),
callback: closure)
performProtected({ () -> Void in
switch state {
case .idle,
.executing:
Expand All @@ -110,29 +102,23 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
})
}

final public override func addDependency(_ operation: Operation) {
public final override func addDependency(_ operation: Operation) {
addDependencyInternal(operation: operation, isStrongDependency: false)
}

final public func addDependency(operation dependencyOperation: TaskManager.OperationInQueue, isStrongDependency: Bool) {
public final func addDependency(operation dependencyOperation: TaskManager.OperationInQueue, isStrongDependency: Bool) {
guard let test = dependencyOperation as? Operation else {
return
}
addDependencyInternal(operation: test, isStrongDependency: isStrongDependency)
}

final public func performProtected<T>(_ closure: () -> T) -> T {
return accessLock.execute(closure)
}

/**
Start operation. You can start operation manually, but only if this operation is not in the queue.
You should not start already executing or finished operation.
In debug configuration this will produce assertion failure.
In release - second start will finish operation with `TaskManagerError.internalInconsistencyError`.
*/
/// Start operation. You can start operation manually, but only if this operation is not in the queue.
/// You should not start already executing or finished operation.
/// In debug configuration this will produce assertion failure.
/// In release - second start will finish operation with `TaskManagerError.internalInconsistencyError`.
final public override func start() {
let startFailure = accessLock.execute({ () -> CancellableAsyncResult<ResultType>? in
let startFailure = performProtected({ () -> CancellableAsyncResult<ResultType>? in
var failureResult: CancellableAsyncResult<ResultType>?
guard self.isReady else {
assertionFailure("\(type(of: self)): operation is not ready.")
Expand Down Expand Up @@ -183,12 +169,10 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
})
}

/**
Use this method at the end of 'main()' function to properly finish operation and execute callbacks.
- parameter result: result of the operation. You can retrive it via `operationResult` property.
*/
/// Use this method at the end of 'main()' function to properly finish operation and execute callbacks.
/// - parameter result: result of the operation. You can retrive it via `operationResult` property.
final public func finish(result: CancellableAsyncResult<ResultType>) {
accessLock.execute({ () -> Void in
performProtected({ () -> Void in
switch state {
case .idle,
.finished:
Expand All @@ -198,61 +182,54 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
case .executing:
changeState(to: State.finished(operationResult: result))
internalFinished()
for callback in callbacks {
if let callback = internalCallback {
// essentially this means, that we are inside TaskManager
callback.performAsync(result: result)
} else {
// this is fallback: when operation executed outside of TaskManager (ex.: direct .start())
performCallbacksNoLock(operationResult: result)
}
callbacks.removeAll()
}
})
}

// MARK: - Overridables

/**
Use this method to cancel your internal processes. Default implementation does nothing.
*/
/// Use this method to cancel your internal processes. Default implementation does nothing.
open func internalCancel() {
// do nothing
}

/**
Place your code here.
You MUST override it.
You MUST call `finish(result:)` to properly finish operation.
*/
/// Place your code here.
/// You MUST override it.
/// You MUST call `finish(result:)` to properly finish operation.
open override func main() {
assertionFailure("\(type(of: self)): you MUST override 'main()' function.")
}

/**
This function will be called inside 'finish(result:)'. You can override it to perform cleanup.
*/
/// This function will be called inside 'finish(result:)'. You can override it to perform cleanup.
open func internalFinished() {
//do nothing
// do nothing
}

/**
Priority of operation. Works in conjunction with `priorityType`.
This value should not change after operation object was initialized.
Default value is 0.
*/
open var priorityValue: Int {
/// Priority of operation. Works in conjunction with `priorityType`.
/// This value should not change after operation object was initialized.
/// Default value is 0.
open override var priorityValue: Int {
return 0
}

/**
See `OperationPriorityType` for description.
This value should not change after operation was initialized.
Deafult value is `OperationPriorityType.fifo`
*/
open var priorityType: OperationPriorityType {
/// See `OperationPriorityType` for description.
/// This value should not change after operation was initialized.
/// Deafult value is `OperationPriorityType.fifo`
open override var priorityType: OperationPriorityType {
return OperationPriorityType.fifo
}

// MARK: - Internal

final internal func dependencyResult() -> CancellableAsyncResult<Void>? {
let result = accessLock.execute({ () -> CancellableAsyncResult<Void>? in
let result = performProtected({ () -> CancellableAsyncResult<Void>? in
switch state {
case .idle,
.executing:
Expand All @@ -264,11 +241,30 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
return result
}

/// Special completion, that is used by task manager itself to manage its queue.
/// - parameter queue: queue for a completion block.
/// - parameter closure: completion block.
internal final override func setInternalCompletion(queue: DispatchQueue, closure: @escaping () -> Void) {
internalCallback = OperationCallback(callbackQueue: queue, callback: { (_) in
closure()
})
}

final internal override func executeOnCompleteCallbacks() {
performProtected({
switch state {
case .idle,
.executing:
assertionFailure("BaseOperation: invalid state '\(state)'")
case .finished(operationResult: let operationResult):
performCallbacksNoLock(operationResult: operationResult)
}
})
}

// MARK: - Private

/**
Change state of the operation. Also produces proper 'isExecuting' & 'isFinished' KVO notifications.
*/
/// Change state of the operation. Also produces proper 'isExecuting' & 'isFinished' KVO notifications.
private func changeState(to newState: State) {
switch state {
case .idle:
Expand Down Expand Up @@ -297,7 +293,7 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
}

private func addDependencyInternal(operation dependencyOperation: Operation, isStrongDependency: Bool) {
accessLock.execute({ () -> Void in
performProtected({ () -> Void in
guard case .idle = state else {
assertionFailure("BaseOperation: can't add dependencies for operation in state '\(state)'")
return
Expand All @@ -309,4 +305,11 @@ open class BaseOperation<ResultType, OptionsType: BaseOperationOptions>: TaskOpe
})
}

private func performCallbacksNoLock(operationResult: CancellableAsyncResult<ResultType>) {
for callback in callbacks {
callback.performAsync(result: operationResult)
}
callbacks.removeAll()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import Foundation

public protocol BaseOperationOptions {

/**
Hash string for all **meaningfull** options of operation.
Used in 'BaseOperation.operationHash()'
Default implementation returns empty string(meaning all hashes are equal).
*/
/// Hash string for all **meaningfull** options of operation.
/// Used in 'BaseOperation.operationHash()'
/// Default implementation returns empty string(meaning all hashes are equal).
func optionsHash() -> String

}
Expand Down
15 changes: 7 additions & 8 deletions Source/Operation/DependentOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ import Foundation
import Shakuro_CommonTypes

internal protocol DependencyProtocol: class {
/**
See `DependencyResult` for description.
*/

/// See `DependencyResult` for description.
func dependencyResult() -> CancellableAsyncResult<Void>?

}

public protocol DependentOperation {

/**
Add specified operation as a dependency for current operation. Please do not add same operation twice - result is undefined.
- parameter operation: Operation current operation should be dependent upon.
- parameter isStrongDependency: if `true` - failure/cancel of the target operation will be propagated into current operation. If operation is strongly dependent on several operations, than firstly encountered failure/cancel will be propagated.
*/
/// Add specified operation as a dependency for current operation. Please do not add same operation twice - result is undefined.
/// - parameter operation: Operation current operation should be dependent upon.
/// - parameter isStrongDependency: if `true` - failure/cancel of the target operation will be propagated into current operation.
/// If operation is strongly dependent on several operations, than firstly encountered failure/cancel will be propagated.
func addDependency(operation dependencyOperation: TaskManager.OperationInQueue, isStrongDependency: Bool)

}
4 changes: 1 addition & 3 deletions Source/Operation/OperationCallback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import Foundation
import Shakuro_CommonTypes

/**
Wrapper for a block, that can be run on a specified queue.
*/
/// Wrapper for a block, that can be run on a specified queue.
final internal class OperationCallback<ResultType> {

internal typealias CallbackType = (_ result: CancellableAsyncResult<ResultType>) -> Void
Expand Down
37 changes: 0 additions & 37 deletions Source/Operation/OperationPriority.swift

This file was deleted.

Loading

0 comments on commit c4c96bf

Please sign in to comment.