110 lines
6.1 KiB
Markdown
110 lines
6.1 KiB
Markdown
# Operations
|
||
|
||
## Overview
|
||
|
||
An operation runs an asynchronous task which starts with a request and ends with either success or failure. At high level an operation has the same interface across all Amplify platforms. Some operations are quick, like getting a pre-signed URL because the size of the response is very small. Other operations like uploading a large file will take longer and offer progress updates so that the UI can show the user the current status. All operations can also be cancelled which may be necessary for various purposes.
|
||
|
||
## Running an Operation
|
||
|
||
A basic operation is created with a `Request` and a result listener which is a closure which will receive a `Result` which will either contain the value for `Success` or `Failure`. Let’s see how we would run an operation named `FastOperation`. It will take in a `Request` and a closure to listen for the `Result`. See the code below.
|
||
|
||
```swift
|
||
let request = FastOperationRequest(numbers: [1, 2, 3])
|
||
let operation = FastOperation(request: request) { result in
|
||
switch result {
|
||
case .success(let result):
|
||
print("Result: \(result.value)")
|
||
case .failure(let error):
|
||
print("Result: \(error)")
|
||
}
|
||
}
|
||
operation.start()
|
||
```
|
||
|
||
A request is created and the initializer takes in a series of numbers. The operation is created with the request and a closure is provided to listen for the result. The `Result` type is an enum to a switch statement can be used to access the value for `Success` or `Failure`.
|
||
|
||
## Operations with Progress Updates
|
||
|
||
For operations which take a longer time to run there is another listener which can provide updates as the work is in process. It is used as the `InProcess` generic type which is often the `Progress` type built into the platform which can provide the total unit count with the value for completed unit count being updated while work is progressing. The percentage can be collected from `Progress` with the `fractionCompleted` property.
|
||
|
||
Below is code which works with `LongOperation` which uses a `LongOperationRequest` and ends with `LongOperationResult`.
|
||
|
||
```swift
|
||
let request = LongOperationRequest(steps: 10, delay: 0.1)
|
||
let operation = LongOperation(request: request,
|
||
progressListener: { progress in
|
||
let percent = Int(progress.fractionCompleted * 100)
|
||
print("Progress: \(percent)")
|
||
}, resultListener: { result in
|
||
switch result {
|
||
case .success:
|
||
print("Result: Success")
|
||
case .failure(let error):
|
||
print("Result: \(error)")
|
||
}
|
||
})
|
||
```
|
||
|
||
Notice that this operation includes a progress listener which uses the `Progress` type. As the work progresses this listener will be executed every time the value for completed unit count is updated. The `Request` used by this operation specifies the number of steps and delay for each step.
|
||
|
||
This code includes 10 steps with a short delay. The progress listener is called multiple times and the print statement shows the percent of completion. Eventually the result listener will be called and will be printed as well. These progress updates can be used to update UI or to log this activity.
|
||
|
||
## Canceling an Operation
|
||
|
||
For a long running operation which could be using resources like battery or network resources it may be helpful to cancel operations if the result is no longer needed. A user may be scrolling through a list which shows images and these images are being provided by an operation in the Storage category. If the row which would show an image is scrolled off the screen the operation which is requesting the image can be cancelled. Inside the operation is a network request which can be accessed with a task. This task can be cancelled to stop the request and release resources. These resources could be used for other network requests which are pending.
|
||
|
||
With previous long operation we can update the progress listener to cancel the request once it reaches 50% completed with this revised code below in a Swift Playground.
|
||
|
||
```swift
|
||
import PlaygroundSupport
|
||
|
||
PlaygroundPage.current.needsIndefiniteExecution = true
|
||
|
||
func die() {
|
||
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
||
print("End.")
|
||
PlaygroundPage.current.finishExecution()
|
||
}
|
||
}
|
||
|
||
let cancelEnabled = true
|
||
|
||
// pre-declare cancel closure to define once operation is defined
|
||
var cancel: (() -> Void)?
|
||
|
||
let request = LongOperationRequest(steps: 10, delay: 0.1)
|
||
let operation = LongOperation(request: request,
|
||
progressListener: { progress in
|
||
let percent = Int(progress.fractionCompleted * 100)
|
||
print("Progress: \(percent)")
|
||
|
||
if cancelEnabled && percent >= 50 {
|
||
// cancel halfway through the steps
|
||
cancel?()
|
||
die()
|
||
}
|
||
}, resultListener: { result in
|
||
switch result {
|
||
case .success:
|
||
print("Result: Success")
|
||
case .failure(let error):
|
||
print("Result: \(error)")
|
||
}
|
||
die()
|
||
})
|
||
|
||
cancel = {
|
||
operation.cancel()
|
||
}
|
||
```
|
||
|
||
When `cancelEnabled` is set to true the operation will be cancelled. After a short delay the playground page will finish execution. At the start of every step of the operation there is a check to see if the operation has been cancelled which prevents it from proceeding. Instead it finishes without calling the result listener.
|
||
|
||
## Request, Success and Failure
|
||
|
||
There are many supported operations in the Amplify iOS library across the categories. While operations work in the same way there are differences which can be handled using generic types. The actual type for `Request`, `Success` and `Failure` are defined by each implementation of an operation. Each unique request can support any properties which are needed along with the initializer. Every `Success` type can be any type, such as Int or even a custom struct. The `Failure` type should conform to `AmplifyError`. When working with a specific operation these generic types will be known so the necessary values can be provided for the request when it is initialized and the listeners can receive the result that is expected.
|
||
|
||
There is also the `InProcess` generic type which is often the `Progress` type. For the operations which support it, the listener passes an instance which can be used for updates which are in process with the operation.
|
||
|
||
|