diff --git a/AlecrimAsyncKit.png b/AlecrimAsyncKit.png new file mode 100644 index 0000000..3e4dcf4 Binary files /dev/null and b/AlecrimAsyncKit.png differ diff --git a/AlecrimAsyncKit.podspec b/AlecrimAsyncKit.podspec index 0e90483..31ee53c 100644 --- a/AlecrimAsyncKit.podspec +++ b/AlecrimAsyncKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimAsyncKit" - s.version = "2.0-beta.1" + s.version = "2.0" s.summary = "Bringing async and await to Swift world with some flavouring." s.homepage = "https://github.com/Alecrim/AlecrimAsyncKit" diff --git a/README.md b/README.md index 4f70bfc..5e5ffde 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -#AlecrimAsyncKit +![AlecrimAsyncKit](https://raw.githubusercontent.com/Alecrim/AlecrimAsyncKit/master/AlecrimAsyncKit.png) + +[![Language: Swift](https://img.shields.io/badge/lang-Swift-orange.svg?style=flat)](https://developer.apple.com/swift/) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/Alecrim/AlecrimAsyncKit/develop/LICENSE) +[![CocoaPods](https://img.shields.io/cocoapods/v/AlecrimAsyncKit.svg?style=flat)](http://cocoapods.org) +[![Forks](https://img.shields.io/github/forks/Alecrim/AlecrimAsyncKit.svg?style=flat)](https://github.com/Alecrim/AlecrimAsyncKit/network) +[![Stars](https://img.shields.io/github/stars/Alecrim/AlecrimAsyncKit.svg?style=flat)](https://github.com/Alecrim/AlecrimAsyncKit/stargazers) Bringing async and await to Swift world with some flavouring. @@ -206,13 +212,24 @@ Only failable tasks can be cancelled. #### The main thread -Even if you cannot "await" a task on main thread, you still can start a background task from the main thread and handle its completion using the `didFinish` method. If the queue parameter is not added, the callback closure will be called on the main thread. +Even if you cannot "await" a task on main thread, you still can start a background task from the main thread. If you want to handle its completion you may use `TaskWaiter` helper class. If the queue parameter is not added, the callback closures will be called on the main thread. ```swift // this code is running on the main thread -asyncCalculate() - .didFinish { value, error in - if let error = error { +TaskWaiter(task: asyncCalculate()) + .didFinishWithValue { value in + print("The result is \(value)") + } + .didFinishWithError { error in + // do a nice error handling here + } + .didCancel { + print("Task was cancelled") + } + .didFinish { task in + // this closure will be always called, even if the task was cancelled + + if let error = error where !error.userCancelled { // do a nice error handling here } else { @@ -240,7 +257,7 @@ func asyncCalculate() -> Task { } ``` -The difference between a failable task and a non-failable task is that a non-failable does not have the `error` parameter in its `didFinish` closure. A failable task has also a `didCancel` method (note that the `didFinish` closure will be called even if the task was cancelled). +The difference between a failable task and a non-failable task is that a non-failable task waiter is called `NonFailableTaskWaiter` and it does not have the `didFinishWithError` and `didCancel` methods. ### Considerations diff --git a/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj b/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj index 095158f..c00a7db 100644 --- a/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj +++ b/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 1413F62C1BDCAE56001049D5 /* TaskState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1413F62B1BDCAE56001049D5 /* TaskState.swift */; }; 1413F62E1BDCAE8B001049D5 /* TaskType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1413F62D1BDCAE8B001049D5 /* TaskType.swift */; }; 1416E08D1BDC931A00F1EA73 /* TaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1416E08C1BDC931A00F1EA73 /* TaskObserver.swift */; }; + 142561B51BE31B9D00A4C974 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142561B41BE31B9D00A4C974 /* TaskOperation.swift */; }; 143045511BB7AB5900CD0AED /* ProcessInfoActivityTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 143045501BB7AB5900CD0AED /* ProcessInfoActivityTaskObserver.swift */; }; 14749B841B9C437700661E98 /* EventStorePermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14749B831B9C437700661E98 /* EventStorePermissionTaskCondition.swift */; }; 14749B861B9C4DA300661E98 /* BooleanTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14749B851B9C4DA300661E98 /* BooleanTaskCondition.swift */; }; @@ -21,7 +21,7 @@ 148513A11B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513911B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift */; }; 148513A21B9BD15100FAB0FA /* ReachabilityTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513921B9BD15100FAB0FA /* ReachabilityTaskCondition.swift */; }; 148513A31B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513931B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift */; }; - 148513A41B9BD15100FAB0FA /* TaskBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513951B9BD15100FAB0FA /* TaskBuilder.swift */; }; + 148513A41B9BD15100FAB0FA /* AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513951B9BD15100FAB0FA /* AsyncAwait.swift */; }; 148513A51B9BD15100FAB0FA /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513961B9BD15100FAB0FA /* Errors.swift */; }; 148513A61B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513971B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift */; }; 148513A71B9BD15100FAB0FA /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513981B9BD15100FAB0FA /* Task.swift */; }; @@ -34,9 +34,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 1413F62B1BDCAE56001049D5 /* TaskState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskState.swift; sourceTree = ""; }; 1413F62D1BDCAE8B001049D5 /* TaskType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskType.swift; sourceTree = ""; }; 1416E08C1BDC931A00F1EA73 /* TaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskObserver.swift; sourceTree = ""; }; + 142561B41BE31B9D00A4C974 /* TaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskOperation.swift; sourceTree = ""; }; 143045501BB7AB5900CD0AED /* ProcessInfoActivityTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessInfoActivityTaskObserver.swift; sourceTree = ""; }; 14749B831B9C437700661E98 /* EventStorePermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventStorePermissionTaskCondition.swift; sourceTree = ""; }; 14749B851B9C4DA300661E98 /* BooleanTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BooleanTaskCondition.swift; sourceTree = ""; }; @@ -48,7 +48,7 @@ 148513911B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosPermissionTaskCondition.swift; sourceTree = ""; }; 148513921B9BD15100FAB0FA /* ReachabilityTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityTaskCondition.swift; sourceTree = ""; }; 148513931B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteNotificationPermissionTaskCondition.swift; sourceTree = ""; }; - 148513951B9BD15100FAB0FA /* TaskBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBuilder.swift; sourceTree = ""; }; + 148513951B9BD15100FAB0FA /* AsyncAwait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncAwait.swift; sourceTree = ""; }; 148513961B9BD15100FAB0FA /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 148513971B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutuallyExclusiveTaskCondition.swift; sourceTree = ""; }; 148513981B9BD15100FAB0FA /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; @@ -100,10 +100,10 @@ 148513941B9BD15100FAB0FA /* Core */ = { isa = PBXGroup; children = ( + 148513951B9BD15100FAB0FA /* AsyncAwait.swift */, 148513981B9BD15100FAB0FA /* Task.swift */, 1413F62D1BDCAE8B001049D5 /* TaskType.swift */, - 1413F62B1BDCAE56001049D5 /* TaskState.swift */, - 148513951B9BD15100FAB0FA /* TaskBuilder.swift */, + 142561B41BE31B9D00A4C974 /* TaskOperation.swift */, 14C2903F1BDE544B006F1BB3 /* TaskWaiter.swift */, 148513961B9BD15100FAB0FA /* Errors.swift */, 148513991B9BD15100FAB0FA /* TaskCondition.swift */, @@ -250,7 +250,6 @@ files = ( 148513A11B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift in Sources */, 14C290401BDE544B006F1BB3 /* TaskWaiter.swift in Sources */, - 1413F62C1BDCAE56001049D5 /* TaskState.swift in Sources */, 148513A01B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift in Sources */, 148513A31B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift in Sources */, 148513A81B9BD15100FAB0FA /* TaskCondition.swift in Sources */, @@ -259,11 +258,12 @@ 1485139E1B9BD15100FAB0FA /* NegateTaskCondition.swift in Sources */, 148513A61B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift in Sources */, 148513B11B9BD9A500FAB0FA /* TimeoutTaskObserver.swift in Sources */, + 142561B51BE31B9D00A4C974 /* TaskOperation.swift in Sources */, 1413F62E1BDCAE8B001049D5 /* TaskType.swift in Sources */, 148513A21B9BD15100FAB0FA /* ReachabilityTaskCondition.swift in Sources */, 148513A71B9BD15100FAB0FA /* Task.swift in Sources */, 148513A51B9BD15100FAB0FA /* Errors.swift in Sources */, - 148513A41B9BD15100FAB0FA /* TaskBuilder.swift in Sources */, + 148513A41B9BD15100FAB0FA /* AsyncAwait.swift in Sources */, 148513AE1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift in Sources */, 1485139D1B9BD15100FAB0FA /* DelayTaskCondition.swift in Sources */, 1485139F1B9BD15100FAB0FA /* SilentTaskCondition.swift in Sources */, @@ -315,7 +315,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -361,7 +361,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos watchsimulator"; @@ -379,7 +379,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 149; + CURRENT_PROJECT_VERSION = 163; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -401,7 +401,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 149; + CURRENT_PROJECT_VERSION = 163; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift b/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift index d5c49bc..faae52e 100644 --- a/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift +++ b/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift @@ -20,11 +20,7 @@ public final class TimeoutTaskObserver: TaskObserver { let when = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * Double(NSEC_PER_SEC))) let queue: dispatch_queue_t - if #available(OSXApplicationExtension 10.10, *) { - queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) - } else { - queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - } + queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) dispatch_after(when, queue) { weakTask?.cancel() diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift index 973b335..12a0e2b 100644 --- a/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift @@ -109,22 +109,8 @@ public final class RemoteNotificationPermissionTaskCondition: TaskCondition { extension UIApplication { - private struct AssociatedKeys { - private static var remoteNotificationPermissionTaskCondition = "com.alecrim.AlecrimAsyncKit.UIApplication.RemoteNotificationPermissionTaskCondition" - } - - public var remoteNotificationPermissionTaskCondition: RemoteNotificationPermissionTaskCondition { - get { - if let value = objc_getAssociatedObject(self, &AssociatedKeys.remoteNotificationPermissionTaskCondition) as? RemoteNotificationPermissionTaskCondition { - return value - } - else { - let newValue = RemoteNotificationPermissionTaskCondition(application: self) - objc_setAssociatedObject(self, &AssociatedKeys.remoteNotificationPermissionTaskCondition, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - return newValue - } - } + public func remoteNotificationPermissionTaskCondition() -> RemoteNotificationPermissionTaskCondition { + return RemoteNotificationPermissionTaskCondition(application: self) } } diff --git a/Source/AlecrimAsyncKit/Core/AsyncAwait.swift b/Source/AlecrimAsyncKit/Core/AsyncAwait.swift new file mode 100644 index 0000000..ffb9527 --- /dev/null +++ b/Source/AlecrimAsyncKit/Core/AsyncAwait.swift @@ -0,0 +1,127 @@ +// +// AsyncAwait.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-05-10. +// Copyright (c) 2015 Alecrim. All rights reserved. +// + +import Foundation + +private let _defaultTaskQueue: NSOperationQueue = { + let queue = NSOperationQueue() + queue.name = "com.alecrim.AlecrimAsyncKit.Task" + queue.qualityOfService = .Default + queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount + + return queue +}() + +// MARK: - + +public typealias TaskPriority = NSOperationQueuePriority + +// MARK: - async + +@warn_unused_result +public func async(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, observers: [TaskObserver]? = nil, closure: () -> V) -> NonFailableTask { + return asyncEx(queue, qualityOfService: qualityOfService, observers: observers) { task in + let value = closure() + task.finishWithValue(value) + } +} + +@warn_unused_result +public func async(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, condition: TaskCondition, observers: [TaskObserver]? = nil, closure: () throws -> V) -> Task { + return async(queue, qualityOfService: qualityOfService, conditions: [condition], observers: observers, closure: closure) +} + +@warn_unused_result +public func async(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, conditions: [TaskCondition]? = nil, observers: [TaskObserver]? = nil, closure: () throws -> V) -> Task { + return asyncEx(queue, qualityOfService: qualityOfService, observers: observers) { task in + do { + let value = try closure() + task.finishWithValue(value) + } + catch let error { + task.finishWithError(error) + } + } +} + +// MARK: - asyncEx + +@warn_unused_result +public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, taskPriority: TaskPriority? = nil, observers: [TaskObserver]? = nil, closure: (NonFailableTask) -> Void) -> NonFailableTask { + return taskWithQueue(queue, qualityOfService: qualityOfService, taskPriority: taskPriority, conditions: nil, observers: observers, closure: closure) +} + +@warn_unused_result +public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, taskPriority: TaskPriority? = nil, condition: TaskCondition, observers: [TaskObserver]? = nil, closure: (Task) -> Void) -> Task { + return asyncEx(queue, qualityOfService: qualityOfService, taskPriority: taskPriority, conditions: [condition], observers: observers, closure: closure) +} + + +@warn_unused_result +public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, qualityOfService: NSQualityOfService? = nil, taskPriority: TaskPriority? = nil, conditions: [TaskCondition]? = nil, observers: [TaskObserver]? = nil, closure: (Task) -> Void) -> Task { + return taskWithQueue(queue, qualityOfService: qualityOfService, taskPriority: taskPriority, conditions: conditions, observers: observers, closure: closure) +} + + +// MARK: - await + +public func await(@noescape closure: () -> NonFailableTask) -> V { + let task = closure() + return await(task) +} + +public func await(task: NonFailableTask) -> V { + task.waitUntilFinished() + return task.value +} + +public func await(@noescape closure: () -> Task) throws -> V { + let task = closure() + return try await(task) +} + +public func await(task: Task) throws -> V { + task.waitUntilFinished() + + if let error = task.error { + throw error + } + + return task.value +} + + +// MARK: - + +private func taskWithQueue(queue: NSOperationQueue, qualityOfService: NSQualityOfService?, taskPriority: TaskPriority?, conditions: [TaskCondition]?, observers: [TaskObserver]?, closure: (T) -> Void) -> T { + assert(queue.maxConcurrentOperationCount == NSOperationQueueDefaultMaxConcurrentOperationCount || queue.maxConcurrentOperationCount > 1, "Task `queue` cannot be the main queue nor a serial queue.") + + // + let task = T(conditions: conditions, observers: observers, closure: closure) + + // + if let operation = task as? TaskOperation { + if let qualityOfService = qualityOfService { + operation.qualityOfService = qualityOfService + } + + if let taskPriority = taskPriority { + operation.queuePriority = taskPriority + } + + // + operation.willEnqueue() + queue.addOperation(operation) + } + else { + fatalError() + } + + // + return task +} diff --git a/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift b/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift index d79bd4e..5bb1397 100644 --- a/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift @@ -51,18 +51,21 @@ public final class MutuallyExclusiveTaskCondition: TaskCondition { let semaphore: dispatch_semaphore_t - withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) - - if self.mutuallyExclusiveSemaphores[categoryName] == nil { - semaphore = dispatch_semaphore_create(1) - self.mutuallyExclusiveSemaphores[categoryName] = (semaphore, 1) - } - else { - semaphore = self.mutuallyExclusiveSemaphores[categoryName]!.semaphore - self.mutuallyExclusiveSemaphores[categoryName]!.count++ + do { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) + defer { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + } + + if self.mutuallyExclusiveSemaphores[categoryName] == nil { + semaphore = dispatch_semaphore_create(1) + self.mutuallyExclusiveSemaphores[categoryName] = (semaphore, 1) + } + else { + semaphore = self.mutuallyExclusiveSemaphores[categoryName]!.semaphore + self.mutuallyExclusiveSemaphores[categoryName]!.count++ + } } - - withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) } @@ -70,18 +73,21 @@ public final class MutuallyExclusiveTaskCondition: TaskCondition { internal static func decrement(categoryName: String) { let semaphore: dispatch_semaphore_t - withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) - - semaphore = self.mutuallyExclusiveSemaphores[categoryName]!.semaphore - - self.mutuallyExclusiveSemaphores[categoryName]!.count-- - - if self.mutuallyExclusiveSemaphores[categoryName]!.count == 0 { - self.mutuallyExclusiveSemaphores.removeValueForKey(categoryName) + do { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) + defer { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + } + + semaphore = self.mutuallyExclusiveSemaphores[categoryName]!.semaphore + + self.mutuallyExclusiveSemaphores[categoryName]!.count-- + + if self.mutuallyExclusiveSemaphores[categoryName]!.count == 0 { + self.mutuallyExclusiveSemaphores.removeValueForKey(categoryName) + } } - withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) - dispatch_semaphore_signal(semaphore) } diff --git a/Source/AlecrimAsyncKit/Core/Task.swift b/Source/AlecrimAsyncKit/Core/Task.swift index 7292b2d..74e0e7b 100644 --- a/Source/AlecrimAsyncKit/Core/Task.swift +++ b/Source/AlecrimAsyncKit/Core/Task.swift @@ -8,168 +8,168 @@ import Foundation -// MARK: - protocols +public class BaseTask: TaskOperation, TaskWithValueType { -internal protocol BaseTaskDelegate: class { - func task(task: BaseTask, didChangeToState state: TaskState) -} - - -// MARK: - classes - -public class BaseTask: BaseTaskType { - // MARK: - public typealias ValueType = V // MARK: - - private final var _state: TaskState = TaskState.Initialized - internal final var state: TaskState { - get { - self.willAccessValue() - defer { self.didAccessValue() } - - return self._state - } - set { - self.setState(state: newValue, lock: true) - } + private final var valueSpinlock = OS_SPINLOCK_INIT + + private final func willAccessValue() { + withUnsafeMutablePointer(&self.valueSpinlock, OSSpinLockLock) } - private func setState(state newValue: TaskState, lock: Bool) { - do { - if lock { - self.willAccessValue() - } - - defer { - if lock { - self.didAccessValue() - } - } + private final func didAccessValue() { + withUnsafeMutablePointer(&self.valueSpinlock, OSSpinLockUnlock) + } - guard newValue != self._state else { return } - guard self._state != .Finished else { return } - - assert(self._state.canTransitionToState(newValue)) + + // MARK: - + + public private(set) final var value: V! + + public func finishWithValue(value: V) { + self.willAccessValue() + defer { + self.didAccessValue() - self._state = newValue + self.finishOperation() - if self._state == .Finished { - dispatch_group_leave(self.dispatchGroup) + if let progress = self._progress { + progress.completedUnitCount = progress.totalUnitCount } } - self.delegate?.task(self, didChangeToState: self._state) + guard self.value == nil else { return } + + self.value = value + } + + // MARK: - + + public override final func waitUntilFinished() { + assert(!NSThread.isMainThread(), "Cannot wait task on main thread.") + super.waitUntilFinished() } + // MARK: - + private var _progress: NSProgress? + public final var progress: NSProgress { + if self._progress == nil { + self._progress = TaskProgress(task: self) + } + + return self._progress! + } + // MARK: - - public private(set) final var value: V! + private final var closure: (() -> Void)? + private override init(conditions: [TaskCondition]?, observers: [TaskObserver]?) { + super.init(conditions: conditions, observers: observers) + } - public final var finished: Bool { return self.state == .Finished } - // MARK: - - - internal final var progressAssigned = false - public lazy final var progress: NSProgress = { - let p = NSProgress() - p.totalUnitCount = 1 - p.completedUnitCount = 0 + internal override final func execute() { + super.execute() - self.progressAssigned = true - - return p - }() + if !self.cancelled, let closure = self.closure { + closure() + } + else { + self.finishOperation() + } + } +} + +public final class Task: BaseTask, InitializableTaskType, FailableTaskType { + + // MARK: - + + private var _cancellationHandler: (() -> Void)? public var cancellationHandler: (() -> Void)? { - get { return self.progress.cancellationHandler } + get { + return self._cancellationHandler + } set { - if let oldValue = self.cancellationHandler { + if let oldValue = self._cancellationHandler { if let newValue = newValue { - self.progress.cancellationHandler = { + self._cancellationHandler = { oldValue() newValue() } } else { - self.progress.cancellationHandler = oldValue + self._cancellationHandler = newValue } } else { - self.progress.cancellationHandler = newValue + self._cancellationHandler = newValue } } } - - // MARK: - - private final let dispatchGroup: dispatch_group_t = dispatch_group_create() - private final var spinlock = OS_SPINLOCK_INIT - - // MARK: - - - private final var closure: (() -> Void)! - - // MARK: - - - internal /* weak */ var delegate: BaseTaskDelegate? - - - // MARK: - - - private init() { - dispatch_group_enter(self.dispatchGroup) - } - - deinit { - assert(self.finished, "Either value or error were never assigned or task was never cancelled.") - } - - internal func execute() { - self.closure() - } - - internal func wait() throws { - assert(!NSThread.isMainThread(), "Cannot wait task on main thread.") - dispatch_group_wait(self.dispatchGroup, DISPATCH_TIME_FOREVER) + public override func cancel() { + if let cancellationHandler = self.cancellationHandler { + self.cancellationHandler = nil + cancellationHandler() + } + + self.willAccessValue() + defer { + self.didAccessValue() + super.cancel() + self.finishOperation() + } + + guard self.value == nil && self.error == nil else { return } + + self.error = NSError.userCancelledError() } // MARK: - - public func finishWithValue(value: V) { + public private(set) var error: ErrorType? + + public override func finishWithValue(value: V) { self.willAccessValue() - defer { self.didAccessValue() } + defer { + self.didAccessValue() + + self.finishOperation() + + if let progress = self._progress { + progress.completedUnitCount = progress.totalUnitCount + } + } - guard self.value == nil else { return } + guard self.value == nil && self.error == nil else { return } - self.setState(state: .Finishing, lock: false) self.value = value - self.setState(state: .Finished, lock: false) - } - - // MARK: - - - private final func willAccessValue() { - withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) } - private final func didAccessValue() { - withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + public func finishWithError(error: ErrorType) { + self.willAccessValue() + defer { + self.didAccessValue() + self.finishOperation() + } + + guard self.value == nil && self.error == nil else { return } + + self.error = error } -} - -public final class NonFailableTask: BaseTask, NonFailableTaskType { - // MARK: - - public init(closure: (NonFailableTask) -> Void) { - super.init() + public required init(conditions: [TaskCondition]?, observers: [TaskObserver]?, closure: (Task) -> Void) { + super.init(conditions: conditions, observers: observers) self.closure = { [unowned self] in closure(self) @@ -178,71 +178,65 @@ public final class NonFailableTask: BaseTask, NonFailableTaskType { } -public final class Task: BaseTask, FailableTaskType { - - // MARK: - - - public private(set) var error: ErrorType? - public var cancelled: Bool { - self.willAccessValue() - defer { self.didAccessValue() } - - return self.error?.userCancelled ?? false - } - - // MARK: - - - public init(closure: (Task) -> Void) { - super.init() +public final class NonFailableTask: BaseTask, InitializableTaskType, NonFailableTaskType { + + public required init(conditions: [TaskCondition]?, observers: [TaskObserver]?, closure: (NonFailableTask) -> Void) { + super.init(conditions: conditions, observers: observers) self.closure = { [unowned self] in closure(self) } } - // MARK: - - - internal override func wait() throws { - try super.wait() - - if let error = self.error { - throw error - } + @available(*, unavailable) + public override func cancel() { + super.cancel() } + +} + + +// MARK: - + +private final class TaskProgress: NSProgress { - // MARK: - - - public func cancel() { - self.finishWithError(NSError.userCancelledError()) - } + private unowned let task: TaskType - public override func finishWithValue(value: V) { - self.finishWithValue(value, error: nil) + private init(task: TaskType) { + self.task = task + super.init(parent: nil, userInfo: nil) + + self.totalUnitCount = 1 + self.cancellable = self.task is CancellableTaskType } - public func finishWithError(error: ErrorType) { - self.finishWithValue(nil, error: error) + // + private override var cancellationHandler: (() -> Void)? { + get { + if let cancellableTask = self.task as? CancellableTaskType { + return cancellableTask.cancellationHandler + } + else { + return super.cancellationHandler + } + } + set { + if let cancellableTask = self.task as? CancellableTaskType { + cancellableTask.cancellationHandler = newValue + } + else { + super.cancellationHandler = newValue + } + } } - public func finishWithValue(value: V!, error: ErrorType?) { - self.willAccessValue() - defer { self.didAccessValue() } - - guard self.value == nil && self.error == nil else { return } - assert(value != nil || error != nil, "Invalid combination of value/error.") - - self.setState(state: .Finishing, lock: false) + private override func cancel() { + super.cancel() - if let error = error { - self.value = nil - self.error = error + if let cancellableTask = self.task as? CancellableTaskType { + cancellableTask.cancel() } - else { - self.value = value - self.error = nil - } - - self.setState(state: .Finished, lock: false) } } + diff --git a/Source/AlecrimAsyncKit/Core/TaskBuilder.swift b/Source/AlecrimAsyncKit/Core/TaskBuilder.swift deleted file mode 100644 index 1831665..0000000 --- a/Source/AlecrimAsyncKit/Core/TaskBuilder.swift +++ /dev/null @@ -1,270 +0,0 @@ -// -// TaskBuilder.swift -// AlecrimAsyncKit -// -// Created by Vanderlei Martinelli on 2015-05-10. -// Copyright (c) 2015 Alecrim. All rights reserved. -// - -import Foundation - -private let _defaultTaskQueue: NSOperationQueue = { - let queue = NSOperationQueue() - queue.name = "com.alecrim.AlecrimAsyncKit.Task" - - if #available(OSXApplicationExtension 10.10, *) { - queue.qualityOfService = .Background - } - - queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount - - return queue -}() - -private let _defaultConditionEvaluationQueue: NSOperationQueue = { - let queue = NSOperationQueue() - queue.name = "com.alecrim.AlecrimAsyncKit.ConditionEvaluation" - - if #available(OSXApplicationExtension 10.10, *) { - queue.qualityOfService = .Background - } - - queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount - - return queue -}() - -// MARK: - async - -@warn_unused_result -public func async(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, observers: [TaskObserver]? = nil, closure: () -> V) -> NonFailableTask { - return asyncEx(queue, userInitiated: userInitiated, observers: observers) { task in - let value = closure() - task.finishWithValue(value) - } -} - -@warn_unused_result -public func async(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, condition: TaskCondition, observers: [TaskObserver]? = nil, closure: () throws -> V) -> Task { - return async(queue, userInitiated: userInitiated, conditions: [condition], observers: observers, closure: closure) -} - -@warn_unused_result -public func async(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, conditions: [TaskCondition]? = nil, observers: [TaskObserver]? = nil, closure: () throws -> V) -> Task { - return asyncEx(queue, userInitiated: userInitiated, observers: observers) { task in - do { - let value = try closure() - task.finishWithValue(value) - } - catch let error { - task.finishWithError(error) - } - } -} - -// MARK: - asyncEx - -@warn_unused_result -public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, observers: [TaskObserver]? = nil, closure: (NonFailableTask) -> Void) -> NonFailableTask { - return TaskBuilder(queue: queue, userInitiated: userInitiated, conditions: nil, observers: observers, closure: closure).start() -} - -@warn_unused_result -public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, condition: TaskCondition, observers: [TaskObserver]? = nil, closure: (Task) -> Void) -> Task { - return asyncEx(queue, userInitiated: userInitiated, conditions: [condition], observers: observers, closure: closure) -} - - -@warn_unused_result -public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, userInitiated: Bool = false, conditions: [TaskCondition]? = nil, observers: [TaskObserver]? = nil, closure: (Task) -> Void) -> Task { - return TaskBuilder(queue: queue, userInitiated: userInitiated, conditions: conditions, observers: observers, closure: closure).start() -} - - -// MARK: - await - -public func await(@noescape closure: () -> NonFailableTask) -> V { - let task = closure() - return await(task) -} - -public func await(task: NonFailableTask) -> V { - try! task.wait() - return task.value -} - -public func await(@noescape closure: () -> Task) throws -> V { - let task = closure() - return try await(task) -} - -public func await(task: Task) throws -> V { - try task.wait() - return task.value -} - - -// MARK: - TaskBuilder - -private final class TaskBuilder { - - private let queue: NSOperationQueue - private let userInitiated: Bool - private let conditions: [TaskCondition]? - private let observers: [TaskObserver]? - private var closure: ((T) -> Void)! - - private init!(queue: NSOperationQueue, userInitiated: Bool, conditions: [TaskCondition]?, observers: [TaskObserver]?, closure: (T) -> Void) { - assert(queue.maxConcurrentOperationCount == NSOperationQueueDefaultMaxConcurrentOperationCount || queue.maxConcurrentOperationCount > 1, "Task `queue` cannot be the main queue nor a serial queue.") - - // - self.queue = queue - self.userInitiated = userInitiated - self.conditions = conditions - self.observers = observers - self.closure = closure - } - - private func start() -> T { - // - let task = T(closure: self.closure) as! BaseTask - self.closure = nil - task.delegate = self - - // - task.state = .Pending - - // - return task as! T - } - -} - -extension TaskBuilder: BaseTaskDelegate { - - private func task(task: BaseTask, didChangeToState state: TaskState) { - switch state { - case .Pending: - if let conditions = self.conditions where !conditions.isEmpty { - task.state = .EvaluatingConditions - } - else { - task.state = .Ready - } - - case .EvaluatingConditions: - if let conditions = self.conditions where !conditions.isEmpty, let ft = task as? Task { - let conditionEvaluationOperation = NSBlockOperation { - // - let mutuallyExclusiveConditions = conditions.flatMap { $0 as? MutuallyExclusiveTaskCondition } - if !mutuallyExclusiveConditions.isEmpty { - mutuallyExclusiveConditions.forEach { mutuallyExclusiveCondition in - MutuallyExclusiveTaskCondition.increment(mutuallyExclusiveCondition.categoryName) - } - } - - // - do { - try await(TaskCondition.asyncEvaluateConditions(conditions)) - ft.state = .Ready - } - catch TaskConditionError.NotSatisfied { - ft.cancel() - } - catch TaskConditionError.Failed(let innerError) { - ft.finishWithError(innerError) - } - catch let error { - ft.finishWithError(error) - } - } - - if self.userInitiated { - conditionEvaluationOperation.qualityOfService = .UserInitiated - } - else { - conditionEvaluationOperation.qualityOfService = self.queue.qualityOfService - } - - _defaultConditionEvaluationQueue.addOperation(conditionEvaluationOperation) - } - else { - task.state = .Ready - } - - case .Ready: - // - if let observers = self.observers where !observers.isEmpty { - observers.forEach { $0.taskWillStartClosure?(task) } - } - - // enqueue - let operation = NSBlockOperation { - guard task.state == .Ready else { return } - - task.state = .Executing - task.execute() - } - - if self.userInitiated { - if #available(OSXApplicationExtension 10.10, *) { - operation.qualityOfService = .UserInitiated - } - } - else { - if #available(OSXApplicationExtension 10.10, *) { - operation.qualityOfService = self.queue.qualityOfService - } - } - - self.queue.addOperation(operation) - - case .Executing: - if let observers = self.observers where !observers.isEmpty { - observers.forEach { $0.taskDidStartClosure?(task) } - } - - // transition to .Finishing is handled by task class - - case .Finishing: - if let observers = self.observers where !observers.isEmpty { - observers.forEach { $0.taskWillFinishClosure?(task) } - } - - // transition to .Finished is handled by task class - - case .Finished: - // - if let conditions = self.conditions where !conditions.isEmpty, let _ = task as? Task { - let mutuallyExclusiveConditions = conditions.flatMap { $0 as? MutuallyExclusiveTaskCondition } - if !mutuallyExclusiveConditions.isEmpty { - mutuallyExclusiveConditions.forEach { mutuallyExclusiveCondition in - MutuallyExclusiveTaskCondition.decrement(mutuallyExclusiveCondition.categoryName) - } - } - } - - // - if let observers = self.observers where !observers.isEmpty { - observers.forEach { $0.taskDidFinishClosure?(task) } - } - - // - if task.progressAssigned { - if task.value != nil { - task.progress.completedUnitCount = task.progress.totalUnitCount - } - else if let ft = task as? Task, let error = ft.error as? NSError where error.userCancelled { - task.progress.cancellationHandler?() - } - } - - // to ensure - task.delegate = nil - - default: - break - } - } - -} diff --git a/Source/AlecrimAsyncKit/Core/TaskCondition.swift b/Source/AlecrimAsyncKit/Core/TaskCondition.swift index f58395f..3cfe9b4 100644 --- a/Source/AlecrimAsyncKit/Core/TaskCondition.swift +++ b/Source/AlecrimAsyncKit/Core/TaskCondition.swift @@ -11,11 +11,7 @@ import Foundation private let _defaultTaskConditionQueue: NSOperationQueue = { let queue = NSOperationQueue() queue.name = "com.alecrim.AlecrimAsyncKit.TaskCondition" - - if #available(OSXApplicationExtension 10.10, *) { - queue.qualityOfService = .Background - } - + queue.qualityOfService = .Default queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount return queue @@ -135,17 +131,24 @@ public class TaskCondition { extension TaskCondition { internal static func asyncEvaluateConditions(conditions: [TaskCondition]) -> Task { - return async(_defaultTaskConditionQueue) { - for condition in conditions { - if let subconditions = condition.subconditions where !subconditions.isEmpty { - try await(TaskCondition.asyncEvaluateConditions(subconditions)) - } - - if let dependencyTask = condition.dependencyTaskClosure() { - try await(dependencyTask) + return asyncEx(_defaultTaskConditionQueue) { task in + do { + for condition in conditions { + if let subconditions = condition.subconditions where !subconditions.isEmpty { + try await(TaskCondition.asyncEvaluateConditions(subconditions)) + } + + if let dependencyTask = condition.dependencyTaskClosure() { + try await(dependencyTask) + } + + try await(condition.asyncEvaluate()) } - try await(condition.asyncEvaluate()) + task.finish() + } + catch let error { + task.finishWithError(error) } } } diff --git a/Source/AlecrimAsyncKit/Core/TaskObserver.swift b/Source/AlecrimAsyncKit/Core/TaskObserver.swift index ab95172..aeb892d 100644 --- a/Source/AlecrimAsyncKit/Core/TaskObserver.swift +++ b/Source/AlecrimAsyncKit/Core/TaskObserver.swift @@ -10,31 +10,31 @@ import Foundation public class TaskObserver { - internal final var taskWillStartClosure: ((Any) -> Void)? - internal final var taskDidStartClosure: ((Any) -> Void)? + internal final var taskWillStartClosure: ((TaskType) -> Void)? + internal final var taskDidStartClosure: ((TaskType) -> Void)? - internal final var taskWillFinishClosure: ((Any) -> Void)? - internal final var taskDidFinishClosure: ((Any) -> Void)? + internal final var taskWillFinishClosure: ((TaskType) -> Void)? + internal final var taskDidFinishClosure: ((TaskType) -> Void)? public init() { } - public final func taskWillStart(closure: (Any) -> Void) -> Self { + public final func taskWillStart(closure: (TaskType) -> Void) -> Self { self.taskWillStartClosure = closure return self } - public final func taskDidStart(closure: (Any) -> Void) -> Self { + public final func taskDidStart(closure: (TaskType) -> Void) -> Self { self.taskDidStartClosure = closure return self } - public final func taskWillFinish(closure: (Any) -> Void) -> Self { + public final func taskWillFinish(closure: (TaskType) -> Void) -> Self { self.taskWillFinishClosure = closure return self } - public final func taskDidFinish(closure: (Any) -> Void) -> Self { + public final func taskDidFinish(closure: (TaskType) -> Void) -> Self { self.taskDidFinishClosure = closure return self } diff --git a/Source/AlecrimAsyncKit/Core/TaskOperation.swift b/Source/AlecrimAsyncKit/Core/TaskOperation.swift new file mode 100644 index 0000000..7eafe2e --- /dev/null +++ b/Source/AlecrimAsyncKit/Core/TaskOperation.swift @@ -0,0 +1,246 @@ +// +// TaskOperation.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-10-29. +// Copyright © 2015 Alecrim. All rights reserved. +// + +import Foundation + +private let _conditionEvaluationQueue: NSOperationQueue = { + let queue = NSOperationQueue() + queue.name = "com.alecrim.AlecrimAsyncKit.ConditionEvaluation" + queue.qualityOfService = .Default + queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount + + return queue +}() + + +public class TaskOperation: NSOperation, TaskType { + + private enum StateKey: String { + case Executing = "isExecuting" + case Finished = "isFinished" + case Ready = "isReady" + } + + // MARK: - + + private var stateSpinlock = OS_SPINLOCK_INIT + + private func willAccessState() { + withUnsafeMutablePointer(&self.stateSpinlock, OSSpinLockLock) + } + + private func didAccessState() { + withUnsafeMutablePointer(&self.stateSpinlock, OSSpinLockUnlock) + } + + private func willChangeValueForStateKey(stateKey: StateKey) { + self.willChangeValueForKey(stateKey.rawValue) + self.willAccessState() + } + + private func didChangeValueForStateKey(stateKey: StateKey) { + self.didAccessState() + self.didChangeValueForKey(stateKey.rawValue) + } + + // + + private var __executing: Bool = false + public private(set) override var executing: Bool { + get { + self.willAccessState() + defer { self.didAccessState() } + + return self.__executing + } + set { + self.willChangeValueForStateKey(.Executing) + defer { self.didChangeValueForStateKey(.Executing) } + + self.__executing = newValue + } + } + + private var __finished: Bool = false + public private(set) override var finished: Bool { + get { + self.willAccessState() + defer { self.didAccessState() } + + return self.__finished + } + set { + self.willChangeValueForStateKey(.Finished) + defer { self.didChangeValueForStateKey(.Finished) } + + self.__finished = newValue + } + } + + private var __ready: Bool = false + public private(set) override var ready: Bool { + get { + self.willAccessState() + defer { self.didAccessState() } + + return self.__ready + } + set { + self.willChangeValueForStateKey(.Ready) + defer { self.didChangeValueForStateKey(.Ready) } + + self.__ready = newValue + } + } + + // MARK: - + + public override func cancel() { + super.cancel() + + self.executing = false + self.ready = true + } + + // MARK: - + + internal final func willEnqueue() { + self.evaluateConditions() + } + + internal final func evaluateConditions() { + guard !self.cancelled, let conditions = self.conditions where !conditions.isEmpty else { + self.ready = true + return + } + + // + let evaluateConditionsOperation = NSBlockOperation { + do { + defer { + self.ready = true + } + + if !self.cancelled { + try await(TaskCondition.asyncEvaluateConditions(conditions)) + } + } + catch TaskConditionError.NotSatisfied { + self.cancel() + } + catch TaskConditionError.Failed(let innerError) { + if let task = self as? TaskWithErrorType { + task.finishWithError(innerError) + } + else { + self.cancel() + } + } + catch let error { + if let task = self as? TaskWithErrorType { + task.finishWithError(error) + } + else { + self.cancel() + } + } + } + + // + _conditionEvaluationQueue.addOperation(evaluateConditionsOperation) + } + + internal let u = NSUUID().UUIDString + + + internal func execute() { + // to be overrided calling super + self.ready = false + self.executing = true + + // + if let mutuallyExclusiveConditions = self.conditions?.flatMap({ $0 as? MutuallyExclusiveTaskCondition }) where !mutuallyExclusiveConditions.isEmpty { + mutuallyExclusiveConditions.forEach { MutuallyExclusiveTaskCondition.increment($0.categoryName) } + var decremented = false + + self.completionBlock = { + if !decremented { + decremented = true + mutuallyExclusiveConditions.forEach { MutuallyExclusiveTaskCondition.decrement($0.categoryName) } + } + } + } + + // + if let observers = self.observers where !observers.isEmpty { + observers.forEach { $0.taskDidStartClosure?(self) } + } + } + + private var hasFinishedAlready = false + internal final func finishOperation() { + guard self.hasStarted else { + self.cancel() + return + } + + guard !self.hasFinishedAlready else { return } + self.hasFinishedAlready = true + + if let observers = self.observers where !observers.isEmpty { + observers.forEach { $0.taskWillFinishClosure?(self) } + } + + self.ready = false + self.executing = false + self.finished = true + + if let observers = self.observers where !observers.isEmpty { + observers.forEach { $0.taskDidFinishClosure?(self) } + } + } + + // MARK: - + + private let conditions: [TaskCondition]? + private let observers: [TaskObserver]? + + internal init(conditions: [TaskCondition]?, observers: [TaskObserver]?) { + self.conditions = conditions + self.observers = observers + + super.init() + } + + // MARK : - + + private var hasStarted = false + public override final func start() { + self.hasStarted = true + + super.start() + + if self.cancelled { + self.finishOperation() + } + } + + public override func main() { + if let observers = self.observers where !observers.isEmpty { + observers.forEach { $0.taskWillStartClosure?(self) } + } + + if self.cancelled { + self.finishOperation() + } + else { + self.execute() + } + } + +} diff --git a/Source/AlecrimAsyncKit/Core/TaskState.swift b/Source/AlecrimAsyncKit/Core/TaskState.swift deleted file mode 100644 index 1a67869..0000000 --- a/Source/AlecrimAsyncKit/Core/TaskState.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// TaskState.swift -// AlecrimAsyncKit -// -// Created by Vanderlei Martinelli on 2015-10-25. -// Copyright © 2015 Alecrim. All rights reserved. -// - -import Foundation - -public enum TaskState: Int { - case Initialized - case Pending - case EvaluatingConditions - case Ready - case Executing - case Finishing - case Finished - - internal func canTransitionToState(newState: TaskState) -> Bool { - switch (self, newState) { - case (.Initialized, .Pending): - return true - - case (.Pending, .EvaluatingConditions): - return true - - case (.Pending, .Ready): - return true - - case (.Pending, .Finishing): - return true - - case (.EvaluatingConditions, .Ready): - return true - - case (.EvaluatingConditions, .Finishing): - return true - - case (.Ready, .Executing): - return true - - case (.Ready, .Finishing): - return true - - case (.Executing, .Finishing): - return true - - case (.Finishing, .Finished): - return true - - default: - return false - } - } - -} diff --git a/Source/AlecrimAsyncKit/Core/TaskType.swift b/Source/AlecrimAsyncKit/Core/TaskType.swift index 4f5603d..1dd6650 100644 --- a/Source/AlecrimAsyncKit/Core/TaskType.swift +++ b/Source/AlecrimAsyncKit/Core/TaskType.swift @@ -8,40 +8,64 @@ import Foundation -public protocol CancellableTaskType: class { +public protocol TaskType: class { + func waitUntilFinished() +} + +public protocol InitializableTaskType: TaskType { + init(conditions: [TaskCondition]?, observers: [TaskObserver]?, closure: (Self) -> Void) +} + +public protocol CancellableTaskType: TaskType { var cancelled: Bool { get } + var cancellationHandler: (() -> Void)? { get set } + func cancel() } -public protocol BaseTaskType: class { +public protocol TaskWithErrorType: TaskType { + var error: ErrorType? { get } + func finishWithError(error: ErrorType) +} + +public protocol TaskWithValueType: TaskType { typealias ValueType - var finished: Bool { get } - var value: Self.ValueType! { get } - - var progress: NSProgress { get } - func finishWithValue(value: Self.ValueType) - } -public protocol TaskType: BaseTaskType { - init(closure: (Self) -> Void) +public protocol FailableTaskType: CancellableTaskType, TaskWithValueType, TaskWithErrorType { + func finishWithValue(value: Self.ValueType!, error: ErrorType?) } +public protocol NonFailableTaskType: TaskWithValueType { -public protocol NonFailableTaskType: TaskType { } -public protocol FailableTaskType: TaskType, CancellableTaskType { - var error: ErrorType? { get } +// MARK: - + +extension CancellableTaskType { - func finishWithValue(value: Self.ValueType!, error: ErrorType?) - func finishWithError(error: ErrorType) + public func forwardCancellationTo(task: CancellableTaskType) -> Self { + self.cancellationHandler = { [weak task] in + task?.cancel() + } + + return self + } + + public func inheritCancellationFrom(task: CancellableTaskType) -> Self { + task.forwardCancellationTo(self) + + return self + } + } -extension TaskType where Self.ValueType == Void { +// MARK: - + +extension TaskWithValueType where Self.ValueType == Void { public func finish() { self.finishWithValue(()) @@ -49,25 +73,33 @@ extension TaskType where Self.ValueType == Void { } -extension NonFailableTaskType { +// MARK: - + +extension FailableTaskType { - public func continueWithTask(task: T) { - try! (task as! NonFailableTask).wait() - self.finishWithValue(task.value) + public func finishWithValue(value: Self.ValueType!, error: ErrorType?) { + if let error = error { + self.finishWithError(error) + } + else { + self.finishWithValue(value) + } + } + + public func continueWithTask(task: T) { + task.waitUntilFinished() + self.finishWithValue(task.value, error: task.error) } } -extension FailableTaskType { +// MARK: - + +extension NonFailableTaskType { - public func continueWithTask(task: T) { - do { - try (task as! Task).wait() - self.finishWithValue(task.value) - } - catch let error { - self.finishWithError(error) - } + public func continueWithTask(task: T) { + task.waitUntilFinished() + self.finishWithValue(task.value) } } diff --git a/Source/AlecrimAsyncKit/Core/TaskWaiter.swift b/Source/AlecrimAsyncKit/Core/TaskWaiter.swift index 8304447..03cc0f3 100644 --- a/Source/AlecrimAsyncKit/Core/TaskWaiter.swift +++ b/Source/AlecrimAsyncKit/Core/TaskWaiter.swift @@ -11,11 +11,7 @@ import Foundation private let _defaultTaskWaiterQueue: NSOperationQueue = { let queue = NSOperationQueue() queue.name = "com.alecrim.AlecrimAsyncKit.TaskWaiter" - - if #available(OSXApplicationExtension 10.10, *) { - queue.qualityOfService = .Background - } - + queue.qualityOfService = .Default queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount return queue