concurrency-kit
Last Update: 05/January/2020.
β It will show the creator your appreciation and help others to discover the repo.
If you like the project, please give it a star βοΈ About
iOS
development [Task
, Atomic
, Lock
, etc.].
π₯ Features
- Atomics - synchronization primitive that is implemented in several forms:
Generic
,Int
andBool
.Fast
. Under the hood a mutex (pthread_mutex_lock
) that is more efficient thanOSSpinLock
and faster thanNSLock
.Throwable
. You can safely throwErrors
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
- AnRW
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
, whereasyncAfter
andonce
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 toFutures & Promises
and execute them serially. - Tasks can be
sequenced
- meaning that you are able to compose differentgroups
and execute them concurrently. No need to repeatedly useDispatchGroup
(enter
/leave
).
- Tasks can be
- Stateful Operation - is a custom
Operation
class that supports modern,Swifty
state management through the usage ofAtomics
andEnum
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
}
}
π Installation
Swift Package Manager
Xcode 11+
- Open
MenuBar
βFile
βSwift Packages
βAdd Package Dependency...
- Paste the package repository url
https://github.com/jVirus/concurrency-kit
and hitNext
. - Select the installment rules.
After specifying which version do you want to install, the package will be downloaded and attached to your project.
Package.swift
If you already have a Package.swift
or you are building your own package simply add a new dependency:
dependencies: [
.package(url: "https://github.com/jVirus/concurrency-kit", from: "1.0.0")
]
Manual
You can always use copy-paste the sources method
π¨βπ» Author
π Licence
The project is available under MIT licence