Latest 1.1.1
Homepage https://github.com/jVirus/concurrency-kit
License MIT
Platforms ios 11.0, requires ARC
Authors

concurrency-kit Awesome

[Platforms]()
[Language]()
[CocoaPod]()
Build Status
Coverage
[License]()

Last Update: 06/April/2019.

concurrency-kit

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.1.1' 

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).
  • Stateful Operation – is a custom Operation class that supports modern, Swifty state management through the usage of Atomics and Enum types.
  • 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)
}

Stateful Operation

Operation that has more ‘Swifty’ state management system, where state is an enum type with a number of possible cases. In order to demostrate the typical usage, let’s define a new custom operation for network request:

class NetworkStatefulOperation: StatefullOperation {

  // MARK: - Properties

  private let callback: (StatefullOperation?) -> Void
  private let service: NetworkService             
  private let dataHandler: Parsable            

  // MARK: - Initializers

  init(_ service: NetworkService, _ dataHandler: Parser, callback: @escaping (StatefullOperation?) -> Void) {
    self.service = service
    self.dataHandler = dataHandler
    self.callback = callback
  }

  // MARK: - Overrides

  override func executableSection() {
    service.dataTask { [weak self] result in 
      self?.dataHandler.parse(result)
      self?.finishIfNotCancelled()
      self?.callback(self)
    }
  }
}

Then, the usage of the NetworkStatefulOperation class is quite straightforward:

// 1. Create an instance of `NetworkStatefulOperation` class:
let networkiOperation = NetworkStatefulOperation(service, parser) {
  // 3. As soon as the operation is finished, this closure will be executed with the operation state that can futher be handled to properly update the UI:
  updateUI(with: $0.state)
}
// 2. Then call the `start` method:
networkOperation.start()

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

Latest podspec

{
    "name": "concurrency-kit",
    "version": "1.1.1",
    "summary": "ud83dude84 Concurrency abstractions framework for iOS development [Task, Atomic, Lock, Operation, etc.].",
    "description": "ud83dude84 Concurrency abstractions framework for `iOS` development [`Task`, `Atomic`, `Lock`, `Operation`, etc.].",
    "homepage": "https://github.com/jVirus/concurrency-kit",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "Astemir Eleev": "[email protected]"
    },
    "platforms": {
        "ios": "11.0"
    },
    "source": {
        "git": "https://github.com/jVirus/concurrency-kit.git",
        "tag": "1.1.1"
    },
    "source_files": "concurrency-kit/**/*.swift",
    "requires_arc": true,
    "pod_target_xcconfig": {
        "SWIFT_VERSION": "5.0"
    },
    "swift_version": "5.0"
}

Pin It on Pinterest

Share This