Skip to content

Commit

Permalink
Add ThreadSafeMap and ThreadSafeList
Browse files Browse the repository at this point in the history
  • Loading branch information
vlaminck committed Dec 7, 2019
1 parent c379de6 commit f36e0de
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 19 deletions.
10 changes: 3 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ import PackageDescription

let package = Package(
name: "ThreadSafeCollections",
platforms: [
.iOS(.v12)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "ThreadSafeCollections",
targets: ["ThreadSafeCollections"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "ThreadSafeCollections",
dependencies: []),
Expand Down
3 changes: 0 additions & 3 deletions Sources/ThreadSafeCollections/ThreadSafeCollections.swift

This file was deleted.

91 changes: 91 additions & 0 deletions Sources/ThreadSafeCollections/ThreadSafeList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Foundation

public class ThreadSafeList<V> {

private var items = [V]()
private var itemQueue: DispatchQueue
public init(identifier: String = UUID().uuidString) {
itemQueue = DispatchQueue(
label: "ThreadSafeList.\(String(describing: V.self)).queue.\(identifier)",
qos: .userInitiated,
attributes: .concurrent,
target: DispatchQueue.global(qos: .userInitiated)
)
}

public func get(_ index: Int) -> V? {
var value: V?
itemQueue.sync { // safely read
if self.items.indices.contains(index) {
value = self.items[index]
} else {
value = nil
}
}
return value
}

public func insert(_ value: V, at index: Int) {
itemQueue.async(flags: .barrier) { // safely write
self.items.insert(value, at: index)
}
}

@discardableResult public func remove(at index: Int) -> V? {
guard let value = get(index) else {
// make sure the index exists
return nil
}
itemQueue.async(flags: .barrier) { // safely write
self.items.remove(at: index)
}
return value
}

public func append(_ value: V) {
itemQueue.async(flags: .barrier) { // safely write
self.items.append(value)
}
}

public func append(contentsOf values: [V]) {
itemQueue.async(flags: .barrier) { // safely write
self.items.append(contentsOf: values)
}
}

public func removeAll() {
itemQueue.async(flags: .barrier) { // safely write
self.items.removeAll()
}
}

public func getAll() -> [V] {
var allItems = [V]()
itemQueue.sync { // safely read
allItems = self.items
}
return allItems
}

public func count() -> Int {
var count = 0
itemQueue.sync { // safely read
count = self.items.count
}
return count
}

public subscript(index: Int) -> V? {
get {
return self.get(index)
}
set(newValue) {
if let value = newValue {
self.insert(value, at: index)
} else {
self.remove(at: index)
}
}
}
}
73 changes: 73 additions & 0 deletions Sources/ThreadSafeCollections/ThreadSafeMap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Foundation

public class ThreadSafeMap<K: Hashable, V> {

private var items = [K: V]()
private var itemQueue: DispatchQueue

public init(identifier: String = UUID().uuidString) {
itemQueue = DispatchQueue(
label: "ThreadSafeMap.\(String(describing: K.self)).\(String(describing: V.self)).queue.\(identifier)",
qos: .userInitiated,
attributes: .concurrent,
target: DispatchQueue.global(qos: .userInitiated)
)
}

public func get(_ key: K) -> V? {
var value: V?
itemQueue.sync { // safely read
value = self.items[key]
}
return value
}

public func put(_ key: K, _ value: V?) {
itemQueue.async(flags: .barrier) { // safely write
self.items[key] = value
}
}

public func removeAll() {
itemQueue.async(flags: .barrier) { // safely write
self.items.removeAll()
}
}

public func removeValue(forKey key: K) {
itemQueue.async(flags: .barrier) { // safely write
self.items.removeValue(forKey: key)
}
}

public func getAll() -> [K: V] {
var allItems = [K: V]()
itemQueue.sync { // safely read
allItems = self.items
}
return allItems
}

public func putAll(_ allItems: [K: V]) {
itemQueue.async(flags: .barrier) { // safely write
self.items = allItems
}
}

public subscript(key: K) -> V? {
get {
return self.get(key)
}
set(newValue) {
self.put(key, newValue)
}
}

public func count() -> Int {
var count = 0
itemQueue.sync { // safely read
count = self.items.count
}
return count
}
}
46 changes: 37 additions & 9 deletions Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,42 @@ import XCTest
@testable import ThreadSafeCollections

final class ThreadSafeCollectionsTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(ThreadSafeCollections().text, "Hello, World!")

func testMap() {
let m = ThreadSafeMap<Int, Int>()
(0...99).forEach { i in
m.put(i, i)
XCTAssertEqual(i, m.get(i))
m.removeValue(forKey: i)
XCTAssertNil(m.get(i))
m[i] = i
XCTAssertEqual(i, m[i])
}

static var allTests = [
("testExample", testExample),
]
XCTAssertEqual(100, m.count())
let items = m.getAll()
m.removeAll()
XCTAssertEqual(0, m.count())
m.putAll(items)
XCTAssertEqual(100, m.count())
}

func testList() {
let m = ThreadSafeList<Int>()
(0...99).forEach { i in
m.append(i)
XCTAssertEqual(i, m.get(i))
let removed = m.remove(at: i)
XCTAssertEqual(removed, i)
XCTAssertNil(m.get(i))
m[i] = i
XCTAssertEqual(i, m[i])
}
XCTAssertEqual(100, m.count())
let items = m.getAll()
m.removeAll()
XCTAssertEqual(0, m.count())
m.append(contentsOf: items)
XCTAssertEqual(100, m.count())
}

}

0 comments on commit f36e0de

Please sign in to comment.