⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Overview

Build Status Plaforms Carthage Compatible Swift Package Manager Compatible Pod version

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your application. As our applications grow in complexity, the more and more work needs to be done. You need to start thinking about how to categorize and optimize work, how to make that work more efficient, more optimized and finally, faster. In most cases that doesn’t end very well because you need to know a lot about concurrency, multithreading etc. - it’s a very complex field. You need to know all API specifics before you are able to write something.

Overdrive was created as a result of that struggle. It is a framework that exposes several simple concepts which are made on top of complex system frameworks that enable multithreading, concurrency and most importantly, more speed.

let task = URLSessionTask(url: "https://api.swiftable.io")

task
  .retry(3)
  .onValue { json in
    print(json["message"])
  }.onError { error in
    print(error)
}

TaskQueue.background.add(task: task)

Contents:

What can I do with Overdrive?

  • Execute tasks concurrently
  • Utilize multi-core systems by default
  • Easily defer task execution to custom thread or queue
  • Ensure that multiple tasks are executed in the correct order
  • Express custom conditions under which tasks can be executed
  • Enforce testability
  • Retry tasks that finished with errors
  • Write thread safe code by default

Requirements

  • Xcode 8.0+
  • Swift 3
  • Platforms:
    • iOS 8.0+
    • macOS 10.11+
    • tvOS 9.0+
    • watchOS 2.0+
    • Ubuntu

Installation

Carthage

github "arikis/Overdrive" >= 0.3

Cocoa Pods

platform :ios, '8.0'
use_frameworks!

target 'Your App Target' do
  pod 'Overdrive', '~> 0.3'
end

Swift Package Manager

import PackageDescription

let package = Package(
  name: "Your Package Name",
  dependencies: [
    .Package(url: "https://github.com/arikis/Overdrive.git",
             majorVersion: 0,
           minor: 3)
  ]
)

Manual installation

Overdrive can also be installed manually by dragging the Overdrive.xcodeproj to your project and adding Overdrive.framework to the embedded libraries in project settings.

Usage

Overdrive features two main classes:

Workflow:

  1. Create subclass of Task<T>
  2. Override run() method and encapsulate any synchronous or asynchronous operation
  3. Finish execution with value(T) or error(Error) by using finish(with:) method
  4. Create instance of subclass
  5. Add it to the TaskQueue when you want to start execution

Example Task<UIImage> subclass for photo download task:

class GetLogoTask: Task<UIImage> {

  override func run() {
    let logoURL = URL(string: "https://swiftable.io/logo.png")!

    do {
      let logoData = try Data(contentsOf: logoURL)
      let image = UIImage(data: logoData)!
      finish(with: .value(image)) // Finish with image
    } catch {
      finish(with: .error(error)) // Finish with error if any
    }
  }
}

To setup completion blocks, you use onValue and onError methods:

let logoTask = GetLogoTask()

logoTask
  .onValue { logo in
    print(logo) // UIImage object
  }.onError { error in
    print(error)
}

To execute the task add it to the instance of TaskQueue

let queue = TaskQueue()
queue.add(task: logoTask)

Concurrency

TaskQueue executes tasks concurrently by default. Maximum number of concurrent operations is defined by the current system conditions. If you want to limit the number of maximum concurrent task executions use maxConcurrentTaskCount property.

let queue = TaskQueue()
queue.maxConcurrentTaskCount = 3

Thread safety

All task properties are thread-safe by default, meaning that you can access them from any thread or queue and not worry about locks and access synchronization.

Inspiration

Overdrive framework was heavily inspired by talks and code from several Apple WWDC videos.

Overdrive is a term for an effect used in electric guitar playing that occurs when guitar amp tubes starts to produce overdriven, almost distorted sound, due to the higher gain(master) setting.

Long term plans

This section defines some long term plans for Overdrive. They're not scheduled for implementation or for any specific version.

Remove Foundation.Operation dependency

Currently, Overdrive leverages Foundation.Operation and Foundation.OperationQueue classes for concurrency and execution. While those classes provide excellent functionality, they're still rewrites of their Objective C counterpart (NSOperation and NSOperationQueue). This means that writing Task<T> requires a lot of overrides and state management.

For example, any task subclass must override run() method to define execution point. If this method is not overridden, queue will perform assert to notify that this method should be overridden. Same will happen if super.run() is called.

In the future, Overdrive should only use libdispatch for it's functionality.

