concurrency-kit

πŸš„ Concurrency abstractions framework for iOS development [Task, Atomic, Lock, etc.].


Keywords
apple, async, atomics, concurrency, concurrent, dispatch-queues, framework, gcd, grand-central-dispatch, ios, ipados, locks, macos, mutex, spm, swift, swift-package-manager, task, watchos
License
MIT
Install
pod try concurrency-kit

Documentation

concurrency-kit Awesome

Platforms Language CocoaPod Build Status Coverage License

Last Update: 13/March/2019.

If you like the project, please give it a star ⭐ It will show the creator your appreciation and help others to discover the repo.

✍️ About

πŸš„ Concurrency abstractions framework for iOS development [Task, Atomic, Lock, etc.].

πŸ— Installation

CocoaPods

concurrency-kit is available via CocoaPods

pod 'concurrency-kit', '~> 1.0.0' 

Manual

You can always use copy-paste the sources method πŸ˜„. Or you can compile the framework and include it with your project.

πŸ”₯ Features

  • Atomics - synchronization primitive that is implemented in several forms: Generic, Int and Bool.
    • Fast. Under the hood a mutex (pthread_mutex_lock) that is more efficient than OSSpinLock and faster than NSLock.
    • Throwable. You can safely throw Errors and be able to delegate the handling.
  • Locks - contains a number of locks, such as:
    • UnfairLock - A lock which causes a thread trying to acquire it to simply wait in a loop ("spin") while repeatedly checking if the lock is available.
    • ReadWriteLock - An RW lock allows concurrent access for read-only operations, while write operations require exclusive access.
    • Mutex - Allows only one thread to be active in a given region of code.
  • DispatchQueue+Extensions - extended DispatchQueue, where asyncAfter and once methods add convenience.
  • Task - A unit of work that performs a specific job and usually runs concurrently with other tasks.
    • Tasks can be grouped - meaning that you are able to compose the tasks, similar to Futures & Promises and execute them serially.
    • Tasks can be sequenced - meaning that you are able to compose different groups and execute them concurrently. No need to repeatedly use DispatchGroup (enter/leave).
  • Thoroughly tested.

πŸ“š Examples

Task

In order to create a Task, you need to simply use the Task struct and the trailing closure syntax:

let uploadingTask = Task { controller in
  uploader(photos) { result in 
    switch result {
      case .success:
        controller.finish()
      case .failure(let error):          
        controller.fail(with error)
    }
  }
}
        
uploadingTask.perform { outcome in
  handle(outcome)
}

You can group the tasks, so the concurrent operations will be performed sequentially, one after another. Then, you can chain a completion closure to handle the outcome:

let filesToUpload = [file, photo, video, xml]

let group = Task.group(fileToUpload)
group.perform { outcome in 
  handle(outcome)
}

Or you can concurrently perform a collection of tasks. They will be executed asynchronously, in parallel (if possible) or concurrently, that is up to the GCD:

let filesToUpload = [file, photo, video, xml]

let group = Task.sequence(filesToUpload)
group.perform { outcome in 
  handle(outcome)
}

Atomics

Guarantees safe mutation of a property in multiple async dispatch queues. Simply wrap a property in Atomic type:

let atomic = Atomic(0)

DispatchQueue.global().async {
  atomic.modify { $0 + 1 }
}

DispatchQueue.global().async {
  atomic.modify { $0 + 1 }
}

You can also use slightly more performance-friendly AtomicInt and AtomicBool classes, hence there is no dynamic dispatch involved (though Swift compiler is smart enough to apply complier optimization called compile-time generic specialization)

Locks

Read Write Lock

A syncronozation primitive that solves one of the readers–writers problems:

let rwLock = ReadWriteLock()

rwLock.writeLock()
sharedValue += 1
rwLock.unlock()

Or you can restrict the reading access, so other threads will not be able to read the mutated value of a property until the lock will be released:

let rwLock = ReadWriteLock()

rwLock.readLock()
sharedValue += 1
rwLock.unlock()

Unfair Lock

A lock which causes a thread trying to acquire it to simply wait in a loop ("spin"), while repeatedly checking if the lock is available:

let unfairLock = UnfairLock()

unfairLock.lock()
sharedValue += 1
unfairLock.unlock()

Mutex

Used to protect shared resources. A mutex is owned by the task that takes it. In a given region of code only one thread is active:

let mutex = Mutex()

mutex.withCriticalScope {
  return sharedValue += 1
}

Dispatch Queue

There is a convenience method that removes the need to pass .now() + time in order to make an async call:

DispatchQueue.main.asyncAfter(seconds: 2.5) {
       expectation.fulfill()
}

Also, DispatchQueue.once was returned back::

// The following concurrentQueue is called multiple times, though the caughtValue will be set to value only once.
concurrentQueue.async {
  DispatchQueue.once(token: "caught") {
    caughtValue = value
  }
}

πŸ‘¨β€πŸ’» Author

Astemir Eleev

πŸ”– Licence

The project is available under MIT licence