diff --git a/.jazzy.yml b/.jazzy.yml
index 03040143..1aad7cdb 100644
--- a/.jazzy.yml
+++ b/.jazzy.yml
@@ -28,7 +28,9 @@ custom_categories:
- CFRunLoop
- name: Futures Than Can Fail
children:
+ - TaskResult
- Task
+ - TaskProtocol
- Either
- name: Coordinating Access to Data
children:
@@ -46,7 +48,7 @@ github_url: "https://github.com/bignerdranch/Deferred"
head: |
-
+
hide_documentation_coverage: true
module: Deferred
diff --git a/Sources/Deferred/Locking.swift b/Sources/Deferred/Locking.swift
index 4afbd484..72d8ed26 100644
--- a/Sources/Deferred/Locking.swift
+++ b/Sources/Deferred/Locking.swift
@@ -96,7 +96,7 @@ public final class NativeLock: Locking {
}
}
#else
-public typealias NativeLock = NSLock
+typealias NativeLock = NSLock
#endif
/// A readers-writer lock provided by the platform implementation of the
diff --git a/Sources/Task/TaskChain.swift b/Sources/Task/TaskChain.swift
index 078516d2..05feda42 100644
--- a/Sources/Task/TaskChain.swift
+++ b/Sources/Task/TaskChain.swift
@@ -30,8 +30,11 @@ struct TaskChain {
/// that first task's `Root`.
@objc(BNRTaskRootProgress)
private class Root: Progress {
+ /// Key for value of type `Root?` in `Thread.threadDictionary`.
static let threadKey = "_BNRTaskCurrentRoot"
+ /// Propogates the current Task chain for explicit composition during
+ /// `Task.andThen`.
static var active: Root? {
get {
return Thread.current.threadDictionary[threadKey] as? Root
@@ -40,6 +43,30 @@ struct TaskChain {
Thread.current.threadDictionary[threadKey] = newValue
}
}
+
+ /// Key for value of type `Bool` in `Progress.userInfo`.
+ static let expandsKey = ProgressUserInfoKey(rawValue: "_BNRTaskExpandChildren")
+
+ /// If `true`, Propogates the current Task chain for explicit composition during
+ /// `Task.andThen`.
+ var expandsAddedChildren: Bool {
+ get {
+ return userInfo[Root.expandsKey] as? Bool == true
+ }
+ set {
+ setUserInfoObject(newValue, forKey: Root.expandsKey)
+ }
+ }
+
+ @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
+ override func addChild(_ child: Progress, withPendingUnitCount unitCount: Int64) {
+ if expandsAddedChildren, !child.wasGeneratedByTask {
+ totalUnitCount += TaskChain.explicitChildUnitCount - unitCount
+ super.addChild(child, withPendingUnitCount: TaskChain.explicitChildUnitCount)
+ } else {
+ super.addChild(child, withPendingUnitCount: unitCount)
+ }
+ }
}
/// The beginning of this chain. May be the same as `effectiveProgress`.
@@ -99,16 +126,19 @@ struct TaskChain {
// MARK: -
- /// The handler passed to `map` supports explicit progress reporting.
- /// Because we must commit to a `pendingUnitCount` in order to
- /// `becomeCurrent`, for now it only attaches for a single unit.
+ /// The handler passed to `map` can use implicit progress reporting.
+ /// During the handler, the first `Progress` object created using
+ /// `parent: .current()` will be given a 100x slice of the task chain on
+ /// macOS 10.11, iOS 9, and above.
func beginMap() {
+ root.expandsAddedChildren = true
root.becomeCurrent(withPendingUnitCount: TaskChain.singleUnit)
}
/// See `beginMap`.
func commitMap() {
root.resignCurrent()
+ root.expandsAddedChildren = false
}
// MARK: -
diff --git a/Tests/TaskTests/TaskProgressTests.swift b/Tests/TaskTests/TaskProgressTests.swift
index 1ff2721c..aaa217f5 100644
--- a/Tests/TaskTests/TaskProgressTests.swift
+++ b/Tests/TaskTests/TaskProgressTests.swift
@@ -15,6 +15,8 @@ import Task
import Deferred
#endif
+// swiftlint:disable type_body_length
+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
class TaskProgressTests: CustomExecutorTestCase {
static let allTests: [(String, (TaskProgressTests) -> () throws -> Void)] = [
@@ -308,5 +310,46 @@ class TaskProgressTests: CustomExecutorTestCase {
XCTAssertEqual(task.progress.totalUnitCount, 103)
XCTAssertEqual(task.progress.fractionCompleted, 1)
}
+
+ func testThatMappingWithCustomProgressIsWeighted() {
+ let promise1 = Task.Promise()
+ let expect = expectation(description: "map handler has started executing")
+
+ let task = promise1
+ .map(upon: .any(), transform: { (value) -> Int in
+ XCTAssertNotNil(Progress.current())
+
+ let customProgress = Progress(totalUnitCount: 32)
+ expect.fulfill()
+
+ customProgress.completedUnitCount = 1
+ customProgress.completedUnitCount = 2
+ customProgress.completedUnitCount = 4
+ customProgress.completedUnitCount = 8
+ customProgress.completedUnitCount = 16
+ customProgress.completedUnitCount = 32
+
+ return value * 2
+ })
+ .map(upon: .any(), transform: { "\($0)" })
+ .map(upon: .any(), transform: { "\($0)\($0)" })
+
+ XCTAssertEqual(task.progress.completedUnitCount, 0)
+ XCTAssertEqual(task.progress.totalUnitCount, 4)
+ XCTAssertEqual(task.progress.fractionCompleted, 0)
+
+ promise1.succeed(with: 9000)
+
+ wait(for: [ expect ], timeout: shortTimeout)
+
+ XCTAssertEqual(task.progress.totalUnitCount, 103)
+
+ wait(for: [
+ expectation(toFinish: task.progress)
+ ], timeout: longTimeout)
+
+ XCTAssertEqual(task.progress.completedUnitCount, 103)
+ XCTAssertEqual(task.progress.totalUnitCount, 103)
+ }
}
#endif