From 01fc16a6dcb01512bf07e2074b8551afcc829a03 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 21 Jan 2015 09:57:32 +0900 Subject: [PATCH 1/3] Refactor code, using fast & naive _StateMachine instead of dynamic but slow SwiftState. --- SwiftTask.xcodeproj/project.pbxproj | 6 + SwiftTask/SwiftTask.swift | 467 +++++++++------------------- SwiftTask/_StateMachine.swift | 118 +++++++ 3 files changed, 271 insertions(+), 320 deletions(-) create mode 100644 SwiftTask/_StateMachine.swift diff --git a/SwiftTask.xcodeproj/project.pbxproj b/SwiftTask.xcodeproj/project.pbxproj index d1ee1de..da2c968 100644 --- a/SwiftTask.xcodeproj/project.pbxproj +++ b/SwiftTask.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; }; 48A1E8221A366F9C007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F46DED4199EDF1000F97868 /* SwiftTask.framework */; }; 48A1E8231A366FA8007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */; }; + 48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */; }; + 48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */; }; 48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */; }; 48CD5A4619AEEC2E0042B9F1 /* SwiftTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F46DED9199EDF1000F97868 /* SwiftTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -53,6 +55,7 @@ 484552C91A4CF48A007770CA /* SwiftState.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftState.framework; path = Carthage/Checkouts/SwiftState/build/Debug/SwiftState.framework; sourceTree = ""; }; 48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = ""; }; 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeInferenceTests.swift; sourceTree = ""; }; + 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _StateMachine.swift; sourceTree = ""; }; 48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -118,6 +121,7 @@ isa = PBXGroup; children = ( 1F46DED9199EDF1000F97868 /* SwiftTask.h */, + 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */, 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */, 1F46DED7199EDF1000F97868 /* Supporting Files */, ); @@ -362,6 +366,7 @@ buildActionMask = 2147483647; files = ( 1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */, + 48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -402,6 +407,7 @@ buildActionMask = 2147483647; files = ( 48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */, + 48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftTask/SwiftTask.swift b/SwiftTask/SwiftTask.swift index db7b955..d1249c7 100644 --- a/SwiftTask/SwiftTask.swift +++ b/SwiftTask/SwiftTask.swift @@ -6,19 +6,17 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // -import SwiftState - // NOTE: nested type inside generic Task class is not allowed in Swift 1.1 -public enum TaskState: String, StateType, Printable +public enum TaskState: String, Printable { case Paused = "Paused" case Running = "Running" case Fulfilled = "Fulfilled" case Rejected = "Rejected" + case Cancelled = "Cancelled" case Any = "Any" - case Cancelled = "Cancelled" // NOTE: .Cancelled is never added to StateMachine's routes, but is returned via `task.state` - + public init(nilLiteral: Void) { self = Any @@ -30,43 +28,6 @@ public enum TaskState: String, StateType, Printable } } -internal enum _TaskEvent: String, StateEventType, Printable -{ - case Pause = "Pause" - case Resume = "Resume" - case Progress = "Progress" - case Fulfill = "Fulfill" - case Reject = "Reject" // also used in cancellation for simplicity - - // - // Private events to temporarily switch states (no event handlers) - // without invoking `configure.resume()`, - // i.e. - // - // `.Paused` (paused-init) - // => `.Running` (only while invoking `_performInitClosure()`) - // => `.Paused` (switch back) - // - // and finally by sending regular `.Resume` event: - // - // => `.Running` (again, but this time `configure.resume()` will be invoked) - // - case _InitResume = "_InitResume" - case _InitPause = "_InitPause" - - case Any = "Any" - - internal init(nilLiteral: Void) - { - self = Any - } - - internal var description: String - { - return self.rawValue - } -} - // NOTE: use class instead of struct to pass reference to closures so that future values can be stored public class TaskConfiguration { @@ -74,6 +35,11 @@ public class TaskConfiguration public var resume: (Void -> Void)? public var cancel: (Void -> Void)? + public init() + { + + } + internal func clear() { self.pause = nil @@ -84,25 +50,25 @@ public class TaskConfiguration public class Task: Printable { + public typealias ProgressTuple = (oldProgress: Progress?, newProgress: Progress) public typealias ErrorInfo = (error: Error?, isCancelled: Bool) - public typealias ProgressHandler = (Progress) -> Void - public typealias FulFillHandler = (Value) -> Void - public typealias RejectHandler = (Error) -> Void + public typealias ProgressHandler = (Progress -> Void) + public typealias FulfillHandler = (Value -> Void) + public typealias RejectHandler = (Error -> Void) public typealias Configuration = TaskConfiguration - public typealias ProgressTuple = (oldProgress: Progress?, newProgress: Progress) - public typealias BulkProgress = (completedCount: Int, totalCount: Int) + public typealias PromiseInitClosure = (fulfill: FulfillHandler, reject: RejectHandler) -> Void + public typealias InitClosure = (progress: ProgressHandler, fulfill: FulfillHandler, reject: RejectHandler, configure: TaskConfiguration) -> Void - public typealias PromiseInitClosure = (fulfill: FulFillHandler, reject: RejectHandler) -> Void - public typealias InitClosure = (progress: ProgressHandler, fulfill: FulFillHandler, reject: RejectHandler, configure: TaskConfiguration) -> Void + internal typealias _Machine = _StateMachine - internal typealias _RejectHandler = (ErrorInfo) -> Void - internal typealias _InitClosure = (machine: Machine, progress: ProgressHandler, fulfill: FulFillHandler, _reject: _RejectHandler, configure: TaskConfiguration) -> Void + internal typealias _InitClosure = (machine: _Machine, progress: ProgressHandler, fulfill: FulfillHandler, _reject: _RejectInfoHandler, configure: TaskConfiguration) -> Void - internal typealias Machine = StateMachine + internal typealias _ProgressTupleHandler = (ProgressTuple -> Void) + internal typealias _RejectInfoHandler = (ErrorInfo -> Void) - private var machine: Machine! + internal let _machine: _Machine // store initial parameters for cloning task when using `try()` internal let _weakified: Bool @@ -113,40 +79,30 @@ public class Task: Printable /// and will be set to `nil` afterward internal var _performInitClosure: (Void -> Void)? + public var state: TaskState { return self._machine.state } + /// progress value - public internal(set) var progress: Progress? + public var progress: Progress? { return self._machine.progress } /// fulfilled value - public internal(set) var value: Value? + public var value: Value? { return self._machine.value } /// rejected/cancelled tuple info - public internal(set) var errorInfo: ErrorInfo? + public var errorInfo: ErrorInfo? { return self._machine.errorInfo } public var name: String = "DefaultTask" - public var state: TaskState - { - // return .Cancelled if .Rejected & errorInfo.isCancelled=true - if self.machine.state == .Rejected { - if let errorInfo = self.errorInfo { - if errorInfo.isCancelled { - return .Cancelled - } - } - } - - return self.machine.state - } - public var description: String { var valueString: String? switch (self.state) { - case .Fulfilled: valueString = "value=\(self.value!)" - case .Rejected: fallthrough - case .Cancelled: valueString = "errorInfo=\(self.errorInfo!)" - default: valueString = "progress=\(self.progress)" + case .Fulfilled: + valueString = "value=\(self.value!)" + case .Rejected, .Cancelled: + valueString = "errorInfo=\(self.errorInfo!)" + default: + valueString = "progress=\(self.progress)" } return "<\(self.name); state=\(self.state.rawValue); \(valueString!))>" @@ -169,8 +125,9 @@ public class Task: Printable { self._weakified = weakified self._paused = paused + self._machine = _Machine(paused: paused) - let _initClosure: _InitClosure = { machine, progress, fulfill, _reject, configure in + let _initClosure: _InitClosure = { _, progress, fulfill, _reject, configure in // NOTE: don't expose rejectHandler with ErrorInfo (isCancelled) for public init initClosure(progress: progress, fulfill: fulfill, reject: { (error: Error?) in _reject(ErrorInfo(error: error, isCancelled: false)) }, configure: configure) } @@ -236,12 +193,13 @@ public class Task: Printable }) } - /// internal-init for accessing private `machine` inside `_initClosure` - /// (NOTE: _initClosure has _RejectHandler as argument) + /// internal-init for accessing `machine` inside `_initClosure` + /// (NOTE: _initClosure has _RejectInfoHandler as argument) internal init(weakified: Bool = false, paused: Bool = false, _initClosure: _InitClosure) { self._weakified = weakified self._paused = paused + self._machine = _Machine(paused: paused) self.setup(weakified, paused: paused, _initClosure) } @@ -251,69 +209,6 @@ public class Task: Printable // #if DEBUG // println("[init] \(self.name)") // #endif - - let configuration = Configuration() - - let initialState: TaskState = paused ? .Paused : .Running - - // NOTE: Swift 1.1 compiler fails if using [weak self] instead... - weak var weakSelf = self - - // setup state machine - self.machine = Machine(state: initialState) { - - $0.addRouteEvent(._InitPause, transitions: [.Running => .Paused]) - $0.addRouteEvent(._InitResume, transitions: [.Paused => .Running]) - - $0.addRouteEvent(.Pause, transitions: [.Running => .Paused]) - $0.addRouteEvent(.Resume, transitions: [.Paused => .Running]) - $0.addRouteEvent(.Progress, transitions: [.Running => .Running]) - $0.addRouteEvent(.Fulfill, transitions: [.Running => .Fulfilled]) - $0.addRouteEvent(.Reject, transitions: [.Running => .Rejected, .Paused => .Rejected]) - - $0.addEventHandler(.Resume) { context in - configuration.resume?() - return - } - - $0.addEventHandler(.Pause) { context in - configuration.pause?() - return - } - - // NOTE: use order = 90 (< default = 100) to prepare setting value before handling progress/fulfill/reject - $0.addEventHandler(.Progress, order: 90) { context in - if let progressTuple = context.userInfo as? ProgressTuple { - weakSelf?.progress = progressTuple.newProgress - } - } - $0.addEventHandler(.Fulfill, order: 90) { context in - if let value = context.userInfo as? Value { - weakSelf?.value = value - } - configuration.clear() - } - $0.addEventHandler(.Reject, order: 90) { context in - if let errorInfo = context.userInfo as? ErrorInfo { - weakSelf?.errorInfo = errorInfo - configuration.cancel?() // NOTE: call configured cancellation on reject as well - } - configuration.clear() - } - - // clear `_initClosure` & all StateMachine's handlers to prevent retain cycle - $0.addEventHandler(.Fulfill, order: 255) { context in -// weakSelf?._initClosure = nil // comment-out: let `task.deinit()` handle this - weakSelf?._performInitClosure = nil - weakSelf?.machine?.removeAllHandlers() - } - $0.addEventHandler(.Reject, order: 255) { context in -// weakSelf?._initClosure = nil - weakSelf?._performInitClosure = nil - weakSelf?.machine?.removeAllHandlers() - } - - } self._initClosure = _initClosure @@ -323,48 +218,43 @@ public class Task: Printable if let self_ = self { var progressHandler: ProgressHandler - var fulfillHandler: FulFillHandler - var rejectHandler: _RejectHandler + var fulfillHandler: FulfillHandler + var rejectInfoHandler: _RejectInfoHandler if weakified { progressHandler = { [weak self_] (progress: Progress) in if let self_ = self_ { - let oldProgress = self_.progress - self_.machine <-! (.Progress, (oldProgress, progress)) + self_._machine.handleProgress(progress) } } fulfillHandler = { [weak self_] (value: Value) in if let self_ = self_ { - self_.machine <-! (.Fulfill, value) + self_._machine.handleFulfill(value) } } - rejectHandler = { [weak self_] (errorInfo: ErrorInfo) in + rejectInfoHandler = { [weak self_] (errorInfo: ErrorInfo) in if let self_ = self_ { - self_.machine <-! (.Reject, errorInfo) + self_._machine.handleRejectInfo(errorInfo) } } } else { progressHandler = { (progress: Progress) in - let oldProgress = self_.progress - self_.machine <-! (.Progress, (oldProgress, progress)) - return + self_._machine.handleProgress(progress) } fulfillHandler = { (value: Value) in - self_.machine <-! (.Fulfill, value) - return + self_._machine.handleFulfill(value) } - rejectHandler = { (errorInfo: ErrorInfo) in - self_.machine <-! (.Reject, errorInfo) - return + rejectInfoHandler = { (errorInfo: ErrorInfo) in + self_._machine.handleRejectInfo(errorInfo) } } - _initClosure(machine: self_.machine, progress: progressHandler, fulfill: fulfillHandler, _reject: rejectHandler, configure: configuration) + _initClosure(machine: self_._machine, progress: progressHandler, fulfill: fulfillHandler, _reject: rejectInfoHandler, configure: self_._machine.configuration) } @@ -444,11 +334,7 @@ public class Task: Printable /// public func progress(progressClosure: ProgressTuple -> Void) -> Task { - self.machine.addEventHandler(.Progress) { [weak self] context in - if let progressTuple = context.userInfo as? ProgressTuple { - progressClosure(progressTuple) - } - } + self._machine.progressTupleHandlers.append(progressClosure) return self } @@ -472,72 +358,28 @@ public class Task: Printable /// public func then(thenClosure: (Value?, ErrorInfo?) -> Task) -> Task { - return Task { [weak self] machine, progress, fulfill, _reject, configure in + return Task { [weak self] newMachine, progress, fulfill, _reject, configure in - let bind = { [weak machine] (value: Value?, errorInfo: ErrorInfo?) -> Void in - let innerTask = thenClosure(value, errorInfo) - - // NOTE: don't call `then` for innerTask, or recursive bindings may occur - // Bad example: https://github.com/inamiy/SwiftTask/blob/e6085465c147fb2211fb2255c48929fcc07acd6d/SwiftTask/SwiftTask.swift#L312-L316 - switch innerTask.machine.state { - case .Fulfilled: - fulfill(innerTask.value!) - case .Rejected: - _reject(innerTask.errorInfo!) - default: - innerTask.machine.addEventHandler(.Progress) { context in - if let (_, progressValue) = context.userInfo as? Task.ProgressTuple { - progress(progressValue) - } - } - innerTask.machine.addEventHandler(.Fulfill) { context in - if let value = context.userInfo as? Value2 { - fulfill(value) - } - } - innerTask.machine.addEventHandler(.Reject) { context in - if let errorInfo = context.userInfo as? ErrorInfo { - _reject(errorInfo) - } - } - } - - configure.pause = { innerTask.pause(); return } - configure.resume = { innerTask.resume(); return } - configure.cancel = { innerTask.cancel(); return } - - // pause/cancel innerTask if descendant task is already paused/cancelled - if machine!.state == .Paused { - innerTask.pause() - } - else if machine!.state == .Cancelled { - innerTask.cancel() - } + // + // NOTE: + // We split `self` (Task) and `self.machine` (StateMachine) separately to + // let `completionHandler` retain `selfMachine` instead of `self` + // so that `selfMachine`'s `completionHandlers` can be invoked even though `self` is deinited. + // This is especially important for ReactKit's `deinitSignal` behavior. + // + let selfMachine = self!._machine + + let completionHandler: Void -> Void = { + let innerTask = thenClosure(selfMachine.value, selfMachine.errorInfo) + _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } if let self_ = self { - switch self_.machine.state { - case .Fulfilled: - bind(self_.value!, nil) - case .Rejected: - bind(nil, self_.errorInfo!) + switch self_.state { + case .Fulfilled, .Rejected, .Cancelled: + completionHandler() default: - // comment-out: only innerTask's progress should be sent to newTask -// self_.machine.addEventHandler(.Progress) { context in -// if let (_, progressValue) = context.userInfo as? Task.ProgressTuple { -// progress(progressValue) -// } -// } - self_.machine.addEventHandler(.Fulfill) { context in - if let value = context.userInfo as? Value { - bind(value, nil) - } - } - self_.machine.addEventHandler(.Reject) { context in - if let errorInfo = context.userInfo as? ErrorInfo { - bind(nil, errorInfo) - } - } + self_._machine.completionHandlers.append(completionHandler) } } @@ -563,58 +405,26 @@ public class Task: Printable /// public func success(successClosure: Value -> Task) -> Task { - return Task { [weak self] machine, progress, fulfill, _reject, configure in + return Task { [weak self] newMachine, progress, fulfill, _reject, configure in - let bind = { [weak machine] (value: Value) -> Void in - let innerTask = successClosure(value) - - innerTask.progress { _, progressValue in - progress(progressValue) - }.then { (value: Value2?, errorInfo: ErrorInfo?) -> Void in - if let value = value { - fulfill(value) - } - else if let errorInfo = errorInfo { - _reject(errorInfo) - } - } - - configure.pause = { innerTask.pause(); return } - configure.resume = { innerTask.resume(); return } - configure.cancel = { innerTask.cancel(); return } - - // pause/cancel innerTask if descendant task is already paused/cancelled - if machine!.state == .Paused { - innerTask.pause() + let selfMachine = self!._machine + + let completionHandler: Void -> Void = { + if let value = selfMachine.value { + let innerTask = successClosure(value) + _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } - else if machine!.state == .Cancelled { - innerTask.cancel() + else if let errorInfo = selfMachine.errorInfo { + _reject(errorInfo) } } if let self_ = self { - switch self_.machine.state { - case .Fulfilled: - bind(self_.value!) - case .Rejected: - _reject(self_.errorInfo!) + switch self_.state { + case .Fulfilled, .Rejected, .Cancelled: + completionHandler() default: - // comment-out: only innerTask's progress should be sent to newTask -// self_.machine.addEventHandler(.Progress) { context in -// if let (_, progressValue) = context.userInfo as? Task.ProgressTuple { -// progress(progressValue) -// } -// } - self_.machine.addEventHandler(.Fulfill) { context in - if let value = context.userInfo as? Value { - bind(value) - } - } - self_.machine.addEventHandler(.Reject) { context in - if let errorInfo = context.userInfo as? ErrorInfo { - _reject(errorInfo) - } - } + self_._machine.completionHandlers.append(completionHandler) } } @@ -642,59 +452,26 @@ public class Task: Printable /// public func failure(failureClosure: ErrorInfo -> Task) -> Task { - return Task { [weak self] machine, progress, fulfill, _reject, configure in + return Task { [weak self] newMachine, progress, fulfill, _reject, configure in - let bind = { [weak machine] (errorInfo: ErrorInfo) -> Void in - let innerTask = failureClosure(errorInfo) - - innerTask.progress { _, progressValue in - progress(progressValue) - }.then { (value: Value?, errorInfo: ErrorInfo?) -> Void in - if let value = value { - fulfill(value) - } - else if let errorInfo = errorInfo { - _reject(errorInfo) - } - } - - configure.pause = { innerTask.pause(); return } - configure.resume = { innerTask.resume(); return } - configure.cancel = { innerTask.cancel(); return } - - // pause/cancel innerTask if descendant task is already paused/cancelled - if machine!.state == .Paused { - innerTask.pause() + let selfMachine = self!._machine + + let completionHandler: Void -> Void = { + if let value = selfMachine.value { + fulfill(value) } - else if machine!.state == .Cancelled { - innerTask.cancel() + else if let errorInfo = selfMachine.errorInfo { + let innerTask = failureClosure(errorInfo) + _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } } if let self_ = self { - switch self_.machine.state { - case .Fulfilled: - fulfill(self_.value!) - case .Rejected: - let errorInfo = self_.errorInfo! - bind(errorInfo) + switch self_.state { + case .Fulfilled, .Rejected, .Cancelled: + completionHandler() default: - // comment-out: only innerTask's progress should be sent to newTask -// self_.machine.addEventHandler(.Progress) { context in -// if let (_, progressValue) = context.userInfo as? Task.ProgressTuple { -// progress(progressValue) -// } -// } - self_.machine.addEventHandler(.Fulfill) { context in - if let value = context.userInfo as? Value { - fulfill(value) - } - } - self_.machine.addEventHandler(.Reject) { context in - if let errorInfo = context.userInfo as? ErrorInfo { - bind(errorInfo) - } - } + self_._machine.completionHandlers.append(completionHandler) } } @@ -703,7 +480,7 @@ public class Task: Printable public func pause() -> Bool { - return self.machine <-! .Pause + return self._machine.handlePause() } public func resume() -> Bool @@ -721,7 +498,7 @@ public class Task: Printable // if (self._performInitClosure != nil) { - let isPaused = self.machine.state == .Paused + let isPaused = (self.state == .Paused) // // Temporarily switch to `.Running` without invoking `configure.resume()`. @@ -729,18 +506,20 @@ public class Task: Printable // inside its `initClosure` *immediately*. // if isPaused { - self.machine <-! ._InitResume + self._machine.state = .Running } self._performInitClosure?() self._performInitClosure = nil - if isPaused { - self.machine <-! ._InitPause // switch back + // switch back to `.Paused` only if temporary `.Running` has not changed + // (NOTE: `_performInitClosure` sometimes invokes `initClosure`'s `fulfill()`/`reject()` immediately) + if isPaused && self.state == .Running { + self._machine.state = .Paused } } - return self.machine <-! .Resume + return self._machine.handleResume() } public func cancel(error: Error? = nil) -> Bool @@ -750,12 +529,60 @@ public class Task: Printable internal func _cancel(error: Error? = nil) -> Bool { - return self.machine <-! (.Reject, ErrorInfo(error: error, isCancelled: true)) + return self._machine.handleCancel(error: error) + } +} + +// MARK: - Helper + +internal func _bindInnerTask( + innerTask: Task, + newMachine: _StateMachine, + progress: Task.ProgressHandler, + fulfill: Task.FulfillHandler, + _reject: Task._RejectInfoHandler, + configure: TaskConfiguration + ) +{ + switch innerTask.state { + case .Fulfilled: + fulfill(innerTask.value!) + return + case .Rejected, .Cancelled: + _reject(innerTask.errorInfo!) + return + default: + break + } + + innerTask.progress { _, progressValue in + progress(progressValue) + }.then { (value: Value2?, errorInfo: Task.ErrorInfo?) -> Void in + if let value = value { + fulfill(value) + } + else if let errorInfo = errorInfo { + _reject(errorInfo) + } + } + + configure.pause = { innerTask.pause(); return } + configure.resume = { innerTask.resume(); return } + configure.cancel = { innerTask.cancel(); return } + + // pause/cancel innerTask if descendant task is already paused/cancelled + if newMachine.state == .Paused { + innerTask.pause() + } + else if newMachine.state == .Cancelled { + innerTask.cancel() } } extension Task { + public typealias BulkProgress = (completedCount: Int, totalCount: Int) + public class func all(tasks: [Task]) -> Task { return Task { machine, progress, fulfill, _reject, configure in diff --git a/SwiftTask/_StateMachine.swift b/SwiftTask/_StateMachine.swift new file mode 100644 index 0000000..5fc4e22 --- /dev/null +++ b/SwiftTask/_StateMachine.swift @@ -0,0 +1,118 @@ +// +// _StateMachine.swift +// SwiftTask +// +// Created by Yasuhiro Inami on 2015/01/21. +// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. +// + +import Foundation + +/// fast, naive event-handler-manager in replace of ReactKit/SwiftState (dynamic but slow), +/// introduced from SwiftTask 2.6.0 +internal class _StateMachine +{ + internal typealias ErrorInfo = Task.ErrorInfo + + internal var state: TaskState + + internal var progress: Progress? + internal var value: Value? + internal var errorInfo: ErrorInfo? + + internal var progressTupleHandlers: [Task._ProgressTupleHandler] = [] + internal var completionHandlers: [Void -> Void] = [] + + internal let configuration = TaskConfiguration() + + internal init(paused: Bool) + { + self.state = paused ? .Paused : .Running + } + + internal func handleProgress(progress: Progress) + { + let oldProgress = self.progress + self.progress = progress + + if self.state == .Running { + for handler in self.progressTupleHandlers { + handler(oldProgress: oldProgress, newProgress: progress) + } + } + } + + internal func handleFulfill(value: Value) + { + if self.state == .Running { + self.state = .Fulfilled + self.value = value + self.complete() + } + } + + internal func handleRejectInfo(errorInfo: ErrorInfo) + { + if self.state == .Running || self.state == .Paused { + self.state = errorInfo.isCancelled ? .Cancelled : .Rejected + self.errorInfo = errorInfo + self.complete() + } + } + + internal func handlePause() -> Bool + { + if self.state == .Running { + self.configuration.pause?() + self.state = .Paused + return true + } + else { + return false + } + } + + internal func handleResume() -> Bool + { + if self.state == .Paused { + self.configuration.resume?() + self.state = .Running + return true + } + else { + return false + } + } + + internal func handleCancel(error: Error? = nil) -> Bool + { + if self.state == .Running || self.state == .Paused { + + self.state = .Cancelled + self.errorInfo = ErrorInfo(error: error, isCancelled: true) + self.complete { + // NOTE: call `configuration.cancel()` after all `completionHandlers` are invoked + self.configuration.cancel?() + return + } + + return true + } + else { + return false + } + } + + internal func complete(closure: (Void -> Void)? = nil) + { + for handler in self.completionHandlers { + handler() + } + + closure?() + + self.progressTupleHandlers.removeAll() + self.completionHandlers.removeAll() + self.configuration.clear() + } +} \ No newline at end of file From fcd758c875b91850534a226e93248b0ac167071a Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 21 Jan 2015 10:01:59 +0900 Subject: [PATCH 2/3] Remove SwiftState dependency. --- .gitmodules | 3 --- Cartfile | 1 - Cartfile.resolved | 1 - Carthage/Checkouts/SwiftState | 1 - SwiftTask.xcodeproj/project.pbxproj | 6 ------ SwiftTask.xcworkspace/contents.xcworkspacedata | 11 ----------- 6 files changed, 23 deletions(-) delete mode 100644 Cartfile delete mode 160000 Carthage/Checkouts/SwiftState diff --git a/.gitmodules b/.gitmodules index 091ed75..0226b08 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "Carthage/Checkouts/SwiftState"] - path = Carthage/Checkouts/SwiftState - url = https://github.com/ReactKit/SwiftState.git [submodule "Carthage/Checkouts/Alamofire"] path = Carthage/Checkouts/Alamofire url = https://github.com/Alamofire/Alamofire.git diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 7a61e81..0000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "ReactKit/SwiftState" ~> 1.1.1 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 21145d3..fddcbe4 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,2 @@ github "Alamofire/Alamofire" "2dd2f6f6186b98f27b75479b15c109ebd4dd8b52" github "duemunk/Async" "32cec7ee89d6f1a39b10f2f69d91640ba3416eca" -github "ReactKit/SwiftState" "1.1.1" diff --git a/Carthage/Checkouts/SwiftState b/Carthage/Checkouts/SwiftState deleted file mode 160000 index 2cd54e4..0000000 --- a/Carthage/Checkouts/SwiftState +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2cd54e4153f7e691d926e73d3a90235a57575664 diff --git a/SwiftTask.xcodeproj/project.pbxproj b/SwiftTask.xcodeproj/project.pbxproj index da2c968..10c7c3d 100644 --- a/SwiftTask.xcodeproj/project.pbxproj +++ b/SwiftTask.xcodeproj/project.pbxproj @@ -24,8 +24,6 @@ 484552331A4CF37F007770CA /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 484551CE1A4CF37F007770CA /* Alamofire.swift */; }; 4845524A1A4CF37F007770CA /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 484551DC1A4CF37F007770CA /* Async.swift */; }; 4845524B1A4CF37F007770CA /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 484551DC1A4CF37F007770CA /* Async.swift */; }; - 484552CA1A4CF48A007770CA /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 484552C91A4CF48A007770CA /* SwiftState.framework */; }; - 484552CB1A4CF48D007770CA /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 484552C91A4CF48A007770CA /* SwiftState.framework */; }; 48511C5B19C17563002FE03C /* RetainCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48511C5A19C17563002FE03C /* RetainCycleTests.swift */; }; 485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; }; 485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; }; @@ -52,7 +50,6 @@ 4822F0D019D00ABF00F5F572 /* SwiftTask-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftTask-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 484551CE1A4CF37F007770CA /* Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alamofire.swift; sourceTree = ""; }; 484551DC1A4CF37F007770CA /* Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = ""; }; - 484552C91A4CF48A007770CA /* SwiftState.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftState.framework; path = Carthage/Checkouts/SwiftState/build/Debug/SwiftState.framework; sourceTree = ""; }; 48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = ""; }; 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeInferenceTests.swift; sourceTree = ""; }; 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _StateMachine.swift; sourceTree = ""; }; @@ -64,7 +61,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 484552CA1A4CF48A007770CA /* SwiftState.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -88,7 +84,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 484552CB1A4CF48D007770CA /* SwiftState.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -98,7 +93,6 @@ 1F46DECA199EDF1000F97868 = { isa = PBXGroup; children = ( - 484552C91A4CF48A007770CA /* SwiftState.framework */, 484551B31A4CF37F007770CA /* Checkouts */, 1F46DED6199EDF1000F97868 /* SwiftTask */, 1F46DEE0199EDF1000F97868 /* SwiftTaskTests */, diff --git a/SwiftTask.xcworkspace/contents.xcworkspacedata b/SwiftTask.xcworkspace/contents.xcworkspacedata index c4e02cd..7e515cd 100644 --- a/SwiftTask.xcworkspace/contents.xcworkspacedata +++ b/SwiftTask.xcworkspace/contents.xcworkspacedata @@ -1,17 +1,6 @@ - - - - - - From 1d02eb17b90843bfd99dc232c996b45e92ea6dc9 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 21 Jan 2015 22:15:25 +0900 Subject: [PATCH 3/3] Update podspec. --- SwiftTask.podspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SwiftTask.podspec b/SwiftTask.podspec index 1bc8692..e65f265 100644 --- a/SwiftTask.podspec +++ b/SwiftTask.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SwiftTask' - s.version = '2.5.1' + s.version = '2.6.0' s.license = { :type => 'MIT' } s.homepage = 'https://github.com/ReactKit/SwiftTask' s.authors = { 'Yasuhiro Inami' => 'inamiy@gmail.com' } @@ -8,6 +8,4 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/inamiy/SwiftTask.git', :tag => "#{s.version}" } s.source_files = 'SwiftTask/**/*.{h,swift}' s.requires_arc = true - - s.dependency 'SwiftState', '~> 1.1.1' end \ No newline at end of file