Task
public final class Task<Success>
A type for managing some work that may either succeed or fail at some point in the future, including interacting with the end result of the work.
Tasks work exactly like futures but, because they operate on types that have one or more exclusive states to describe a success or failure, have patterns for dealing with just the successful value, the failing error, chaining dependent operations together, recovering from errors, and so on.
Returning a Task
from your API encapsulates the entire lifecycle of
asynchronous work, regardless of it being managed by OperationQueue
,
or DispatchQueue
, or URLSession
. Consumers of your Task
object might
interact with the task by cancelling, pausing, or resuming it.
Creating a Task
Like Future
, a task will forward operations involving the result of the
work being performed to some underlying type, hiding the implementation
details of your asynchronous API.
let promise = Task<Int>.Promise()
DispatchQueue.any().asyncAfter(deadline: .now() + 3) {
if Bool.random() {
promise.succeed(with: 4) // chosen by fair dice roll.
// guaranteed to be random.
} else {
promise.fail(with: Error.reallyBad)
}
}
return Task(promise)
Tasks Represent Workflows
You can design an API to use Task
even the result is known immediately.
This allows you to evolve your code over time without changing callers of
the code.
let alreadySucceeded = Task(success: "13 miles away")
let alreadyFailed = Task(failure: Error.couldNotFetchLocation)
Consider a method that checks the validity of parameters to a web service before fetching from it:
func fetchFriends(for user: User) throws -> Future<[Friend]?> {
guard !user.id.isEmpty else {
throw Error.invalidParameters
}
...
}
Consuming this asynchronous value must be done in multiple paths:
do {
let futureFriends = try fetchFriends(for: currentUser)
futureFriends.upon(managedObjectContext) { (friends) in
if let friends = friends {
do {
try import(friends)
catch {
// handle an error
}
} else {
// handle an error
}
}
} catch {
// handle an error
}
Embracing Task
as the common currency type between layers of code in
your application can consolidate these multiple branches of checking.
func fetchFriends(for user: User) -> Task<[Friend]> {
guard !user.id.isEmpty else {
return Task(failure: Error.invalidParameters)
}
...
}
fetchFriends(for: currentUser)
.map(upon: managedObjectContext, transform: import)
.uponSuccess(on: .main) { _ in /* handle success */ }
.uponFailure(on: .main) { _ in /* handle failure */ }
Tasks Can Be Cancelled
When creating a task from a future or promise, an optional cancellation
handler can be provided. Inside this method body, you can cancel ongoing
work, such as a network connection or image processing.
When the underlying work can be interrupted, cancelling a Task
will
typically lead to the operation completing with an error, such as
CocoaError.userCancelled
.
let promise = Task<UIImage>.Promise()
let operation = makeImageProcessingOperation(for: data)
operation.completionBlock = { [unowned operation] in
promise.fill(with: operation.result!)
}
operationQueue.add(operation)
return Task(promise, uponCancel: operation.cancel)
Cancellation may be invoked in any threading context. If work associated with cancellation must be done on a specific queue, dispatch to that queue from within the cancellation handler.
Tasks Can Report Progress
On macOS, iOS, watchOS, and tvOS, where apps are expected to react to
current conditions, Task
will automatically maintain instances of the
Progress
object. These objects can be used to drive UI controls displaying
that progress, as well as interactive controls like buttons to cancel,
pause, or resume the work.
In the simplest cases, using Task
and methods like map
and andThen
give some insight into the work your application is doing and improve the
user experience. Consider:
let task = downloadImage(for: url)
.map(upon: .any(), transform: decompressImage)
.map(upon: .any(), start: applyFiltersToImage)
.map(upon: .any(), start: writeImageToCache)
task.progress
will have four work units, each of which will complete after
the work assiociated with the initial task, map
, andThen
, and map
,
yielding progress updates of 25%, 50%, 75%, and 100% respectively.
If you have a better source of data for progress, like those provided on
URLSessionTask
or UIDocument
, those can also be incorporated into
Task
during creation. These Task
s are weighted more than surrounding
calls to map
or andThen
. For instance, you can modify downloadImage
above to include another source of progress:
func downloadImage(for url: URL) -> Task<Data> {
let promise = Task<Data>.Promise()
let urlSessionTask = urlSession.dataTask(with: url) {
promise.fill(with: ...)
}
urlSessionTask.resume()
return Task(promise, progress: urlSessionTask.progress)
}
downloadImage
will account for up to 90% of the returned progress. That
90% slice
will fill in as chunks of data get loaded from the network.
You may also create your own Progress
instances to be given to Task
.
Progress objects use any context you desire, like byte counts or fractions,
and will also be weighted higher by Task
. If applyFiltersToImage
above
applies 5 user-selected filters, you might create a custom progress and
update it when each of the filters is complete:
func applyFiltersToImage(_ image: UIImage) -> UIImage {
var image = image
let progress = Progress(totalUnitCount: filters.count)
for (n, filter) in filters.enumerated() {
image = filter.apply(to: image)
progress.completedUnitCount = n
}
return image
}
downloadImage
and applyFiltersToImage
will each take up to 45% of the
returned progress.
Seealso
TaskProtocol
Seealso
Future
-
A type that represents either a wrapped value or an error, representing the possible return values of a throwing function.
See moreDeclaration
Swift
public enum Result
-
The progress of the task, which may be updated as work is completed.
If the task does not report progress, this progress is indeterminate, and becomes determinate and completed when the task is finished.
Declaration
Swift
@objc dynamic public let progress: Progress
-
Creates a task whose
upon(_:execute:)
methods use those ofbase
.Declaration
Swift
public init<Wrapped>(_ wrapped: Wrapped, progress: Progress) where Success == Wrapped.Success, Wrapped : TaskProtocol
-
Creates a task whose
upon(_:execute:)
methods use those ofbase
.cancellation
will be called asynchronously, but not on any specific queue. If you must do work on a specific queue, schedule work on it.Declaration
Swift
public init<Wrapped>(_ wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Success == Wrapped.Success, Wrapped : TaskProtocol
-
Declaration
Swift
public func upon(_ executor: Executor, execute body: @escaping(Result) -> Void)
-
Declaration
Swift
public func peek() -> Result?
-
Declaration
Swift
public func wait(until timeout: DispatchTime) -> Result?
-
Declaration
Swift
public var isCancelled: Bool { get }
-
Declaration
Swift
public func cancel()
-
A type for communicating the result of asynchronous work.
Create an instance of the task’s
Promise
to be filled asynchronously. -
Creates a task whose
upon(_:execute:)
methods use those ofbase
.Declaration
Swift
public convenience init<Wrapped>(succeedsFrom wrapped: Wrapped, progress: Progress) where Success == Wrapped.Value, Wrapped : FutureProtocol
-
Creates a task whose
upon(_:execute:)
methods use those ofbase
.cancellation
will be called asynchronously, but not on any specific queue. If you must do work on a specific queue, schedule work on it.Declaration
Swift
public convenience init<Wrapped>(succeedsFrom wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Success == Wrapped.Value, Wrapped : FutureProtocol
-
Creates an operation that has already completed with
value
.Declaration
Swift
public convenience init(success value: @autoclosure() throws -> Success)
-
Creates an operation that has already failed with
error
.Declaration
Swift
public convenience init(failure error: Failure)
-
Creates a task having the same underlying operation as the
other
task.Declaration
Swift
public convenience init(_ task: Task<Success>)
-
Create a task that will never complete.
Declaration
Swift
public static var never: Task<Success> { get }
-
Captures the result of asynchronously executing
work
onqueue
.Canceling the returned task will not race with the contents of
work
. Once it begins to run, canceling will have no effect.Declaration
Swift
public static func async(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel makeError: @autoclosure @escaping() -> Failure, execute work: @escaping() throws -> Success) -> Task
Parameters
queue
A dispatch queue to perform the
work
on.flags
Options controlling how the
work
is executed with respect to system resources.produceError
Upon cancellation, this value is used to preemptively fail the task.
body
A function body that either calculates and returns the success value for the task or throws to indicate failure.