Skip to content
This repository was archived by the owner on Aug 29, 2022. It is now read-only.

Commit a7ae402

Browse files
authored
Merge pull request #284 from bignerdranch/zwaldowski/and-then-progress
Fixes issue where multiple nested andThen did not track progress (#281)
2 parents 24c48a7 + 9cb92c4 commit a7ae402

File tree

9 files changed

+108
-103
lines changed

9 files changed

+108
-103
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ source "https://rubygems.org"
22

33
gem 'fastlane'
44
gem 'semantic'
5-
gem 'cocoapods', '>= 1.6.0.beta'
5+
gem 'cocoapods', '>= 1.6.0'
66
gem 'jazzy'

Gemfile.lock

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ GEM
22
remote: https://rubygems.org/
33
specs:
44
CFPropertyList (3.0.0)
5-
activesupport (4.2.11)
5+
activesupport (4.2.11.1)
66
i18n (~> 0.7)
77
minitest (~> 5.1)
88
thread_safe (~> 0.3, >= 0.3.4)
@@ -12,10 +12,10 @@ GEM
1212
atomos (0.1.3)
1313
babosa (1.0.2)
1414
claide (1.0.2)
15-
cocoapods (1.6.0.beta.2)
15+
cocoapods (1.6.1)
1616
activesupport (>= 4.0.2, < 5)
1717
claide (>= 1.0.2, < 2.0)
18-
cocoapods-core (= 1.6.0.beta.2)
18+
cocoapods-core (= 1.6.1)
1919
cocoapods-deintegrate (>= 1.0.2, < 2.0)
2020
cocoapods-downloader (>= 1.2.2, < 2.0)
2121
cocoapods-plugins (>= 1.0.0, < 2.0)
@@ -25,22 +25,22 @@ GEM
2525
cocoapods-try (>= 1.1.0, < 2.0)
2626
colored2 (~> 3.1)
2727
escape (~> 0.0.4)
28-
fourflusher (~> 2.0.1)
28+
fourflusher (>= 2.2.0, < 3.0)
2929
gh_inspector (~> 1.0)
3030
molinillo (~> 0.6.6)
3131
nap (~> 1.0)
32-
ruby-macho (~> 1.3, >= 1.3.1)
33-
xcodeproj (>= 1.7.0, < 2.0)
34-
cocoapods-core (1.6.0.beta.2)
32+
ruby-macho (~> 1.4)
33+
xcodeproj (>= 1.8.1, < 2.0)
34+
cocoapods-core (1.6.1)
3535
activesupport (>= 4.0.2, < 6)
3636
fuzzy_match (~> 2.0.4)
3737
nap (~> 1.0)
38-
cocoapods-deintegrate (1.0.2)
38+
cocoapods-deintegrate (1.0.4)
3939
cocoapods-downloader (1.2.2)
4040
cocoapods-plugins (1.0.0)
4141
nap
4242
cocoapods-search (1.0.0)
43-
cocoapods-stats (1.0.0)
43+
cocoapods-stats (1.1.0)
4444
cocoapods-trunk (1.3.1)
4545
nap (>= 0.8, < 2.0)
4646
netrc (~> 0.11)
@@ -49,7 +49,7 @@ GEM
4949
colored2 (3.1.2)
5050
commander-fastlane (4.4.6)
5151
highline (~> 1.7.2)
52-
concurrent-ruby (1.1.4)
52+
concurrent-ruby (1.1.5)
5353
declarative (0.0.10)
5454
declarative-option (0.1.0)
5555
digest-crc (0.4.1)
@@ -67,7 +67,7 @@ GEM
6767
faraday_middleware (0.13.1)
6868
faraday (>= 0.7.4, < 1.0)
6969
fastimage (2.1.5)
70-
fastlane (2.119.0)
70+
fastlane (2.120.0)
7171
CFPropertyList (>= 2.3, < 4.0.0)
7272
addressable (>= 2.3, < 3.0.0)
7373
babosa (>= 1.0.2, < 2.0.0)
@@ -105,7 +105,7 @@ GEM
105105
xcpretty (~> 0.3.0)
106106
xcpretty-travis-formatter (>= 0.0.3)
107107
ffi (1.10.0)
108-
fourflusher (2.0.1)
108+
fourflusher (2.2.0)
109109
fuzzy_match (2.0.4)
110110
gh_inspector (1.1.3)
111111
google-api-client (0.23.9)
@@ -179,9 +179,9 @@ GEM
179179
uber (< 0.2.0)
180180
retriable (3.1.2)
181181
rouge (2.0.7)
182-
ruby-macho (1.3.1)
182+
ruby-macho (1.4.0)
183183
rubyzip (1.2.2)
184-
sass (3.7.3)
184+
sass (3.7.4)
185185
sass-listen (~> 4.0.0)
186186
sass-listen (4.0.0)
187187
rb-fsevent (~> 0.9, >= 0.9.4)
@@ -197,7 +197,7 @@ GEM
197197
CFPropertyList
198198
naturally
199199
slack-notifier (2.3.2)
200-
sqlite3 (1.3.13)
200+
sqlite3 (1.4.0)
201201
terminal-notifier (2.0.0)
202202
terminal-table (1.8.0)
203203
unicode-display_width (~> 1.1, >= 1.1.1)
@@ -231,7 +231,7 @@ PLATFORMS
231231
ruby
232232

233233
DEPENDENCIES
234-
cocoapods (>= 1.6.0.beta)
234+
cocoapods (>= 1.6.0)
235235
fastlane
236236
jazzy
237237
semantic

Sources/Task/Progress+ExplicitComposition.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Deferred.Atomics
1616

1717
/// A progress object whose attributes reflect that of an external progress
1818
/// tree.
19+
@objc(BNRTaskProxyProgress)
1920
private final class ProxyProgress: Progress {
2021

2122
@objc dynamic let observee: Progress

Sources/Task/TaskAndThen.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension TaskProtocol {
4343
/// - see: FutureProtocol.andThen(upon:start:)
4444
public func andThen<NewTask: TaskProtocol>(upon executor: Executor, start startNextTask: @escaping(Success) throws -> NewTask) -> Task<NewTask.Success> {
4545
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
46-
let chain = TaskChain(continuingWith: self)
46+
let chain = TaskChain(andThenFrom: self)
4747
#else
4848
let cancellationToken = Deferred<Void>()
4949
#endif

Sources/Task/TaskChain.swift

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ struct TaskChain {
1919
/// The default work unit count for a single call to a `Task` initializer or
2020
/// chaining method.
2121
private static let singleUnit = Int64(1)
22+
/// The work unit count applied when a chaining method may later determine
23+
/// a higher or lower unit count.
24+
private static let undeterminedUnit = Int64(8)
2225
/// The work unit count when a `Task` initializer or chaining method accepts
2326
/// an user-provided `Progress` instance.
24-
private static let explicitChildUnitCount = Int64(100)
27+
private static let explicitChildUnitCount = Int64(20)
2528

2629
/// Marker class representing the start of a task chain.
2730
///
@@ -30,14 +33,14 @@ struct TaskChain {
3033
/// that first task's `Root`.
3134
@objc(BNRTaskRootProgress)
3235
private class Root: Progress {
33-
/// Key for value of type `Root?` in `Thread.threadDictionary`.
34-
static let threadKey = "_BNRTaskCurrentRoot"
36+
/// Key for value of type `[Root]?` in `Thread.threadDictionary`.
37+
static let threadKey = "_BNRTaskProgressStack"
3538

3639
/// Propogates the current Task chain for explicit composition during
3740
/// `Task.andThen`.
38-
static var active: Root? {
41+
static var activeStack: [Root] {
3942
get {
40-
return Thread.current.threadDictionary[threadKey] as? Root
43+
return Thread.current.threadDictionary[threadKey] as? [Root] ?? []
4144
}
4245
set {
4346
Thread.current.threadDictionary[threadKey] = newValue
@@ -81,7 +84,7 @@ struct TaskChain {
8184
/// Locates or creates the root of a task chain, then generates any
8285
/// progress objects needed to represent `wrapped` in that chain.
8386
init<Wrapped: TaskProtocol>(startingWith wrapped: Wrapped, using customProgress: Progress? = nil, uponCancel cancellation: (() -> Void)? = nil) {
84-
if let root = Root.active {
87+
if let root = Root.activeStack.last {
8588
// We're inside `andThen` — `commitAndThen(with:)` will compose instead.
8689
self.root = root
8790
self.effectiveProgress = customProgress ?? .basicProgress(parent: nil, for: wrapped, uponCancel: cancellation)
@@ -90,8 +93,8 @@ struct TaskChain {
9093
self.root = root
9194
self.effectiveProgress = root
9295
} else {
93-
// Create a "root" progress for the task and its follow-up steps.
94-
// If the initial operation provides progress, give it a 100x slice.
96+
// Create a "root" progress for the task and its follow-up steps. If
97+
// the initial operation provides progress, give it a larger slice.
9598
let unitCount = customProgress == nil ? TaskChain.singleUnit : TaskChain.explicitChildUnitCount
9699
self.root = Root()
97100
self.root.totalUnitCount = unitCount
@@ -107,17 +110,17 @@ struct TaskChain {
107110

108111
/// Locates or creates the root of a task chain, then increments its
109112
/// total units in preparation for a follow-up operation to be performed.
110-
init<Wrapped: TaskProtocol>(continuingWith wrapped: Wrapped) {
113+
private init<Wrapped: TaskProtocol>(continuingFrom wrapped: Wrapped, pendingUnitCount: Int64) {
111114
if let task = wrapped as? Task<Wrapped.Success>, let root = task.progress as? Root {
112115
// If `wrapped` is a Task created normally, reuse the progress root;
113116
// this `map` or `andThen` builds on that progress.
117+
root.totalUnitCount += pendingUnitCount
114118
self.root = root
115-
self.root.totalUnitCount += TaskChain.singleUnit
116119
self.effectiveProgress = root
117120
} else {
118121
// If `wrapped` is a `Future` or something else, start a new chain.
119122
self.root = Root()
120-
self.root.totalUnitCount = TaskChain.singleUnit * 2
123+
self.root.totalUnitCount = TaskChain.singleUnit + pendingUnitCount
121124
self.effectiveProgress = root
122125

123126
root.monitorCompletion(of: wrapped, withPendingUnitCount: TaskChain.singleUnit)
@@ -126,9 +129,15 @@ struct TaskChain {
126129

127130
// MARK: -
128131

132+
/// Locates or creates the root of a task chain, then increments its
133+
/// total units in preparation for a follow-up operation to be performed.
134+
init<Wrapped: TaskProtocol>(mapFrom wrapped: Wrapped) {
135+
self.init(continuingFrom: wrapped, pendingUnitCount: TaskChain.singleUnit)
136+
}
137+
129138
/// The handler passed to `map` can use implicit progress reporting.
130139
/// During the handler, the first `Progress` object created using
131-
/// `parent: .current()` will be given a 100x slice of the task chain on
140+
/// `parent: .current()` will be given a larger slice of the task chain on
132141
/// macOS 10.11, iOS 9, and above.
133142
func beginMap() {
134143
root.expandsAddedChildren = true
@@ -143,32 +152,40 @@ struct TaskChain {
143152

144153
// MARK: -
145154

155+
/// Locates or creates the root of a task chain, then increments its
156+
/// total units in preparation for a follow-up operation to be performed.
157+
init<Wrapped: TaskProtocol>(andThenFrom wrapped: Wrapped) {
158+
self.init(continuingFrom: wrapped, pendingUnitCount: TaskChain.undeterminedUnit)
159+
}
160+
146161
/// The handler passed to `andThen` uses explicit progress reporting.
147162
/// After returning a from the handler, locate or create a representative
148163
/// progress and attach it to the root. If this next step provides custom
149-
/// progress, give it a 100x slice.
164+
/// progress, give it a larger slice.
150165
func beginAndThen() {
151-
Root.active = root
166+
Root.activeStack.append(root)
152167
}
153168

154169
/// See `beginAndThen`.
155170
func commitAndThen<Wrapped: TaskProtocol>(with wrapped: Wrapped) {
156-
if let task = wrapped as? Task<Wrapped.Success>, !(task.progress is Root) {
157-
let pendingUnitCount = task.progress.wasGeneratedByTask ? TaskChain.singleUnit : TaskChain.explicitChildUnitCount
158-
root.totalUnitCount += pendingUnitCount - TaskChain.singleUnit
159-
root.adoptChild(task.progress, withPendingUnitCount: pendingUnitCount)
171+
let wrappedProgress = (wrapped as? Task<Wrapped.Success>)?.progress
172+
let pendingUnitCount = wrappedProgress?.wasGeneratedByTask == false ? TaskChain.explicitChildUnitCount : TaskChain.singleUnit
173+
root.totalUnitCount += pendingUnitCount - TaskChain.undeterminedUnit
174+
if let wrappedProgress = wrappedProgress {
175+
root.monitorChild(wrappedProgress, withPendingUnitCount: pendingUnitCount)
160176
} else {
161-
root.monitorCompletion(of: wrapped, uponCancel: wrapped.cancel, withPendingUnitCount: TaskChain.singleUnit)
177+
root.monitorCompletion(of: wrapped, uponCancel: wrapped.cancel, withPendingUnitCount: pendingUnitCount)
162178
}
163-
Root.active = nil
179+
Root.activeStack.removeLast()
164180
}
165181

166182
/// When the `andThen` handler can't be run at all, mark the enqueued unit
167183
/// as complete anyway.
168184
func flushAndThen() {
169185
root.becomeCurrent(withPendingUnitCount: TaskChain.singleUnit)
170186
root.resignCurrent()
171-
Root.active = nil
187+
root.totalUnitCount += TaskChain.singleUnit - TaskChain.undeterminedUnit
188+
Root.activeStack.removeLast()
172189
}
173190
}
174191
#endif

Sources/Task/TaskFallback.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ extension TaskProtocol {
4242
/// - see: FutureProtocol.andThen(upon:start:)
4343
public func fallback<NewTask: TaskProtocol>(upon executor: Executor, to restartTask: @escaping(Failure) throws -> NewTask) -> Task<Success> where NewTask.Success == Success {
4444
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
45-
let chain = TaskChain(continuingWith: self)
45+
let chain = TaskChain(andThenFrom: self)
4646
#else
4747
let cancellationToken = Deferred<Void>()
4848
#endif

Sources/Task/TaskMap.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ extension TaskProtocol {
3939
/// - see: FutureProtocol.map(upon:transform:)
4040
public func map<NewSuccess>(upon executor: Executor, transform: @escaping(Success) throws -> NewSuccess) -> Task<NewSuccess> {
4141
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
42-
let chain = TaskChain(continuingWith: self)
42+
let chain = TaskChain(mapFrom: self)
4343
#endif
4444

4545
let future: Future = map(upon: executor) { (result) -> Task<NewSuccess>.Result in

Sources/Task/TaskRecovery.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension TaskProtocol {
4040
/// - see: FutureProtocol.map(upon:transform:)
4141
public func recover(upon executor: Executor, substituting substitution: @escaping(Failure) throws -> Success) -> Task<Success> {
4242
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
43-
let chain = TaskChain(continuingWith: self)
43+
let chain = TaskChain(andThenFrom: self)
4444
#endif
4545

4646
let future: Future = map(upon: executor) { (result) -> Task<Success>.Result in

0 commit comments

Comments
 (0)