Comments
  • Release with the memory leak fixed?

    Release with the memory leak fixed?

    Overdrive: 0.2.2 Package manager (SPM, Carthage, CocoaPods, Manual): CocoaPods Xcode: 8.3.1 Platform: iOS

    Hi @arikis,

    The project I'm working on requires Overdrive to work, and while I know I'm not supposed to be asking for ETAs around here, I wanted to know if we can just release a minor bumped version of Overdrive (maybe 0.2.3) with mainly the task observer memory leak fixed. I would be happy to help in any way I can.

    opened by biocross 7
  • Support for Many to One Dependencies

    Support for Many to One Dependencies

    Overdrive: 0.2.2 Package manager (SPM, Carthage, CocoaPods, Manual): CocoaPods Xcode: 8.2.1 Platform: iOS

    Are there any plans for supporting repeat dependencies? For example, if I add a task A which depends on the task Z, both Z and A get added to the queue.

    Now, if I have a task B, which depends on the same instance of task Z as earlier, and I add B, it crashes because I have added the same instance of task Z again to the queue.

    What could be the other behaviour is that when Z came in the first time, Z would have been added to the queue, but the second time, Z would have simply been added as a dependency for B.

    Let me know if this is too specific to my requirements, or it's something we can have at Overdrive level?

    opened by biocross 7
  • didFinish method never called when task is finished

    didFinish method never called when task is finished

    Overdrive: 0.2.2 Package manager (SPM, Carthage, CocoaPods, Manual): CocoaPods Xcode: 8.0 Platform: iOS 10.0

    Hi,

    Following your test class, I wrote a basic example:

    // Custom Task
    class ImageTask: Task<UIImage> {
        
        override func run() {
    
            let logoURL = URL(string: "http://i2.cdn.cnn.com/cnnnext/dam/assets/161217142430-2017-cars-ferrari-1-exlarge-169.jpg")!
            
            do {
                let logoData = try Data(contentsOf: logoURL)
                let image = UIImage(data: logoData)!
                finish(with: .value(image)) // finish with image
                
            } catch {
                finish(with: .error(error)) // finish with error if any
            }
        }
        
    }
    
    // Custom Task Queue
    class CustomTaskQueue: TaskQueueDelegate {
        private var queue:TaskQueue
        
        public init() {
            self.queue = TaskQueue(qos: .default)
            self.queue.delegate = self
        }
        
        public func start() {
            let t1 = ImageTask()
            t1.name = "Image Task"
            t1.onValue { image in
                print("image downloaded \(image)")
            }
            
            t1.onError { error in
                print("image error \(error.localizedDescription)")
            }
            
            self.queue.add(task: t1)
        }
        
        func didAdd<T>(task: Task<T>, toQueue queue: TaskQueue) {
            print("task did add \(task.name)")
        }
        
        func didFinish<T>(task: Task<T>, inQueue queue: TaskQueue) {
            print("task did finish \(task.name)")
        }
        
    }
    

    And then call it via the following lines:

    let taskQueue = CustomTaskQueue()
    taskQueue.start()
    

    The Xcode terminal log output:

    task did add Optional("Image Task")
    image downloaded <UIImage: 0x61800008f0a0>, {780, 438}
    

    I can't receive the end message task did finish Optional("Image Task") when a task has been finished. I listen to the didFinish<T>(task: Task<T>, inQueue queue: TaskQueue) method but nothing happens. Did I make a mistake ?

    Thank you in advance

    question 
    opened by pimeo 2
  • Retry triggered on success?

    Retry triggered on success?

    Overdrive: 0.2.1 Package manager (SPM, Carthage, CocoaPods, Manual): Cocoapods 1.1 Xcode: 8.2 beta 2 Platform: iOS 10

    Running a simple, non-failable task with retry, retries even if the task finishes without error.

    let inlineTask = InlineTask { (_) in
        print("inlineTask")
    }.retry(2)
    
    queue.add(task: inlineTask)
    

    prints inlineTask 3 Times to the console, even if the documentation of retry states:

    If the task finishes with error, it will be executed again until retry count becomes zero.

    bug 
    opened by hffmnn 2
  • Tasks added to suspended TaskQueue won't run

    Tasks added to suspended TaskQueue won't run

    Overdrive: 0.2.2 Package manager (SPM, Carthage, CocoaPods, Manual): CocoaPods Xcode: 8.2.1 Platform: iOS 10

    Tasks added to a TaskQueue being suspended don't run after setting isSuspended = false. Seems to be a Task specific issue because adding a BlockOperation to the TaskQueue's internal OperationQueue does not cause this issue. Please notice the attached sample code. ViewController.swift.zip

    bug 
    opened by RaimarT 1
  • fixed state inconstancy when cancelling task

    fixed state inconstancy when cancelling task

    I encountered a problem when cancelling a task that's in pending state because it has a dependency on another task that's still running. As you just returned true for isReady property when in pending state start() is called on the cancelled task calling moveToFinishedState() leading to an illegal state transition from .pending to .finished. I solved this issue by setting the task's state to .ready as soon as super.isReady returns true.

    opened by RaimarT 1
  • `maxConcurrentTaskCount` is inaccessible for clients

    `maxConcurrentTaskCount` is inaccessible for clients

    Overdrive version: 0.2.0

    Package manager: CocoaPods 1.1

    This snippet

    let queue = TaskQueue()
    queue.maxConcurrentTaskCount = 3
    

    throws 'maxConcurrentTaskCount' is inaccessible due to 'internal' protection level. See #2 for a possible fix.

    opened by hffmnn 1
  • SynchronousTask not finishing if result property is not set

    SynchronousTask not finishing if result property is not set

    Description: Since SynchronousTask implementation relies on result property being set to finish, not setting the result property would make the task execute forever.

    Proposed fix: Add internal method to finish the task if the result is not set.

    bug 
    opened by saidsikira 1
  • fixed state inconstancy when cancelling task

    fixed state inconstancy when cancelling task

    I encountered a problem when cancelling a task that's in pending state because it has a dependency on another task that's still running. As you just returned true for isReady property when in pending state start() is called on the cancelled task calling moveToFinishedState() leading to an illegal state transition from .pending to .finished. I solved this issue by setting the task's state to .ready as soon as super.isReady returns true. Added appropriate test.

    opened by RaimarT 0
  • fixed state inconstancy when cancelling task

    fixed state inconstancy when cancelling task

    I encountered a problem when cancelling a task that's in pending state because it has a dependency on another task that's still running. As you just returned true for isReady property when in pending state start() is called on the cancelled task calling moveToFinishedState() leading to an illegal state transition from .pending to .finished. I solved this issue by setting the task's state to .ready as soon as super.isReady returns true.

    opened by RaimarT 0
  • fixed state inconstancy when cancelling task

    fixed state inconstancy when cancelling task

    I encountered a problem when cancelling a task that's in pending state because it has a dependency on another task that's still running. As you just returned true for isReady property when in pending state start() is called on the cancelled task calling moveToFinishedState() leading to an illegal state transition from .pending to .finished. I solved this issue by setting the task's state to .ready as soon as super.isReady returns true.

    opened by RaimarT 0
  • maxConcurrentCount=1 only executes the first task in the queue

    maxConcurrentCount=1 only executes the first task in the queue

    Overdrive: 0.3 Package manager (SPM, Carthage, CocoaPods, Manual): n/a Xcode: 8.3.2 (macOS 10.12.5) Platform: iOS

    When TaskQueue.maximumConcurrentTaskCount = 1`, only one task is executed. (The 2nd task is not executed.)

    Here is a test case that indicates the issue.

        func testTwoTaskShouldBothCompleteWhenMaxConcurrentTaskCountIsOne() {
    
            queue.maxConcurrentTaskCount = 1
    
            let expectation1 = self.expectation(description: "a")
            let expectation2 = self.expectation(description: "b")
    
            let task = anyTask(withResult: .value(1))
            task.onValue { _ in
                expectation1.fulfill()
            }
            let task2 = anyTask(withResult: .value(1))
            task2.onValue { _ in
                expectation2.fulfill()
            }
            queue.add(task: task)
            queue.add(task: task2)
    
            waitForExpectations(timeout: 5)
    
        }
    

    The test fails as the "b" expectation is unfulfilled. Any ideas what could be wrong?

    bug 
    opened by xinsight 2
  • iOS 11 Crashes - Invalid State Transformation while Evaluating Conditions

    iOS 11 Crashes - Invalid State Transformation while Evaluating Conditions

    Overdrive: 0.3 Package manager (SPM, Carthage, CocoaPods, Manual): CocoaPods Xcode: 9.0 beta 6 Platform: iOS

    The dependency system seems to be failing in iOS 11. I have tried this with Xcode 9 betas 6 & 7 (latest right now). Quite a few tests are failing, especially an assertion failure TaskConditionTests, which seems to be the most severe, which actually is affecting our production users on iOS betas with crashes.

    ios11 
    opened by biocross 11
Releases(0.3)
  • 0.3(Apr 29, 2017)

    Breaking

    • Removed default method (empty) implementations of TaskQueueDelegate and TaskCondition methods as they had no real usage and could present problems if signatures are changes.

    Fixed

    • Fixed retain cycle when task is added to the queue

      • @RaimarT [#7]
    • Fixed state inconsistency when cancelling task

      • @RaimarT [#11]
    • Fixed an issue with adding task to a suspended queue

    Updates

    • Updated signatures of some methods to conform to Swift 3 API guidelines.
    • You can now throw in onValue(:_) task method. Error will be passed to the onError(:_) method.
    • Added throwable flatMap, flatMapError, map and mapError operators on Result<T> enum.
    • Extended TaskQueueDelegate with taskWillFinishExecution(task:) method
      • @RaimarT [#13]

    Note: Framework binary is not attached with this release due to the issue with precompiled frameworks rdar://23551273

    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Nov 24, 2016)

  • 0.2.1(Nov 20, 2016)

  • 0.2.0(Nov 14, 2016)

  • 0.0.1(Jul 12, 2016)

Owner
Said Sikira
Global Business Development @paypal. Previously a Payments Tech Lead @iZettle
Said Sikira
Type-safe networking with Swift Concurrency

AsyncRequest AsyncRequest is a type-safe framework for building a suite of requests to communicate with an API, built on top of Swift Concurrency. Ins

Light Year Software, LLC 1 Feb 9, 2022
AsyncTaskKit - contains some additions to async/await Task

AsyncTaskKit This repo contains some additions to async/await Task. In general i

Michał Zaborowski 0 Jan 2, 2022
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
Slack message generator and API client, written in Swift with Result Builders and Concurrency

Slack Message Client This package provides a Swift object model for a Slack Block Kit message, as well as a Result Builder convenience interface for e

Mike Lewis 2 Jul 30, 2022
A complete set of primitives for concurrency and reactive programming on Swift

A complete set of primitives for concurrency and reactive programming on Swift 1.4.0 is the latest and greatest, but only for Swift 4.2 and 5.0 use 1.

AsyncNinja 156 Aug 31, 2022
A Swift DSL that allows concise and effective concurrency manipulation

NOTE Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves man

Jason Fieldman 25 May 24, 2019
The projects and materials that accompany the Modern Concurrency in Swift book

Modern Concurrency in Swift: Materials This repo contains all the downloadable materials and projects associated with the Modern Concurrency in Swift

raywenderlich 137 Dec 16, 2022
Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6.

ConcurrencyCompatibility Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6. Xcode 13.2 adds backwards deployme

Zachary Waldowski 9 Jan 3, 2023
A Modern Concurrency and Synchronization for Swift.

##Features Simple Atomic<T> class for numbers and strings. Uncomplicated dispatch keyword for firing off background routines. Awesome Chan<T> for conc

Josh Baker 421 Jun 30, 2022
A declarative state management and dependency injection library for SwiftUI x Concurrency

A declarative state management and dependency injection library for SwiftUI x Concurrency

Ryo Aoyama 199 Jan 1, 2023
iOS 13-compatible backports of commonly used async/await-based system APIs that are only available from iOS 15 by default.

AsyncCompatibilityKit Welcome to AsyncCompatibilityKit, a lightweight Swift package that adds iOS 13-compatible backports of commonly used async/await

John Sundell 367 Jan 5, 2023
Swift concurrency collection support

AsyncCollections Functions for running async processes on Swift Collections ForEach Run an async function on every element of a Sequence. await array.

Adam Fowler 11 Jul 11, 2022
An introduction to using Swift's new concurrency features in SwiftUI

SwiftUI Concurrency Essentials An introduction to using Swift's new concurrency features in SwiftUI Discuss with me · Report Bug · Request Feature Art

Peter Friese 80 Dec 14, 2022
Functional Concurrency Primitives

Concurrent Concurrent is a collection of functional concurrency primitives inspired by Concurrent ML and Concurrent Haskell. Traditional approaches to

TypeLift 206 Dec 24, 2022
AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency

AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency, such as combineLatest and distinctUntilChanged.

Ben Pious 3 Jan 18, 2022
🎭 Swift async/await & Actor-powered effectful state-management framework.

?? Actomaton ??‍?? Actor + ?? Automaton = ?? Actomaton Actomaton is Swift async/await & Actor-powered effectful state-management framework inspired by

Yasuhiro Inami 199 Dec 20, 2022
Queues, timers, and task groups in Swift

Dispatcher eases the pain of using Grand Central Dispatch by introducing 4 new Swift classes. Dispatcher Queue Group Timer Requirements Swift 2.0+ Ins

Alec Larson 109 Jan 29, 2022
Async and concurrent versions of Swift’s forEach, map, flatMap, and compactMap APIs.

CollectionConcurrencyKit Welcome to CollectionConcurrencyKit, a lightweight Swift package that adds asynchronous and concurrent versions of the standa

John Sundell 684 Jan 9, 2023
Kommander is a Swift library to manage the task execution in different threads.

A lightweight, pure-Swift library for manage the task execution in different threads. Through the definition a simple but powerful concept, Kommand.

Intelygenz 173 Apr 11, 2022