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

Commit

Permalink
Merge pull request #195 from bignerdranch/zwaldowski/future-to-task
Browse files Browse the repository at this point in the history
Allow creating Task with futures that cannot fail
  • Loading branch information
zwaldowski authored Nov 16, 2017
2 parents aba52fc + 34890ac commit 403b4f2
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Sources/Atomics/include/Atomics.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ static inline uint_fast8_t bnr_atomic_bitmask_and(volatile bnr_atomic_bitmask_t
}

OS_ALWAYS_INLINE
static inline bool bnr_atomic_bitmask_test(const bnr_atomic_bitmask_t *_Nonnull target, uint_fast8_t value) {
static inline bool bnr_atomic_bitmask_test(volatile bnr_atomic_bitmask_t *_Nonnull target, uint_fast8_t value) {
return (__c11_atomic_load((_Atomic(uint_fast8_t) *)&target->value, __ATOMIC_RELAXED) & value) != 0;
}

Expand Down
25 changes: 23 additions & 2 deletions Sources/Task/ExistentialTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public final class Task<SuccessValue>: NSObject {
/// `cancellation` will be called asynchronously, but not on any specific
/// queue. If you must do work on a specific queue, schedule work on it.
public convenience init(future base: Future<Result>, cancellation: (() -> Void)? = nil) {
let progress = Progress.wrapped(base, cancellation: cancellation)
let progress = Progress.wrappingSuccess(of: base, cancellation: cancellation)
self.init(future: base, progress: progress)
}

Expand All @@ -70,6 +70,12 @@ public final class Task<SuccessValue>: NSObject {
where Task.Value: Either, Task.Value.Left == Error, Task.Value.Right == SuccessValue {
self.init(future: Future(task: base), progress: progress)
}

/// Creates a task whose `upon(_:execute:)` methods use those of `base`.
public convenience init<OtherFuture: FutureProtocol>(success base: OtherFuture, progress: Progress)
where OtherFuture.Value == SuccessValue {
self.init(future: Future(success: base), progress: progress)
}
#else
fileprivate let cancellation: (() -> Void)
fileprivate var rawIsCancelled = UnsafeAtomicBool()
Expand Down Expand Up @@ -150,7 +156,8 @@ extension Task {
public convenience init<OtherFuture: FutureProtocol>(_ base: OtherFuture, cancellation: (() -> Void)? = nil)
where OtherFuture.Value: Either, OtherFuture.Value.Left == Error, OtherFuture.Value.Right == SuccessValue {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
self.init(future: Future(task: base), progress: .wrapped(base, cancellation: cancellation))
let progress = Progress.wrappingSuccess(of: base, cancellation: cancellation)
self.init(future: Future(task: base), progress: progress)
#else
let asTask = (base as? Task<SuccessValue>)

Expand All @@ -165,6 +172,20 @@ extension Task {
#endif
}

/// Creates a task whose `upon(_:execute:)` methods use those of `base`.
///
/// `cancellation` will be called asynchronously, but not on any specific
/// queue. If you must do work on a specific queue, schedule work on it.
public convenience init<OtherFuture: FutureProtocol>(success base: OtherFuture, cancellation: (() -> Void)? = nil)
where OtherFuture.Value == SuccessValue {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let progress = Progress.wrappingCompletion(of: base, cancellation: cancellation)
self.init(future: Future<Value>(success: base), progress: progress)
#else
self.init(future: Future<Value>(success: base))
#endif
}

/// Creates an operation that has already completed with `value`.
public convenience init(success value: @autoclosure() throws -> SuccessValue) {
let future = Future<Result>(value: Result(from: value))
Expand Down
12 changes: 10 additions & 2 deletions Sources/Task/ResultFuture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ extension FutureProtocol where Value: Either {
}
}

extension Future where Value: Either {
extension Future where Value: Either, Value.Left == Error {
/// Create a future having the same underlying task as `other`.
public init<Other: FutureProtocol>(task other: Other)
where Other.Value: Either, Other.Value.Left == Error, Other.Value.Right == Value.Right {
where Other.Value: Either, Other.Value.Left == Value.Left, Other.Value.Right == Value.Right {
if let asSelf = other as? Future<Value> {
self.init(asSelf)
} else {
Expand All @@ -73,4 +73,12 @@ extension Future where Value: Either {
})
}
}

/// Create a future having the same underlying task as `other`.
public init<Other: FutureProtocol>(success other: Other)
where Other.Value == Value.Right {
self.init(other.every {
Value(success: $0)
})
}
}
2 changes: 1 addition & 1 deletion Sources/Task/TaskCollections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension Collection where Iterator.Element: FutureProtocol, Iterator.Element.Va
if let task = future as? Task<Iterator.Element.Value.Right> {
progress.adoptChild(task.progress, orphaned: false, pendingUnitCount: 1)
} else {
progress.adoptChild(.wrapped(future, cancellation: nil), orphaned: true, pendingUnitCount: 1)
progress.adoptChild(.wrappingSuccess(of: future, cancellation: nil), orphaned: true, pendingUnitCount: 1)
}
#else
if let task = future as? Task<Iterator.Element.Value.Right> {
Expand Down
40 changes: 23 additions & 17 deletions Sources/Task/TaskProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,28 @@ extension Progress {
}

/// A simple indeterminate progress with a cancellation function.
@nonobjc static func wrapped<Future: FutureProtocol>(_ future: Future, cancellation: (() -> Void)?) -> Progress where Future.Value: Either {
switch (future as? Task<Future.Value.Right>, cancellation) {
@nonobjc static func wrappingCompletion<OtherFuture: FutureProtocol>(of base: OtherFuture, cancellation: (() -> Void)?) -> Progress {
let progress = Progress(parent: nil, userInfo: nil)
progress.totalUnitCount = base.wait(until: .now()) != nil ? 0 : -1

if let cancellation = cancellation {
progress.cancellationHandler = cancellation
} else {
progress.isCancellable = false
}

base.upon(.global(qos: .utility)) { _ in
progress.totalUnitCount = 1
progress.completedUnitCount = 1
}

return progress
}

/// A simple indeterminate progress with a cancellation function.
@nonobjc static func wrappingSuccess<OtherTask: FutureProtocol>(of base: OtherTask, cancellation: (() -> Void)?) -> Progress
where OtherTask.Value: Either {
switch (base as? Task<OtherTask.Value.Right>, cancellation) {
case (let task?, nil):
return task.progress
case (let task?, let cancellation?):
Expand All @@ -221,21 +241,7 @@ extension Progress {
progress.adoptChild(task.progress, orphaned: false, pendingUnitCount: 1)
return progress
default:
let progress = Progress(parent: nil, userInfo: nil)
progress.totalUnitCount = future.wait(until: .now()) != nil ? 0 : -1

if let cancellation = cancellation {
progress.cancellationHandler = cancellation
} else {
progress.isCancellable = false
}

future.upon(.global(qos: .utility)) { _ in
progress.totalUnitCount = 1
progress.completedUnitCount = 1
}

return progress
return .wrappingCompletion(of: base, cancellation: cancellation)
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions Tests/TaskTests/TaskTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,19 @@ class TaskTests: CustomExecutorTestCase {
waitForExpectations()
assertExecutorCalled(2)
}

func testSimpleFutureCanBeUpgradedToTask() {
let expectation = self.expectation(description: "original future filled")
let deferred = Deferred<Int>()
let task = Task<Int>(success: deferred, cancellation: nil)

task.uponSuccess { (value) in
XCTAssertEqual(value, 42)
expectation.fulfill()
}

deferred.fill(with: 42)
waitForExpectations()
}

}

0 comments on commit 403b4f2

Please sign in to comment.