Skip to content

Commit f36e0de

Browse files
committed
Add ThreadSafeMap and ThreadSafeList
1 parent c379de6 commit f36e0de

5 files changed

Lines changed: 204 additions & 19 deletions

File tree

Package.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,15 @@ import PackageDescription
55

66
let package = Package(
77
name: "ThreadSafeCollections",
8+
platforms: [
9+
.iOS(.v12)
10+
],
811
products: [
9-
// Products define the executables and libraries produced by a package, and make them visible to other packages.
1012
.library(
1113
name: "ThreadSafeCollections",
1214
targets: ["ThreadSafeCollections"]),
1315
],
14-
dependencies: [
15-
// Dependencies declare other packages that this package depends on.
16-
// .package(url: /* package url */, from: "1.0.0"),
17-
],
1816
targets: [
19-
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
20-
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
2117
.target(
2218
name: "ThreadSafeCollections",
2319
dependencies: []),

Sources/ThreadSafeCollections/ThreadSafeCollections.swift

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Foundation
2+
3+
public class ThreadSafeList<V> {
4+
5+
private var items = [V]()
6+
private var itemQueue: DispatchQueue
7+
public init(identifier: String = UUID().uuidString) {
8+
itemQueue = DispatchQueue(
9+
label: "ThreadSafeList.\(String(describing: V.self)).queue.\(identifier)",
10+
qos: .userInitiated,
11+
attributes: .concurrent,
12+
target: DispatchQueue.global(qos: .userInitiated)
13+
)
14+
}
15+
16+
public func get(_ index: Int) -> V? {
17+
var value: V?
18+
itemQueue.sync { // safely read
19+
if self.items.indices.contains(index) {
20+
value = self.items[index]
21+
} else {
22+
value = nil
23+
}
24+
}
25+
return value
26+
}
27+
28+
public func insert(_ value: V, at index: Int) {
29+
itemQueue.async(flags: .barrier) { // safely write
30+
self.items.insert(value, at: index)
31+
}
32+
}
33+
34+
@discardableResult public func remove(at index: Int) -> V? {
35+
guard let value = get(index) else {
36+
// make sure the index exists
37+
return nil
38+
}
39+
itemQueue.async(flags: .barrier) { // safely write
40+
self.items.remove(at: index)
41+
}
42+
return value
43+
}
44+
45+
public func append(_ value: V) {
46+
itemQueue.async(flags: .barrier) { // safely write
47+
self.items.append(value)
48+
}
49+
}
50+
51+
public func append(contentsOf values: [V]) {
52+
itemQueue.async(flags: .barrier) { // safely write
53+
self.items.append(contentsOf: values)
54+
}
55+
}
56+
57+
public func removeAll() {
58+
itemQueue.async(flags: .barrier) { // safely write
59+
self.items.removeAll()
60+
}
61+
}
62+
63+
public func getAll() -> [V] {
64+
var allItems = [V]()
65+
itemQueue.sync { // safely read
66+
allItems = self.items
67+
}
68+
return allItems
69+
}
70+
71+
public func count() -> Int {
72+
var count = 0
73+
itemQueue.sync { // safely read
74+
count = self.items.count
75+
}
76+
return count
77+
}
78+
79+
public subscript(index: Int) -> V? {
80+
get {
81+
return self.get(index)
82+
}
83+
set(newValue) {
84+
if let value = newValue {
85+
self.insert(value, at: index)
86+
} else {
87+
self.remove(at: index)
88+
}
89+
}
90+
}
91+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Foundation
2+
3+
public class ThreadSafeMap<K: Hashable, V> {
4+
5+
private var items = [K: V]()
6+
private var itemQueue: DispatchQueue
7+
8+
public init(identifier: String = UUID().uuidString) {
9+
itemQueue = DispatchQueue(
10+
label: "ThreadSafeMap.\(String(describing: K.self)).\(String(describing: V.self)).queue.\(identifier)",
11+
qos: .userInitiated,
12+
attributes: .concurrent,
13+
target: DispatchQueue.global(qos: .userInitiated)
14+
)
15+
}
16+
17+
public func get(_ key: K) -> V? {
18+
var value: V?
19+
itemQueue.sync { // safely read
20+
value = self.items[key]
21+
}
22+
return value
23+
}
24+
25+
public func put(_ key: K, _ value: V?) {
26+
itemQueue.async(flags: .barrier) { // safely write
27+
self.items[key] = value
28+
}
29+
}
30+
31+
public func removeAll() {
32+
itemQueue.async(flags: .barrier) { // safely write
33+
self.items.removeAll()
34+
}
35+
}
36+
37+
public func removeValue(forKey key: K) {
38+
itemQueue.async(flags: .barrier) { // safely write
39+
self.items.removeValue(forKey: key)
40+
}
41+
}
42+
43+
public func getAll() -> [K: V] {
44+
var allItems = [K: V]()
45+
itemQueue.sync { // safely read
46+
allItems = self.items
47+
}
48+
return allItems
49+
}
50+
51+
public func putAll(_ allItems: [K: V]) {
52+
itemQueue.async(flags: .barrier) { // safely write
53+
self.items = allItems
54+
}
55+
}
56+
57+
public subscript(key: K) -> V? {
58+
get {
59+
return self.get(key)
60+
}
61+
set(newValue) {
62+
self.put(key, newValue)
63+
}
64+
}
65+
66+
public func count() -> Int {
67+
var count = 0
68+
itemQueue.sync { // safely read
69+
count = self.items.count
70+
}
71+
return count
72+
}
73+
}

Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,42 @@ import XCTest
22
@testable import ThreadSafeCollections
33

44
final class ThreadSafeCollectionsTests: XCTestCase {
5-
func testExample() {
6-
// This is an example of a functional test case.
7-
// Use XCTAssert and related functions to verify your tests produce the correct
8-
// results.
9-
XCTAssertEqual(ThreadSafeCollections().text, "Hello, World!")
5+
6+
func testMap() {
7+
let m = ThreadSafeMap<Int, Int>()
8+
(0...99).forEach { i in
9+
m.put(i, i)
10+
XCTAssertEqual(i, m.get(i))
11+
m.removeValue(forKey: i)
12+
XCTAssertNil(m.get(i))
13+
m[i] = i
14+
XCTAssertEqual(i, m[i])
1015
}
11-
12-
static var allTests = [
13-
("testExample", testExample),
14-
]
16+
XCTAssertEqual(100, m.count())
17+
let items = m.getAll()
18+
m.removeAll()
19+
XCTAssertEqual(0, m.count())
20+
m.putAll(items)
21+
XCTAssertEqual(100, m.count())
22+
}
23+
24+
func testList() {
25+
let m = ThreadSafeList<Int>()
26+
(0...99).forEach { i in
27+
m.append(i)
28+
XCTAssertEqual(i, m.get(i))
29+
let removed = m.remove(at: i)
30+
XCTAssertEqual(removed, i)
31+
XCTAssertNil(m.get(i))
32+
m[i] = i
33+
XCTAssertEqual(i, m[i])
34+
}
35+
XCTAssertEqual(100, m.count())
36+
let items = m.getAll()
37+
m.removeAll()
38+
XCTAssertEqual(0, m.count())
39+
m.append(contentsOf: items)
40+
XCTAssertEqual(100, m.count())
41+
}
42+
1543
}

0 commit comments

Comments
 (0)