Skip to content

Commit 231973d

Browse files
committed
Move _performInitClosure handling to _StateMachine & rename to 'initResumeClosure'.
1 parent cf96d36 commit 231973d

File tree

2 files changed

+77
-53
lines changed

2 files changed

+77
-53
lines changed

SwiftTask/SwiftTask.swift

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ public class Task<Progress, Value, Error>: Printable
6868
internal let _paused: Bool
6969
internal var _initClosure: _InitClosure! // retained throughout task's lifetime
7070

71-
/// wrapper closure for `_initClosure` to invoke only once when started `.Running`,
72-
/// and will be set to `nil` afterward
73-
internal var _performInitClosure: (Void -> Void)?
74-
7571
public var state: TaskState { return self._machine.state }
7672

7773
/// progress value (NOTE: always nil when `weakified = true`)
@@ -122,10 +118,10 @@ public class Task<Progress, Value, Error>: Printable
122118

123119
let _initClosure: _InitClosure = { _, progress, fulfill, _reject, configure in
124120
// NOTE: don't expose rejectHandler with ErrorInfo (isCancelled) for public init
125-
initClosure(progress: progress, fulfill: fulfill, reject: { (error: Error?) in _reject(ErrorInfo(error: error, isCancelled: false)) }, configure: configure)
121+
initClosure(progress: progress, fulfill: fulfill, reject: { (error: Error) in _reject(ErrorInfo(error: error, isCancelled: false)) }, configure: configure)
126122
}
127123

128-
self.setup(weakified, paused: paused, _initClosure)
124+
self.setup(weakified: weakified, paused: paused, _initClosure: _initClosure)
129125
}
130126

131127
///
@@ -194,19 +190,20 @@ public class Task<Progress, Value, Error>: Printable
194190
self._paused = paused
195191
self._machine = _Machine(weakified: weakified, paused: paused)
196192

197-
self.setup(weakified, paused: paused, _initClosure)
193+
self.setup(weakified: weakified, paused: paused, _initClosure: _initClosure)
198194
}
199195

