Giter Club home page Giter Club logo

operationplus's Introduction

Build Status License Platforms

OperationPlus

OperationPlus is a set of NSOperation subclasses and extensions on NSOperation/NSOperationQueue. Its goal is to fill in the API's missing pieces. You don't need to learn anything new to use it.

NSOperation has been around for a long time, and there are now two potential first-party alternatives, Combine and Swift concurrency. OperationPlus includes some facilities to help Combine and NSOperation interoperate conveniently.

Integration

Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/ChimeHQ/OperationPlus.git")
]

NSOperation Subclasses

  • BaseOperation: provides core functionality for easier NSOperation subclassing
  • AsyncOperation: convenience wrapper around BaseOperation for async support
  • AsyncBlockOperation: convenience class for inline async support
  • (Async)ProducerOperation: produces an output
  • (Async)ConsumerOperation: accepts an input from a ProducerOperation
  • (Async)ConsumerProducerOperation: accepts an input from a ProducerOperation and also produces an output

BaseOperation

This is a simple NSOperation subclass built for easier extensibility. It features:

  • Thread-safety
  • Timeout support
  • Easier cancellation handling
  • Stricter state checking
  • Built-in asynchrous support
  • Straight-foward customization
let a = BaseOperation(timeout: 5.0)

// NSOperation will happily allow you do this even
// if `a` has finished. `BaseOperation` will not.
a.addDependency(another)

// ...
public override func main() {
    // This will return true if your operation is cancelled, timed out,
    // or prematurely finished. ProducerOperation subclass state will be
    // handled correctly as well.
    if self.checkForCancellation() {
        return
    }
}
// ...

AsyncOperation

A BaseOperation subclass that can be used for your asynchronous operations. These are any operations that need to extend their lifetime past the main method.

import Foundation
import OperationPlus

class MyAsyncOperation: AsyncOperation {
    public override func main() {
        DispatchQueue.global().async {
            if self.checkForCancellation() {
                return
            }

            // do stuff

            self.finish()
        }
    }
}

There's also nothing special about this class at all -- it's there just for convenience. If you want, you can just subclass BaseOperation directly and override one method.

import Foundation
import OperationPlus

class MyAsyncOperation: BaseOperation {
    override open var isAsynchronous: Bool {
        return true
    }
}

ProducerOperation

A BaseOperation subclass that yields a value. Includes a completion handler to access the value.

import Foundation
import OperationPlus

class MyValueOperation: ProducerOperation<Int> {
    public override func main() {
        // do your computation

        self.finish(with: 42)
    }
}

// ...

let op = MyValueOperation()

op.resultCompletionBlock = { (value) in
    // use value here
}

AsyncProducerOperation

A variant of ProducerOperation that may produce a value after the main method has completed executing.

import Foundation
import OperationPlus

class MyAsyncOperation: AsyncProducerOperation<Int> {
    public override func main() {
        DispatchQueue.global().async {
            if self.checkForCancellation() {
                return
            }

            // do stuff

            self.finish(with: 42)
        }
    }
}

ConsumerOperation and AsyncConsumerOperation

A BaseOperation sublass that accepts the input of a ProducerOperation.

import Foundation
import OperationPlus

class MyConsumerOperation: ConsumerOperation<Int> {
    override func main() {
        guard let value = producerValue else {
            // handle failure in some way
        }
    }
    
    override func main(with value: Int) {
        // make use of value here, or automatically
        // fail if it wasn't successfully produced
    }
}

let op = MyConsumerOperation(producerOp: myIntProducerOperation)

AsyncBlockOperation

A play on NSBlockOperation, but makes it possible to support asynchronous completion without making an Operation subclass. Great for quick, inline work.

let op = AsyncBlockOperation { (completionBlock) in
    DispatchQueue.global().async {
        // do some async work here, just be certain to call
        // the completionBlock when done
        completionBlock()
    }
}

NSOperation/NSOperationQueue Extensions

Queue creation conveniences:

let a = OperationQueue(name: "myqueue")
let b = OperationQueue(name: "myqueue", maxConcurrentOperations: 1)
let c = OperationQueue.serialQueue()
let d = OperationQueue.serialQueue(named: "myqueue")

Enforcing runtime constraints on queue execution:

OperationQueue.preconditionMain()
OperationQueue.preconditionNotMain()

Consise dependencies:

queue.addOperation(op, dependency: opA)
queue.addOperation(op, dependencies: [opA, opB])
queue.addOperation(op, dependencies: Set([opA, opB]))

op.addDependencies([opA, opB])
op.addDependencies(Set([opA, opB]))

Queueing work when a queue's current operations are complete:

queue.currentOperationsFinished {
  print("all pending ops done")
}

Convenient inline functions:

queue.addAsyncOperation { (completionHandler) in
    DispatchQueue.global().async {
        // do some async work
        completionHandler()
    }
}

Async integration:

queue.addOperation {
    await asyncFunction1()
    await asyncFunction2()
}

let value = try await queue.addResultOperation {
    try await asyncValue()
}

Delays:

queue.addOperation(op, afterDelay: 5.0)
queue.addOperation(afterDelay: 5.0) {
  // work
}

Combine Integration

PublisherOperation

This ProducerOperation subclass takes a publisher. When executed, it creates a subscription and outputs the results.

op.publisher() // AnyPublisher<Void, Never>

producerOp.outputPublisher() // AnyPublisher<Output, Never>
publisher.operation() // PublisherOperation
publisher.execute(on: queue) // subscribes and executes chain on queue and returns a publisher for result

XCTest Support

OperationTestingPlus is an optional micro-framework to help make your XCTest-based tests a little nicer. When using Carthage, it is built as a static framework to help ease integration with your testing targets.

FulfillExpectationOperation

A simple NSOperation that will fulfill an XCTestExpectation when complete. Super-useful when used with dependencies on your other operations.

NeverFinishingOperation

A great way to test out your Operations' timeout behaviors.

OperationExpectation

An XCTestExpectation sublass to make testing async operations a little more XCTest-like.

let op = NeverFinishingOperation()

let expectation = OperationExpectation(operation: op)
expectation.isInverted = true

wait(for: [expectation], timeout: 1.0)

Suggestions or Feedback

We'd love to hear from you! Get in touch via an issue or pull request.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

operationplus's People

Contributors

mattmassicotte avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

operationplus's Issues

NSRecursiveLock to dispatch_sync

I think it is not good idea to use NSRecursiveLock for AsyncOperation,
as I know - lock, unlock should call from a same thread.
Your variant of implementation does not guarantee that.

What do you think about this?

Cocoapods support

I see Cocoapods support was removed. Is there any chance it could come back?

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.