├── .codecov.yml
├── .github
└── workflows
│ └── Build.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── astemireleev.xcuserdatad
│ │ └── IDEFindNavigatorScopes.plist
│ ├── xcshareddata
│ └── xcschemes
│ │ └── ConcurrencyKit.xcscheme
│ └── xcuserdata
│ └── astemireleev.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── ConcurrencyKit
│ ├── Array
│ └── Array+ConcurrentMap.swift
│ ├── Atomics
│ ├── Atomic.swift
│ ├── AtomicBool.swift
│ └── AtomicInt.swift
│ ├── Dispatch
│ ├── DispatchQueue+AsyncAfter.swift
│ └── DispatchQueue+Once.swift
│ ├── Locks
│ ├── LockType.swift
│ ├── Mutex.swift
│ ├── ReadWriteLock.swift
│ └── UnfairLock.swift
│ ├── Operations
│ └── StatefullOperation.swift
│ └── Task
│ └── Task.swift
├── Tests
├── ConcurrencyKitTests
│ ├── Array+ConcurrentMapTests.swift
│ ├── AtomicBoolTests.swift
│ ├── AtomicIntTests.swift
│ ├── AtomicTests.swift
│ ├── DispatchQueue+AsyncAfterTests.swift
│ ├── DispatchQueue+OnceTests.swift
│ ├── ReadWriteLockTests.swift
│ ├── StatefullOperationTests.swift
│ ├── TaskTests.swift
│ ├── UnfairLockTests.swift
│ └── XCTestManifests.swift
└── LinuxMain.swift
└── logo-concurrency_kit.png
/.codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | strict_yaml_branch: master
3 |
4 | ignore:
5 | - "concurrency-kitTests/.*"
6 |
--------------------------------------------------------------------------------
/.github/workflows/Build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on: [push]
3 | jobs:
4 | build:
5 | runs-on: macOS-latest
6 | steps:
7 | - uses: actions/checkout@v1
8 |
9 | - name: Switch to Xcode 11
10 | run: sudo xcode-select --switch /Applications/Xcode_11.3.app
11 | # Since we want to be running our tests from Xcode, we need to
12 | # generate an .xcodeproj file. Luckly, Swift Package Manager has
13 | # build in functionality to do so.
14 | - name: Generate xcodeproj
15 | run: swift package generate-xcodeproj
16 | - name: Run tests
17 | run: xcodebuild test -destination 'name=iPhone 11' -scheme 'ConcurrencyKit-Package'
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcuserdata/astemireleev.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/ConcurrencyKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
68 |
69 |
75 |
76 |
82 |
83 |
84 |
85 |
87 |
88 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcuserdata/astemireleev.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ConcurrencyKit.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | ConcurrencyKit
16 |
17 | primary
18 |
19 |
20 | ConcurrencyKitTests
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Astemir Eleev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ConcurrencyKit",
8 | platforms: [
9 | .iOS(.v12),
10 | .macOS(.v10_13),
11 | .watchOS(.v5)
12 | ],
13 | products: [
14 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
15 | .library(
16 | name: "ConcurrencyKit",
17 | targets: ["ConcurrencyKit"]),
18 | ],
19 | dependencies: [
20 | // Dependencies declare other packages that this package depends on.
21 | // .package(url: /* package url */, from: "1.0.0"),
22 | ],
23 | targets: [
24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
25 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
26 | .target(
27 | name: "ConcurrencyKit",
28 | dependencies: []),
29 | .testTarget(
30 | name: "ConcurrencyKitTests",
31 | dependencies: ["ConcurrencyKit"]),
32 | ],
33 | swiftLanguageVersions: [
34 | .v5
35 | ]
36 | )
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # concurrency-kit [](https://github.com/sindresorhus/awesome)
2 |
3 | []()
4 | [](https://codecov.io/gh/jVirus/concurrency-kit)
5 | []()
6 | []()
7 | []()
8 | []()
9 |
10 | **Last Update: 05/January/2020.**
11 |
12 | 
13 |
14 | ### If you like the project, please give it a star ⭐ It will show the creator your appreciation and help others to discover the repo.
15 |
16 | # ✍️ About
17 | 🚄 Concurrency abstractions framework for `iOS` development [`Task`, `Atomic`, `Lock`, etc.].
18 |
19 | # 🔥 Features
20 | - **Atomics** - synchronization primitive that is implemented in several forms: `Generic`, `Int` and `Bool`.
21 | - `Fast`. Under the hood a mutex (`pthread_mutex_lock`) that is more efficient than `OSSpinLock` and faster than `NSLock`.
22 | - `Throwable`. You can safely throw `Errors` and be able to delegate the handling.
23 | - **Locks** - contains a number of locks, such as:
24 | - `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.
25 | - `ReadWriteLock` - An `RW` lock allows concurrent access for read-only operations, while write operations require exclusive access.
26 | - `Mutex` - Allows only one thread to be active in a given region of code.
27 | - **DispatchQueue+Extensions** - extended `DispatchQueue`, where `asyncAfter` and `once` methods add convenience.
28 | - **Task** - A unit of work that performs a specific job and usually runs concurrently with other tasks.
29 | - Tasks can be `grouped` - meaning that you are able to compose the tasks, similar to `Futures & Promises` and execute them serially.
30 | - 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`).
31 | - **Stateful Operation** - is a custom `Operation` class that supports modern, `Swifty` state management through the usage of `Atomics` and `Enum` types.
32 | - **Thoroughly** tested.
33 |
34 | # 📚 Examples
35 |
36 | ## Task
37 | In order to create a `Task`, you need to simply use the `Task` struct and the `trailing closure` syntax:
38 |
39 | ```swift
40 | let uploadingTask = Task { controller in
41 | uploader(photos) { result in
42 | switch result {
43 | case .success:
44 | controller.finish()
45 | case .failure(let error):
46 | controller.fail(with error)
47 | }
48 | }
49 | }
50 |
51 | uploadingTask.perform { outcome in
52 | handle(outcome)
53 | }
54 | ```
55 |
56 | 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:
57 |
58 | ```swift
59 | let filesToUpload = [file, photo, video, xml]
60 |
61 | let group = Task.group(fileToUpload)
62 | group.perform { outcome in
63 | handle(outcome)
64 | }
65 | ```
66 |
67 | 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`:
68 |
69 | ```swift
70 | let filesToUpload = [file, photo, video, xml]
71 |
72 | let group = Task.sequence(filesToUpload)
73 | group.perform { outcome in
74 | handle(outcome)
75 | }
76 | ```
77 |
78 | ## Stateful Operation
79 | 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:
80 |
81 | ```swift
82 | class NetworkStatefulOperation: StatefullOperation {
83 |
84 | // MARK: - Properties
85 |
86 | private let callback: (StatefullOperation?) -> Void
87 | private let service: NetworkService
88 | private let dataHandler: Parsable
89 |
90 | // MARK: - Initializers
91 |
92 | init(_ service: NetworkService, _ dataHandler: Parser, callback: @escaping (StatefullOperation?) -> Void) {
93 | self.service = service
94 | self.dataHandler = dataHandler
95 | self.callback = callback
96 | }
97 |
98 | // MARK: - Overrides
99 |
100 | override func executableSection() {
101 | service.dataTask { [weak self] result in
102 | self?.dataHandler.parse(result)
103 | self?.finishIfNotCancelled()
104 | self?.callback(self)
105 | }
106 | }
107 | }
108 | ```
109 |
110 | Then, the usage of the `NetworkStatefulOperation` class is quite straightforward:
111 |
112 | ```swift
113 | // 1. Create an instance of `NetworkStatefulOperation` class:
114 | let networkiOperation = NetworkStatefulOperation(service, parser) {
115 | // 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:
116 | updateUI(with: $0.state)
117 | }
118 | // 2. Then call the `start` method:
119 | networkOperation.start()
120 | ```
121 |
122 | ## Atomics
123 | Guarantees safe mutation of a property in multiple async dispatch queues. Simply wrap a property in `Atomic` type:
124 |
125 | ```swift
126 | let atomic = Atomic(0)
127 |
128 | DispatchQueue.global().async {
129 | atomic.modify { $0 + 1 }
130 | }
131 |
132 | DispatchQueue.global().async {
133 | atomic.modify { $0 + 1 }
134 | }
135 | ```
136 |
137 | 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`](https://forums.swift.org/t/compile-time-generic-specialization/5082))
138 |
139 | ## Locks
140 |
141 | ### Read Write Lock
142 | A syncronozation primitive that solves one of the readers–writers problems:
143 |
144 | ```swift
145 | let rwLock = ReadWriteLock()
146 |
147 | rwLock.writeLock()
148 | sharedValue += 1
149 | rwLock.unlock()
150 | ```
151 |
152 | 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:
153 |
154 | ```swift
155 | let rwLock = ReadWriteLock()
156 |
157 | rwLock.readLock()
158 | sharedValue += 1
159 | rwLock.unlock()
160 | ```
161 |
162 | ### Unfair Lock
163 | 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:
164 |
165 | ```swift
166 | let unfairLock = UnfairLock()
167 |
168 | unfairLock.lock()
169 | sharedValue += 1
170 | unfairLock.unlock()
171 | ```
172 |
173 | ### Mutex
174 | 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:
175 |
176 | ```swift
177 | let mutex = Mutex()
178 |
179 | mutex.withCriticalScope {
180 | return sharedValue += 1
181 | }
182 | ```
183 |
184 | ## Dispatch Queue
185 | There is a convenience method that removes the need to pass `.now() + time` in order to make an async call:
186 |
187 | ```swift
188 | DispatchQueue.main.asyncAfter(seconds: 2.5) {
189 | expectation.fulfill()
190 | }
191 | ```
192 |
193 | Also, `DispatchQueue.once` was returned back::
194 |
195 | ```swift
196 | // The following concurrentQueue is called multiple times, though the caughtValue will be set to value only once.
197 | concurrentQueue.async {
198 | DispatchQueue.once(token: "caught") {
199 | caughtValue = value
200 | }
201 | }
202 | ```
203 |
204 | # 🏗 Installation
205 |
206 | ## Swift Package Manager
207 |
208 | ### Xcode 11+
209 |
210 | 1. Open `MenuBar` → `File` → `Swift Packages` → `Add Package Dependency...`
211 | 2. Paste the package repository url `https://github.com/jVirus/concurrency-kit` and hit `Next`.
212 | 3. Select the installment rules.
213 |
214 | After specifying which version do you want to install, the package will be downloaded and attached to your project.
215 |
216 | ### Package.swift
217 | If you already have a `Package.swift` or you are building your own package simply add a new dependency:
218 |
219 | ```swift
220 | dependencies: [
221 | .package(url: "https://github.com/jVirus/concurrency-kit", from: "1.0.0")
222 | ]
223 | ```
224 |
225 | ## Manual
226 | You can always use copy-paste the sources method 😄. Or you can compile the framework and include it with your project.
227 |
228 |
229 |
230 | # 👨💻 Author
231 | [Astemir Eleev](https://github.com/jVirus)
232 |
233 | # 🔖 Licence
234 | The project is available under [MIT licence](https://github.com/jVirus/concurrency-kit/blob/master/LICENSE)
235 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Array/Array+ConcurrentMap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+ConcurrentMap.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 02/06/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 |
13 | /// Concurrently performs map function for the given collection of elements
14 | public func concurrentMap(_ transform: @escaping (Element) -> E) -> [E?] {
15 | var result = Array(repeating: nil, count: count)
16 | let queue = DispatchQueue(label: "array.concurrent.map")
17 |
18 | DispatchQueue.concurrentPerform(iterations: count) { index in
19 | let element = self[index]
20 | let transformed = transform(element)
21 | queue.sync {
22 | result[index] = transformed
23 | }
24 | }
25 | return result
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Atomics/Atomic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Atomic.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Guarantees that a valid value will be returned when accessing such property by using multiple thread. The valid does not always mean correct (atomic property may not be in the state that you expect, at a time when you access it).
12 | final public class Atomic {
13 |
14 | // MARK: - Properties
15 |
16 | internal private(set) var mutex: Mutex
17 | private(set) var value: T
18 |
19 | // MARK: - Initialzers
20 |
21 | public init(_ value: T) {
22 | mutex = Mutex()
23 | self.value = value
24 | }
25 |
26 | // MARK: - Methods
27 |
28 | public func set(_ value: T) {
29 | mutex.withCriticalScope { [weak self] in
30 | self?.value = value
31 | }
32 | }
33 |
34 | public func get() -> T {
35 | return mutex.withCriticalScope { value }
36 | }
37 |
38 | public func swap(_ value: T) -> T {
39 | return modify { _ in value }
40 | }
41 |
42 | @discardableResult
43 | public func with(_ closure: (T) throws -> R) rethrows -> R {
44 | return try mutex.withCriticalScope {
45 | try closure(value)
46 | }
47 | }
48 |
49 | @discardableResult
50 | public func modify(_ action: (T) throws -> T) rethrows -> T {
51 | return try mutex.withCriticalScope {
52 | let previous = value
53 | value = try action(value)
54 | return previous
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Atomics/AtomicBool.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AtomicBool.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final public class AtomicBool {
12 |
13 | // MARK: - Properties
14 |
15 | internal private(set) var mutex: Mutex
16 | private(set) var value: Bool
17 |
18 | // MARK: - Initialzers
19 |
20 | public init(_ value: Bool) {
21 | mutex = Mutex()
22 | self.value = value
23 | }
24 |
25 | // MARK: - Methods
26 |
27 | public func set(_ value: Bool) {
28 | mutex.withCriticalScope { [weak self] in
29 | self?.value = value
30 | }
31 | }
32 |
33 | public func get() -> Bool {
34 | return mutex.withCriticalScope { value }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Atomics/AtomicInt.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AtomicInt.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final public class AtomicInt {
12 |
13 | // MARK: - Properties
14 |
15 | internal private(set) var mutex: Mutex
16 | private(set) var value: Int
17 |
18 | // MARK: - Initialzers
19 |
20 | public init(_ value: Int) {
21 | mutex = Mutex()
22 | self.value = value
23 | }
24 |
25 | // MARK: - Methods
26 |
27 | public func set(_ value: Int) {
28 | mutex.withCriticalScope { [weak self] in
29 | self?.value = value
30 | }
31 | }
32 |
33 | public func get() -> Int {
34 | return mutex.withCriticalScope { value }
35 | }
36 |
37 | public func swap(_ value: Int) -> Int {
38 | return modify { _ in value }
39 | }
40 |
41 | @discardableResult
42 | public func with(_ closure: (Int) throws -> Int) rethrows -> Int {
43 | return try mutex.withCriticalScope {
44 | try closure(value)
45 | }
46 | }
47 |
48 | @discardableResult
49 | public func modify(_ action: (Int) throws -> Int) rethrows -> Int {
50 | return try mutex.withCriticalScope {
51 | let previous = value
52 | value = try action(value)
53 | return previous
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Dispatch/DispatchQueue+AsyncAfter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+AsyncAfter.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension DispatchQueue {
12 |
13 | func asyncAfter(seconds: Double, execute closure: @escaping () -> ()) {
14 | let secPerSec = Double(NSEC_PER_SEC)
15 | let deadline: DispatchTime = DispatchTime.now() + Double(Int64(seconds * secPerSec)) / secPerSec
16 | self.asyncAfter(deadline: deadline, execute: closure)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Dispatch/DispatchQueue+Once.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+Once.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension DispatchQueue {
12 |
13 | // MARK: - Properties
14 |
15 | private static var _onceTracker = [String]()
16 |
17 | // MARK: - Methods
18 |
19 | /// Executes a block of code, associated with a unique token, only once. The code is thread safe and will onle execute the code once even in the presence of multithreaded calls.
20 | ///
21 | /// - Parameters:
22 | /// - token: is a unique reverse DNS-style name such as io.eleev.astemir or a GUID
23 | /// - block: is a non-escaping closure that is executed only once
24 | class func once(token: String, block: () -> Void) {
25 | objc_sync_enter(self)
26 | defer { objc_sync_exit(self) }
27 |
28 | if _onceTracker.contains(token) { return }
29 | _onceTracker.append(token)
30 | block()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Locks/LockType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LockType.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol LockType {
12 | func lock()
13 | func unlock()
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Locks/Mutex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mutex.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// 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.
12 | final public class Mutex: LockType {
13 |
14 | // MARK: - Properties
15 |
16 | private var mutex = pthread_mutex_t()
17 |
18 | // MARK: - Init & Deinit
19 |
20 | init() {
21 | let result = pthread_mutex_init(&mutex, nil)
22 | assert(result == 0, "Failed to init mutex in \(self)")
23 | }
24 |
25 | deinit {
26 | destroy()
27 | }
28 |
29 | // MARK: - Methods
30 |
31 | @discardableResult
32 | public func tryLock() -> Int32 {
33 | return pthread_mutex_trylock(&mutex)
34 | }
35 |
36 | // MARK: - Conformance to LockType protocol
37 |
38 | public func lock() {
39 | pthread_mutex_lock(&mutex)
40 | }
41 |
42 | public func unlock() {
43 | pthread_mutex_unlock(&mutex)
44 | }
45 |
46 | // MARK: - Private methods
47 |
48 | private func destroy() {
49 | pthread_mutex_destroy(&mutex)
50 | }
51 | }
52 |
53 | public extension Mutex {
54 | func withCriticalScope(body: () throws -> R) rethrows -> R {
55 | lock()
56 | defer { unlock() }
57 | return try body()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Locks/ReadWriteLock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReadWriteLock.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A synchronization primitive that solves one of the readers–writers problems. An RW lock allows concurrent access for read-only operations, while write operations require exclusive access. This means that multiple threads can read the data in parallel but an exclusive lock is needed for writing or modifying data.
12 | final public class ReadWriteLock {
13 |
14 | // MARK: - Properties
15 |
16 | private var rwLock = pthread_rwlock_t()
17 |
18 | // MARK: - Init & Deinit
19 |
20 | public init() {
21 | let result = pthread_rwlock_init(&rwLock, nil)
22 | assert(result == 0, "Failed to init read write lock in \(self)")
23 | }
24 |
25 | deinit {
26 | destroy()
27 | }
28 |
29 | // MARK: - Methods
30 |
31 | public func readLock() {
32 | pthread_rwlock_rdlock(&rwLock)
33 | }
34 |
35 | @discardableResult
36 | public func tryReadLock() -> Int32 {
37 | return pthread_rwlock_tryrdlock(&rwLock)
38 | }
39 |
40 | public func writeLock() {
41 | pthread_rwlock_wrlock(&rwLock)
42 | }
43 |
44 | @discardableResult
45 | public func tryWriteLock() -> Int32 {
46 | return pthread_rwlock_trywrlock(&rwLock)
47 | }
48 |
49 | public func unlock() {
50 | pthread_rwlock_unlock(&rwLock)
51 | }
52 |
53 | // MARK: - Private methods
54 |
55 | private func destroy() {
56 | pthread_rwlock_destroy(&rwLock)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Locks/UnfairLock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnfairLock.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// 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
12 | final public class UnfairLock: LockType {
13 |
14 | // MARK: - Properties
15 |
16 | private var unfairLock = os_unfair_lock()
17 |
18 | // MARK: - Conformance to LockType protocol
19 |
20 | public func lock() {
21 | os_unfair_lock_lock(&unfairLock)
22 | }
23 |
24 | @discardableResult
25 | public func tryLock() -> Bool {
26 | return os_unfair_lock_trylock(&unfairLock)
27 | }
28 |
29 | public func unlock() {
30 | os_unfair_lock_unlock(&unfairLock)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Operations/StatefullOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatefulOperation.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 14/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Operation that has more 'Swifty' state management system, where state is an enum type with a number of possible cases:
12 | /// - `ready` - an operation is initialized and is ready to be executed
13 | /// - `executing` - an operation is performing a work
14 | /// - `cancelled` - an operation has not yet successfully finished the work, but was interrupted
15 | /// - `finished` - an operation has successfully finished the work
16 | ///
17 | /// In order to use the class, you need to subclass it and override the `executableSection` method, where the work need to be done. When the work is done, you need to call the `finish` or `finishIfNotCancelled` methods in order to properly change the internal state of an operation.
18 | open class StatefulOperation: Operation {
19 |
20 | // MARK: - Enum types
21 |
22 | public enum State: Equatable {
23 |
24 | // MARK: - Cases
25 |
26 | case ready
27 | case executing
28 | case cancelled
29 | case finished
30 |
31 | // MARK: - Fileprivate properties
32 |
33 | fileprivate var key: String {
34 | switch self {
35 | case .ready:
36 | return "isReady"
37 | case .executing:
38 | return "isExecuting"
39 | case .cancelled:
40 | return "isCancelled"
41 | case .finished:
42 | return "isFinished"
43 | }
44 | }
45 | }
46 |
47 | // MARK: - Private properties
48 |
49 | private let queue = DispatchQueue(label: "io.eleev.astemir.concurrency-kit.statefull-operation")
50 | private var _state: Atomic = Atomic(.ready)
51 |
52 | // MARK: - Public properties
53 |
54 | public private(set) var state: State {
55 | get {
56 | return _state.get()
57 | }
58 | set {
59 | queue.sync {
60 | let oldValue = _state.get()
61 | guard newValue != oldValue else { return }
62 |
63 | willChangeValue(forKey: oldValue.key)
64 | willChangeValue(forKey: newValue.key)
65 |
66 | _state.set(newValue)
67 |
68 | didChangeValue(forKey: oldValue.key)
69 | didChangeValue(forKey: newValue.key)
70 | }
71 | }
72 | }
73 |
74 | // MARK: - Overriden properties
75 |
76 | final public override var isAsynchronous: Bool {
77 | return true
78 | }
79 |
80 | final public override var isExecuting: Bool {
81 | return state == .executing
82 | }
83 |
84 | final public override var isFinished: Bool {
85 | return state == .finished
86 | }
87 |
88 | final public override var isCancelled: Bool {
89 | return state == .cancelled
90 | }
91 |
92 | // MARK: - Overriden methods
93 |
94 | final public override func start() {
95 | if isCancelled { interrupt(); return }
96 | if isFinished { finish(); return }
97 | main()
98 | }
99 |
100 | final public override func main() {
101 | if isCancelled { interrupt(); return }
102 | if isFinished { finish(); return }
103 | state = .executing
104 | executableSection()
105 | }
106 |
107 | // MARK: - Methods
108 |
109 | final public func interrupt() {
110 | state = .cancelled
111 | cancel()
112 | }
113 |
114 | final public func finish() {
115 | state = .finished
116 | }
117 |
118 | final public func finishIfNotCancelled() {
119 | if state != .cancelled { finish() }
120 | }
121 |
122 | /// Overridable point, that is required to call `finish` method in order to mark the executable section as finished.
123 | open func executableSection() {
124 | finish()
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Sources/ConcurrencyKit/Task/Task.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Task.swift
3 | // ConcurrencyKit
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Task {
12 |
13 | // MARK: - Typealiases
14 |
15 | public typealias Closure = (Controller) -> Void
16 |
17 | // MARK: - Properties
18 |
19 | private let closure: Closure
20 |
21 | // MARK: - Initializers
22 |
23 | public init(closure: @escaping Closure) {
24 | self.closure = closure
25 | }
26 |
27 | // MARK: - Methods
28 |
29 | public func perform(on queue: DispatchQueue = .global(),
30 | then handler: @escaping (Outcome) -> Void) {
31 | queue.async {
32 | let controller = Controller(
33 | queue: queue,
34 | handler: handler
35 | )
36 |
37 | self.closure(controller)
38 | }
39 | }
40 | }
41 |
42 |
43 | // MARK: - Constroller extension
44 | public extension Task {
45 |
46 | // MARK: - Controller struct
47 |
48 | struct Controller {
49 |
50 | // MARK: - Priperties
51 |
52 | fileprivate let queue: DispatchQueue
53 | fileprivate let handler: (Outcome) -> Void
54 |
55 | // MARK: - Methods
56 |
57 | public func finish() {
58 | handler(.success)
59 | }
60 |
61 | public func fail(with error: Error) {
62 | handler(.failure(error))
63 | }
64 | }
65 | }
66 |
67 | // MARK: - Outcome extension
68 | public extension Task {
69 |
70 | // MARK: - Enum type
71 |
72 | enum Outcome: Equatable {
73 |
74 | // MARK: - Cases
75 |
76 | case success
77 | case failure(Swift.Error)
78 |
79 | // MARK: - Conformance to Equatable protocol
80 |
81 | public static func == (lhs: Task.Outcome, rhs: Task.Outcome) -> Bool {
82 | switch (lhs, rhs) {
83 | case (.success, .success):
84 | return true
85 | case (.failure( _), .failure( _)):
86 | return true
87 | default:
88 | return false
89 | }
90 | }
91 | }
92 | }
93 |
94 | // MARK: - Serial task executor
95 | public extension Task {
96 |
97 | static func group(_ tasks: [Task]) -> Task {
98 | return Task { controller in
99 | let group = DispatchGroup()
100 | let errorSyncQueue = DispatchQueue(label: "Task.Error.Sync")
101 | var anyError: Error?
102 |
103 | for task in tasks {
104 | group.enter()
105 | task.perform(on: controller.queue) { outcome in
106 | switch outcome {
107 | case .success:
108 | break
109 | case .failure(let error):
110 | errorSyncQueue.sync {
111 | anyError = anyError ?? error
112 | }
113 | }
114 |
115 | group.leave()
116 | }
117 | }
118 |
119 | group.notify(queue: controller.queue) {
120 | if let error = anyError {
121 | controller.fail(with: error)
122 | } else {
123 | controller.finish()
124 | }
125 | }
126 | }
127 | }
128 | }
129 |
130 | // MARK: - Concurrent task executor
131 | public extension Task {
132 |
133 | static func sequence(_ tasks: [Task]) -> Task {
134 | var index = 0
135 |
136 | func performNext(using controller: Controller) {
137 | guard index < tasks.count else {
138 | controller.finish()
139 | return
140 | }
141 |
142 | let task = tasks[index]
143 | index += 1
144 |
145 | task.perform(on: controller.queue) { outcome in
146 | switch outcome {
147 | case .success:
148 | performNext(using: controller)
149 | case .failure(let error):
150 | // As soon as an error was occurred, we’ll
151 | // fail the entire sequence.
152 | controller.fail(with: error)
153 | }
154 | }
155 | }
156 |
157 | return Task(closure: performNext)
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/Array+ConcurrentMapTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+ConcurrentMapTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 02/06/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | final class ArrayConcurrentMapTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("test", test)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func test() {
27 | let elements = Array(repeating: 5, count: 100000)
28 |
29 | measure {
30 | let _ = elements.concurrentMap { element -> Int in
31 | return element * element
32 | }
33 | }
34 |
35 | // measure {
36 | // var output = [elements.count]
37 | //
38 | // for (index, element) in elements.enumerated() {
39 | // let result = element * index
40 | // output.append(result)
41 | // }
42 | // }
43 | }
44 |
45 | func testPerformanceExample() {
46 | // This is an example of a performance test case.
47 | self.measure {
48 | // Put the code you want to measure the time of here.
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/AtomicBoolTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AtomicBoolTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class AtomicBoolTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testSet", testSet),
16 | ("testGet", testGet)
17 | ]
18 |
19 | override func setUp() {
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | }
26 |
27 | func testSet() {
28 | let atomic = AtomicBool(true)
29 | atomic.set(false)
30 | XCTAssertEqual(atomic.value, false)
31 | }
32 |
33 | func testGet() {
34 | let atomic = AtomicBool(false)
35 | atomic.set(true)
36 | let value = atomic.get()
37 | XCTAssertEqual(value, true)
38 | }
39 |
40 | func testPerformanceExample() {
41 | // This is an example of a performance test case.
42 | self.measure {
43 | // Put the code you want to measure the time of here.
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/AtomicIntTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AtomicIntTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class AtomicIntTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testModify", testModify),
16 | ("testWith", testWith),
17 | ("testSet", testSet),
18 | ("testGet", testGet),
19 | ("testSwap", testSwap),
20 | ("testRethrowFromWithValue", testRethrowFromWithValue),
21 | ("testRethrowFromModify", testRethrowFromModify),
22 | ("testHighlyContestedLocking", testHighlyContestedLocking)
23 | ]
24 |
25 | override func setUp() {
26 | // Put setup code here. This method is called before the invocation of each test method in the class.
27 | }
28 |
29 | override func tearDown() {
30 | // Put teardown code here. This method is called after the invocation of each test method in the class.
31 | }
32 |
33 | func testModify() {
34 | let atomic = AtomicInt(0)
35 | atomic.modify { $0 + 10 }
36 | XCTAssertEqual(atomic.value, 10)
37 | }
38 |
39 | func testWith() {
40 | let atomic = AtomicInt(0)
41 | let result = atomic.with { $0 + 10 }
42 | XCTAssertEqual(atomic.value, 0)
43 | XCTAssertEqual(result, 10)
44 | }
45 |
46 | func testSet() {
47 | let atomic = AtomicInt(0)
48 | atomic.set(10)
49 | XCTAssertEqual(atomic.value, 10)
50 | }
51 |
52 | func testGet() {
53 | let atomic = AtomicInt(0)
54 | atomic.set(30)
55 | XCTAssertEqual(atomic.value, 30)
56 | }
57 |
58 | func testSwap() {
59 | let atomic = AtomicInt(1)
60 | let oldValue = atomic.swap(10)
61 | XCTAssertEqual(oldValue, 1)
62 | XCTAssertEqual(atomic.value, 10)
63 | }
64 |
65 | func testRethrowFromWithValue() {
66 | let atomic = AtomicInt(5)
67 | var didCatch = false
68 |
69 | do {
70 | try atomic.with { value in
71 | throw NSError(domain: NSCocoaErrorDomain, code: value, userInfo: nil)
72 | }
73 | XCTFail()
74 | } catch let error as NSError {
75 | didCatch = true
76 | XCTAssertEqual(error, NSError(domain: NSCocoaErrorDomain, code: 5, userInfo: nil))
77 | }
78 |
79 | if atomic.mutex.tryLock() == 0 {
80 | atomic.mutex.unlock()
81 | } else {
82 | XCTFail()
83 | }
84 | XCTAssert(didCatch)
85 | }
86 |
87 | func testRethrowFromModify() {
88 | let atomic = AtomicInt(5)
89 | var didCatch = false
90 |
91 | do {
92 | try atomic.modify { value in
93 | throw NSError(domain: NSCocoaErrorDomain, code: value, userInfo: nil)
94 | }
95 | XCTFail()
96 | } catch let error as NSError {
97 | didCatch = true
98 | XCTAssertEqual(error, NSError(domain: NSCocoaErrorDomain, code: 5, userInfo: nil))
99 | }
100 |
101 | if atomic.mutex.tryLock() == 0 {
102 | atomic.mutex.unlock()
103 | } else {
104 | XCTFail()
105 | }
106 | XCTAssert(didCatch)
107 | }
108 |
109 | func testHighlyContestedLocking() {
110 | let contestedAtomic = AtomicInt(0)
111 |
112 | let concurrentQueue = DispatchQueue.global()
113 | let dispatchGroup = DispatchGroup()
114 | let count = 10_000
115 |
116 | for _ in 0.. 2.45 && timeInterval < 2.6)
37 |
38 | expectation.fulfill()
39 | }
40 |
41 | wait(for: [expectation], timeout: 4.0)
42 | }
43 |
44 | func testPerformanceExample() {
45 | // This is an example of a performance test case.
46 | self.measure {
47 | // Put the code you want to measure the time of here.
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/DispatchQueue+OnceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+OnceTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class DispatchQueue_OnceTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("test", test)
16 | ]
17 |
18 | override func setUp() {
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func test() {
27 | var occurences = 0
28 | let dispatchQueue = DispatchQueue.global()
29 | let dispatchGroup = DispatchGroup()
30 |
31 | for _ in 0..<100 {
32 | dispatchGroup.enter()
33 | dispatchQueue.async {
34 | DispatchQueue.once(token: "Unique Id") {
35 | occurences += 1
36 | }
37 | dispatchGroup.leave()
38 | }
39 | }
40 | let extation = expectation(description: "Dispatch Group Completion")
41 | dispatchGroup.notify(queue: dispatchQueue) {
42 | extation.fulfill()
43 | }
44 | waitForExpectations(timeout: 5, handler: nil)
45 | XCTAssertEqual(occurences, 1)
46 | }
47 |
48 | func testPerformanceExample() {
49 | // This is an example of a performance test case.
50 | self.measure {
51 | // Put the code you want to measure the time of here.
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/ReadWriteLockTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReadWriteLockTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class ReadWriteLockTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testReadLock", testReadLock),
16 | ("testWriteLock", testWriteLock)
17 | ]
18 |
19 | override func setUp() {
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | }
26 |
27 | func testReadLock() {
28 | var value = 0
29 | let lock = ReadWriteLock()
30 |
31 | let concurrentQueue = DispatchQueue.global()
32 | let anotherConcurrentQueue = DispatchQueue.global()
33 | let dispatchGroup = DispatchGroup()
34 | let anotherDispatchGroup = DispatchGroup()
35 | let count = 10_000
36 |
37 | lock.readLock()
38 | for _ in 0.. Void
31 |
32 | init(callback: @escaping (StatefulOperation) -> Void) {
33 | self.callback = callback
34 | }
35 |
36 | override func executableSection() {
37 | DispatchQueue.global().async {
38 | sleep(3)
39 | self.finish()
40 | self.callback(self)
41 | }
42 | }
43 | }
44 |
45 |
46 | let expectation = XCTestExpectation(description: "Operation Expectation")
47 |
48 | let statefullOperation = MockStatefullOperation {
49 | XCTAssertEqual($0.state, .finished)
50 | expectation.fulfill()
51 | }
52 |
53 | XCTAssertEqual(statefullOperation.state, .ready)
54 | statefullOperation.start()
55 |
56 | XCTAssertEqual(statefullOperation.state, .executing)
57 |
58 | wait(for: [expectation], timeout: 5.0)
59 | }
60 |
61 | func testInterruptedOperation() {
62 | class MockStatefullOperation: StatefulOperation {
63 |
64 | private var callback: (StatefulOperation) -> Void
65 |
66 | init(callback: @escaping (StatefulOperation) -> Void) {
67 | self.callback = callback
68 | }
69 |
70 | override func executableSection() {
71 | DispatchQueue.global().async {
72 | sleep(3)
73 |
74 | self.finishIfNotCancelled()
75 | self.callback(self)
76 | }
77 | }
78 | }
79 |
80 |
81 | let expectation = XCTestExpectation(description: "Operation Expectation")
82 |
83 | let statefullOperation = MockStatefullOperation {
84 | XCTAssertEqual($0.state, .cancelled)
85 | expectation.fulfill()
86 | }
87 |
88 | XCTAssertEqual(statefullOperation.state, .ready)
89 | statefullOperation.start()
90 | XCTAssertEqual(statefullOperation.state, .executing)
91 |
92 | // Then after 0.75 seconds the operation by some reason is interrupted and it was decided to cancel it
93 | DispatchQueue.main.asyncAfter(seconds: 0.75) {
94 | statefullOperation.interrupt()
95 |
96 | XCTAssertEqual(statefullOperation.state, .cancelled)
97 | XCTAssertEqual(statefullOperation.isCancelled, true)
98 | }
99 |
100 | wait(for: [expectation], timeout: 5.0)
101 | }
102 |
103 | func testPerformanceExample() {
104 | // This is an example of a performance test case.
105 | self.measure {
106 | // Put the code you want to measure the time of here.
107 | }
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/TaskTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TaskTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class TaskTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testPerform", testPerform),
16 | ("testGroup", testGroup),
17 | ("testSequence", testSequence)
18 | ]
19 |
20 | override func setUp() {
21 | // Put setup code here. This method is called before the invocation of each test method in the class.
22 | }
23 |
24 | override func tearDown() {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | }
27 |
28 | func testPerform() {
29 | let expectation = XCTestExpectation(description: "Task Expectation")
30 |
31 | let iterationTask = Task { controller in
32 | DispatchQueue.global().async {
33 | (0...10_000).forEach({
34 | let _ = $0 * $0
35 | })
36 | controller.finish()
37 | }
38 | }
39 |
40 | iterationTask.perform { outcome in
41 | XCTAssertEqual(outcome, Task.Outcome.success)
42 | expectation.fulfill()
43 | }
44 |
45 | wait(for: [expectation], timeout: 5.0)
46 | }
47 |
48 | func testGroup() {
49 | let expectation = XCTestExpectation(description: "Task Expectation")
50 |
51 | func longRunningTaskMock(completion: @escaping () -> Void) {
52 | DispatchQueue.global().async {
53 | (0...10_000).forEach({
54 | let _ = $0 * $0
55 | })
56 | completion()
57 | }
58 | }
59 |
60 | let longRunningOperations = [longRunningTaskMock, longRunningTaskMock, longRunningTaskMock, longRunningTaskMock, longRunningTaskMock]
61 |
62 | let anotherSetOfLongRunningOperations = longRunningOperations
63 |
64 | let longRunningTasks = longRunningOperations.map { operation in
65 | Task.init(closure: { controller in
66 | operation() {
67 | controller.finish()
68 | }
69 | })
70 | }
71 |
72 | let anotherSeOfLongRunningTasks = anotherSetOfLongRunningOperations.map { operation in
73 | Task.init(closure: { controller in
74 | operation() {
75 | controller.finish()
76 | }
77 | })
78 | }
79 |
80 | let group = Task.group(longRunningTasks + anotherSeOfLongRunningTasks)
81 | group.perform { outcome in
82 | XCTAssertEqual(outcome, Task.Outcome.success)
83 | expectation.fulfill()
84 | }
85 |
86 | wait(for: [expectation], timeout: 8.0)
87 | }
88 |
89 | func testSequence() {
90 | let expectation = XCTestExpectation(description: "Task Expectation")
91 |
92 | func longRunningTaskMock(completion: @escaping () -> Void) {
93 | DispatchQueue.global().async {
94 | (0...10_000).forEach({
95 | let _ = $0 * $0
96 | })
97 | completion()
98 | }
99 | }
100 |
101 | let longRunningOperations = [longRunningTaskMock, longRunningTaskMock, longRunningTaskMock, longRunningTaskMock, longRunningTaskMock]
102 |
103 | let anotherSetOfLongRunningOperations = longRunningOperations
104 |
105 | let longRunningTasks = longRunningOperations.map { operation in
106 | Task.init(closure: { controller in
107 | operation() {
108 | controller.finish()
109 | }
110 | })
111 | }
112 |
113 | let anotherSeOfLongRunningTasks = anotherSetOfLongRunningOperations.map { operation in
114 | Task.init(closure: { controller in
115 | operation() {
116 | controller.finish()
117 | }
118 | })
119 | }
120 |
121 | let group = Task.sequence(longRunningTasks + anotherSeOfLongRunningTasks)
122 | group.perform { outcome in
123 | XCTAssertEqual(outcome, Task.Outcome.success)
124 | expectation.fulfill()
125 | }
126 |
127 | wait(for: [expectation], timeout: 8.0)
128 | }
129 |
130 |
131 | func testPerformanceExample() {
132 | // This is an example of a performance test case.
133 | self.measure {
134 | // Put the code you want to measure the time of here.
135 | }
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/Tests/ConcurrencyKitTests/UnfairLockTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnfairLockTests.swift
3 | // ConcurrencyKitTests
4 | //
5 | // Created by Astemir Eleev on 10/03/2019.
6 | // Copyright © 2019 Astemir Eleev. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ConcurrencyKit
11 |
12 | class UnfairLockTests: XCTestCase {
13 |
14 | static var allTests = [
15 | ("testModify", testModify),
16 | ("testTryLock", testTryLock),
17 | ("testAsync", testAsync),
18 | ("testAsyncTwoQueues", testAsyncTwoQueues),
19 | ]
20 |
21 | override func setUp() {
22 | // Put setup code here. This method is called before the invocation of each test method in the class.
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | func testModify() {
30 | let lock = UnfairLock()
31 | var value = 0
32 |
33 | lock.lock()
34 | value = 10
35 | lock.unlock()
36 | XCTAssertEqual(value, 10)
37 | }
38 |
39 | func testTryLock() {
40 | let lock = UnfairLock()
41 | var value = 0
42 |
43 | lock.tryLock()
44 | value = 10
45 | lock.unlock()
46 | XCTAssertEqual(value, 10)
47 | }
48 |
49 | func testAsync() {
50 | var value = 0
51 | let lock = UnfairLock()
52 |
53 | let concurrentQueue = DispatchQueue.global()
54 | let dispatchGroup = DispatchGroup()
55 | let count = 10_000
56 |
57 | for _ in 0.. [XCTestCaseEntry] {
5 | return [
6 | testCase(ArrayConcurrentMapTests.allTests),
7 | testCase(AtomicBoolTests.allTests),
8 | testCase(AtomicIntTests.allTests),
9 | testCase(AtomicTests.allTests),
10 | testCase(DispatchQueue_AsyncAfterTests.allTests),
11 | testCase(DispatchQueue_OnceTests.allTests),
12 | testCase(ReadWriteLockTests.allTests),
13 | testCase(StatefullOperationTests.allTests),
14 | testCase(TaskTests.allTests),
15 | testCase(UnfairLockTests.allTests)
16 | ]
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import ConcurrencyKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += ArrayConcurrentMapTests.allTests()
7 | tests += AtomicBoolTests.allTests()
8 | tests += AtomicIntTests.allTests()
9 | tests += AtomicTests.allTests()
10 | tests += DispatchQueue_AsyncAfterTests.allTests()
11 | tests += DispatchQueue_OnceTests.allTests()
12 | tests += ReadWriteLockTests.allTests()
13 | tests += StatefullOperationTests.allTests()
14 | tests += TaskTests.allTests()
15 | tests += UnfairLockTests.allTests()
16 | XCTMain(tests)
17 |
--------------------------------------------------------------------------------
/logo-concurrency_kit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eleev/concurrency-kit/ed10d294e66b8d714767090fa70608eb31a0289d/logo-concurrency_kit.png
--------------------------------------------------------------------------------