200-
internal func setup(weakified: Bool, paused: Bool, _initClosure: _InitClosure)
201-
{
196+
// NOTE: don't use `internal init` for this setup method, or this will be a designated initalizer
197+
internal func setup(#weakified: Bool, paused: Bool, _initClosure: _InitClosure)
198+
{
202199
// #if DEBUG
203200
// println("[init] \(self.name)")
204201
// #endif
205202

206203
self._initClosure = _initClosure
207204

208205
// will be invoked on 1st resume (only once)
209-
self._performInitClosure = { [weak self] in
206+
self._machine.initResumeClosure = { [weak self] in
210207

211208
// strongify `self` on 1st resume
212209
if let self_ = self {
@@ -259,7 +256,7 @@ public class Task<Progress, Value, Error>: Printable
259256
self_._machine.handleRejectInfo(errorInfo)
260257
}
261258
}
262-
259+
263260
_initClosure(machine: self_._machine, progress: progressHandler, fulfill: fulfillHandler, _reject: rejectInfoHandler, configure: self_._machine.configuration)
264261

265262
}
@@ -342,7 +339,7 @@ public class Task<Progress, Value, Error>: Printable
342339
///
343340
public func progress(progressClosure: ProgressTuple -> Void) -> Task
344341
{
345-
self._machine.progressTupleHandlers.append(progressClosure)
342+
self._machine.addProgressTupleHandler(progressClosure)
346343

347344
return self
348345
}
@@ -392,7 +389,7 @@ public class Task<Progress, Value, Error>: Printable
392389
case .Fulfilled, .Rejected, .Cancelled:
393390
completionHandler()
394391
default:
395-
self._machine.completionHandlers.append(completionHandler)
392+
self._machine.addCompletionHandler(completionHandler)
396393
}
397394
}
398395

@@ -478,40 +475,6 @@ public class Task<Progress, Value, Error>: Printable
478475

479476
public func resume() -> Bool
480477
{
481-
//
482-
// Always try `_performInitClosure` only once on `resume()`
483-
// even when `.Pause => .Resume` transition fails, e.g. already been fulfilled/rejected.
484-
//
485-
// NOTE:
486-
// **`downstream._performInitClosure` should be invoked first before `downstream.machine <-! .Resume`**
487-
// to add upstream's progress/fulfill/reject handlers inside `downstream.initClosure()`
488-
// before their actual calls, which often happens
489-
// when downstream's `resume()` is configured to call upstream's `resume()`
490-
// which eventually calls `upstream._performInitClosure` and thus actual event handlers.
491-
//
492-
if (self._performInitClosure != nil) {
493-
494-
let isPaused = (self.state == .Paused)
495-
496-
//
497-
// Temporarily switch to `.Running` without invoking `configure.resume()`.
498-
// This allows paused-inited-task to safely call progress/fulfill/reject handlers
499-
// inside its `initClosure` *immediately*.
500-
//
501-
if isPaused {
502-
self._machine.state = .Running // switch temporarily
503-
}
504-
505-
self._performInitClosure?()
506-
self._performInitClosure = nil
507-
508-
// switch back to `.Paused` only if temporary `.Running` has not changed
509-
// (NOTE: `_performInitClosure` sometimes invokes `initClosure`'s `fulfill()`/`reject()` immediately)
510-
if isPaused && self.state == .Running {
511-
self._machine.state = .Paused // switch back
512-
}
513-
}
514-
515478
return self._machine.handleResume()
516479
}
517480

SwiftTask/_StateMachine.swift

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,21 @@ import Foundation
1717
internal class _StateMachine<Progress, Value, Error>
1818
{
1919
internal typealias ErrorInfo = Task<Progress, Value, Error>.ErrorInfo
20+
internal typealias ProgressTupleHandler = Task<Progress, Value, Error>._ProgressTupleHandler
2021

2122
internal let weakified: Bool
22-
internal var state: TaskState
23+
internal private(set) var state: TaskState
2324

24-
internal var progress: Progress?
25-
internal var value: Value?
26-
internal var errorInfo: ErrorInfo?
25+
internal private(set) var progress: Progress? // NOTE: always nil if `weakified = true`
26+
internal private(set) var value: Value?
27+
internal private(set) var errorInfo: ErrorInfo?
2728

28-
internal var progressTupleHandlers: [Task<Progress, Value, Error>._ProgressTupleHandler] = []
29-
internal var completionHandlers: [Void -> Void] = []
29+
/// wrapper closure for `_initClosure` to invoke only once when started `.Running`,
30+
/// and will be set to `nil` afterward
31+
internal var initResumeClosure: (Void -> Void)?
32+
33+
internal private(set) var progressTupleHandlers: [ProgressTupleHandler] = []
34+
internal private(set) var completionHandlers: [Void -> Void] = []
3035

3136
internal let configuration = TaskConfiguration()
3237

@@ -36,6 +41,16 @@ internal class _StateMachine<Progress, Value, Error>
3641
self.state = paused ? .Paused : .Running
3742
}
3843

44+
internal func addProgressTupleHandler(progressTupleHandler: ProgressTupleHandler)
45+
{
46+
self.progressTupleHandlers.append(progressTupleHandler)
47+
}
48+
49+
internal func addCompletionHandler(completionHandler: Void -> Void)
50+
{
51+
self.completionHandlers.append(completionHandler)
52+
}
53+
3954
internal func handleProgress(progress: Progress)
4055
{
4156
if self.state == .Running {
@@ -84,6 +99,50 @@ internal class _StateMachine<Progress, Value, Error>
8499
}
85100

86101
internal func handleResume() -> Bool
102+
{
103+
//
104+
// NOTE:
105+
// `initResumeClosure` should be invoked first before `configure.resume()`
106+
// to let downstream prepare setting upstream's progress/fulfill/reject handlers
107+
// before upstream actually starts sending values, which often happens
108+
// when downstream's `configure.resume()` is configured to call upstream's `task.resume()`
109+
// which eventually calls upstream's `initResumeClosure`
110+
// and thus upstream starts sending values.
111+
//
112+
self._handleInitResumeIfNeeded()
113+
114+
return _handleResume()
115+
}
116+
117+
///
118+
/// Invokes `initResumeClosure` on 1st resume (only once).
119+
///
120+
/// If initial state is `.Paused`, `state` will be temporarily switched to `.Running`
121+
/// during `initResumeClosure` execution, so that Task can call progress/fulfill/reject handlers safely.
122+
///
123+
private func _handleInitResumeIfNeeded()
124+
{
125+
if (self.initResumeClosure != nil) {
126+
127+
let isInitPaused = (self.state == .Paused)
128+
129+
if isInitPaused {
130+
self.state = .Running // switch `.Paused` => `.Resume` temporarily without invoking `configure.resume()`
131+
}
132+
133+
// NOTE: performing `initResumeClosure` might change `state` to `.Fulfilled` or `.Rejected` **immediately**
134+
self.initResumeClosure?()
135+
self.initResumeClosure = nil
136+
137+
// switch back to `.Paused` if temporary `.Running` has not changed
138+
// so that consecutive `_handleResume()` can perform `configure.resume()`
139+
if isInitPaused && self.state == .Running {
140+
self.state = .Paused
141+
}
142+
}
143+
}
144+
145+
private func _handleResume() -> Bool
87146
{
88147
if self.state == .Paused {
89148
self.configuration.resume?()
@@ -125,6 +184,8 @@ internal class _StateMachine<Progress, Value, Error>
125184
self.progressTupleHandlers.removeAll()
126185
self.completionHandlers.removeAll()
127186
self.configuration.clear()
187+
188+
self.initResumeClosure = nil
128189
self.progress = nil
129190
}
130191
}

0 commit comments

Comments
 (0)