diff --git a/Package.swift b/Package.swift index 78b5a1b..61be855 100644 --- a/Package.swift +++ b/Package.swift @@ -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: []), diff --git a/Sources/ThreadSafeCollections/ThreadSafeCollections.swift b/Sources/ThreadSafeCollections/ThreadSafeCollections.swift deleted file mode 100644 index ba94eb1..0000000 --- a/Sources/ThreadSafeCollections/ThreadSafeCollections.swift +++ /dev/null @@ -1,3 +0,0 @@ -struct ThreadSafeCollections { - var text = "Hello, World!" -} diff --git a/Sources/ThreadSafeCollections/ThreadSafeList.swift b/Sources/ThreadSafeCollections/ThreadSafeList.swift new file mode 100644 index 0000000..7891ae0 --- /dev/null +++ b/Sources/ThreadSafeCollections/ThreadSafeList.swift @@ -0,0 +1,91 @@ +import Foundation + +public class ThreadSafeList { + + 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) + } + } + } +} diff --git a/Sources/ThreadSafeCollections/ThreadSafeMap.swift b/Sources/ThreadSafeCollections/ThreadSafeMap.swift new file mode 100644 index 0000000..925983a --- /dev/null +++ b/Sources/ThreadSafeCollections/ThreadSafeMap.swift @@ -0,0 +1,73 @@ +import Foundation + +public class ThreadSafeMap { + + 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 + } +} diff --git a/Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift b/Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift index 1f1e318..84f825e 100644 --- a/Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift +++ b/Tests/ThreadSafeCollectionsTests/ThreadSafeCollectionsTests.swift @@ -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() + (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() + (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()) + } + }