Skip to content

Latest commit

 

History

History
109 lines (80 loc) · 6.12 KB

OPERATIONS.md

File metadata and controls

109 lines (80 loc) · 6.12 KB

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.

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.

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.

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.