Code Monkey home page Code Monkey logo

peakoperation's Introduction

Peak Operation

PeakOperation is a Swift microframework providing enhancement and conveniences to Operation. It is part of the Peak Framework.

Concurrent Operations

ConcurrentOperation is an abstract Operation subclass that can perform work asynchronously. You override execute() to perform your work, and when it is completed call finish() to complete the operation.

class MyOperation: ConcurrentOperation {
   override func execute() {
        print("Hello World!")
        finish()
    }
}

let queue = OperationQueue()
let operation = MyOperation()
operation.enqueue(on: queue)

This means that you can perform asynchronous work inside execute(), such as performing a URLSession request.

Chaining operations

Operation provides the ability to add dependencies between operations. PeakOperation builds upon this functionality and wraps it in an easy-to-use API.

let firstOperation = ...
let secondOperation = ...
        
firstOperation
    .then(do: secondOperation)
    .enqueue()

In the example above, secondOperation will run once firstOperation finishes.

You can also call enqueueWithProgress() on a chain of operations or overallProgress() on a single operation to track their progress.

let progress: Progress = firstOperation
    .then(do: secondOperation)
    .enqueueWithProgress()

// or

let progress: Progress = secondOperation.overallProgress()

Passing Results

Adding dependencies between operations is useful, but passing results between operations is where PeakOperation really shines. PeakOperation includes two protocols your operation can conform to: ProducesResult and ConsumesResult.

Let's say we have three operations. The first produces a Result<Int, Error>.

class IntOperation: ConcurrentOperation, ProducesResult {
    var output: Result<Int, Error>
    override func execute() {
        output = Result { 1 }
        finish()
    }
}

The second operation consumes a Result<Int, Error> and produces a Result<String, Error>. It unpacks its input, adds 1, converts it to a string, then sets it's output.

class AddOneOperation: ConcurrentOperation, ConsumesResult, ProducesResult {
    var input: Result<Int, Error>
    var output: Result<String, Error>
    override func execute() {
        output = Result { "\(try input.get() + 1)" }
        finish()
    }
}

The final operation consumes a Result<String, Error>. It unpacks it and prints it to the console:

class PrintOperation: ConcurrentOperation, ConsumesResult {
    var input: Result<String, Error>
    override func execute() {
        do {
            print("Hello \(try input.get())!")
        } catch { }
        finish()
    }
}

Using passesResult(to:), these three operations can be chained together!

IntOperation()
    .passesResult(to: AddOneOperation())
    .passesResult(to: PrintOperation())
    .enqueue()

    // Hello 2!

As long as the input type matches the output type, you can pass results between any operations conforming to the protocols.

If any of the operations fail and its result is .failure, then the result will still be passed into the next operation. It's up to you to unwrap the result and deal with the error appropriately, perhaps by rethrowing the error.

class RethrowingOperation: ConcurrentOperation, ConsumesResult, ProducesResult {
    var input: Result<String, Error>
    var output: Result<String, Error>
    override func execute() {
        do {
            let _ = try input.get()
            output = ...
        } catch { 
            output = Result { throw error }
        }
        finish()
    }
}

That way, any of the operations can fail and you can still retrieve the error at the end.

let failingOperation = ...
let successfulOperation = ...
let anotherSuccessfulOperation = ...

failingOperation
    .passesResult(to: successfulOperation)
    .passesResult(to: anotherSuccessfulOperation)
    .enqueue()

anotherSuccessfulOperation.addResultBlock { result in
    // result would contain the error from failingOperation 
    // even though the other operations still ran
}

Grouping

GroupChainOperation takes an operation and its dependants and executes them on an internal queue. The result of the operations is retained and it is inspected in order that this operation can produce a result of type Result<Void, Error> - the value is lost, but the .success/.failure is kept. This allows you to chain together groups with otherwise incompatible input/outputs.

// would otherwise produce String, now produces Void
let group1 = intOperation
    .passesResult(to: stringOperation)
    .group()

// Would otherwise accept Bool, now consumes Void
let group2 = boolOperation
    .passesResult(to: anyOperation)
    .group()

group1
    .passesResult(to: group2)
    .enqueue()

Retrying

Sometimes an operation might fail. Perhaps you are dealing with a flaky web service or connection. For this, you can subclass RetryingOperation. This is an operation which ProducesResult, and if the result is .failure, it will try again using a given retryStrategy closure.

class MyRetryingOperation: RetryingOperation<AnyObject> {
   override func execute() {
        output = Result { throw error }
        finish()
    }
}

let operation = MyRetryingOperation()
operation.retryStrategy = { failureCount in
    return failureCount < 3
}

You can provide your own block as a retryStrategy. Here, the operation will be run 3 times before it finally fails.

There are 2 provided StrategyBlocks:

  • RetryStrategy.none
  • RetryStrategy.repeat(times: Int)

Examples

Please see the included tests for further examples. Also check out PeakNetwork which uses PeakOperation extensively.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning.

License

This project is licensed under the MIT License - see the LICENSE.md file for details

Acknowledgments

Peak Framework

The Peak Framework is a collection of open-source microframeworks created by the team at 3Squared, named for the Peak District. It is made up of:

Name Description
PeakCoreData Provides enhances and conveniences to Core Data.
PeakNetwork A networking framework built on top of Session using PeakOperation, leveraging the power of Codable.

peakoperation's People

Contributors

blork avatar zilmarinen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.