-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #125 from Infomaniak/APIV3_SendableProperty
feat: New SendableProperty property wrapper
- Loading branch information
Showing
7 changed files
with
476 additions
and
288 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
52 changes: 52 additions & 0 deletions
52
Sources/InfomaniakCore/Asynchronous/Sendable/SendableProperty.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
Infomaniak Core - iOS | ||
Copyright (C) 2024 Infomaniak Network SA | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import Foundation | ||
|
||
/// Making a property thread safe, while not requiring `await`. Conforms to Sendable. | ||
/// | ||
/// Please prefer using first party structured concurrency. Use this for prototyping or dealing with race conditions. | ||
@propertyWrapper | ||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) | ||
public final class SendableProperty<Property>: @unchecked Sendable { | ||
/// Serial locking queue | ||
let lock = DispatchQueue(label: "com.infomaniak.core.SendableProperty.lock") | ||
|
||
/// Store property | ||
var property: Property? | ||
|
||
public init() { } | ||
|
||
public var wrappedValue: Property? { | ||
get { | ||
lock.sync { | ||
return self.property | ||
} | ||
} | ||
set { | ||
lock.sync { | ||
self.property = newValue | ||
} | ||
} | ||
} | ||
|
||
/// The property wrapper itself for debugging and testing | ||
public var projectedValue: SendableProperty { | ||
self | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
Tests/InfomaniakCoreTests/Sendable/UTSendableArray.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/* | ||
Infomaniak Core - iOS | ||
Copyright (C) 2024 Infomaniak Network SA | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import InfomaniakCore | ||
import XCTest | ||
|
||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) | ||
final class UTSendableArray: XCTestCase { | ||
func testInsertSubscript() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection[0] = "a" | ||
collection[1] = "b" | ||
collection[2] = "c" | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertEqual(collection.count, 3) | ||
} | ||
|
||
func testInsert() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.append("a") | ||
collection.append("b") | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertEqual(collection.count, 2) | ||
} | ||
|
||
func testUpdate() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.append("a") | ||
collection[0] = "b" | ||
collection[1] = "c" | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertEqual(collection.count, 2) | ||
} | ||
|
||
func testRemoveAll() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
collection.append("a") | ||
collection.append("b") | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertTrue(collection.isEmpty) | ||
} | ||
|
||
func testRemoveAllWhere() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
collection.append("a") | ||
collection.append("b") | ||
collection.append("c") | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.removeAll(where: { $0 == "b" }) | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertFalse(collection.values.contains("b")) | ||
} | ||
|
||
func testIterator() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
collection.append("a") | ||
collection.append("b") | ||
|
||
var iterator = collection.makeIterator() | ||
|
||
// WHEN | ||
// We remove all items in the collection | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
await t.finish() | ||
|
||
// THEN | ||
XCTAssertTrue(collection.values.isEmpty, "The collection is expected to be empty") | ||
|
||
// We can work with the captured enumeration | ||
var isEmpty = true | ||
while let value = iterator.next() { | ||
isEmpty = false | ||
|
||
if value == "a" || value == "b" { | ||
// OK | ||
} else { | ||
XCTFail("unexpected value:\(value)") | ||
} | ||
} | ||
|
||
XCTAssertFalse(isEmpty, "the iterator is not supposed to be empty") | ||
} | ||
|
||
func testEnumerated() async { | ||
// GIVEN | ||
let collection = SendableArray<String>() | ||
collection.append("a") | ||
collection.append("b") | ||
|
||
let collectionEnumerated = collection.enumerated() | ||
|
||
// WHEN | ||
// We remove all items in the collection | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
await t.finish() | ||
|
||
// THEN | ||
XCTAssertTrue(collection.values.isEmpty, "The collection is expected to be empty") | ||
|
||
// We can work with the captured enumeration | ||
var isEmpty = true | ||
for (index, value) in collectionEnumerated { | ||
isEmpty = false | ||
|
||
if value == "a" || value == "b" { | ||
// OK | ||
} else { | ||
XCTFail("unexpected value:\(value) at index:\(index)") | ||
} | ||
} | ||
|
||
XCTAssertFalse(isEmpty, "the iterator is not supposed to be empty") | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
Tests/InfomaniakCoreTests/Sendable/UTSendableDictionary.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
Infomaniak Core - iOS | ||
Copyright (C) 2024 Infomaniak Network SA | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import InfomaniakCore | ||
import XCTest | ||
|
||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) | ||
final class UTSendableDictionary: XCTestCase { | ||
func testInsertSubscript() async { | ||
// GIVEN | ||
let collection = SendableDictionary<String, Int>() | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection["a"] = 1 | ||
collection["b"] = 2 | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertEqual(collection.count, 2) | ||
} | ||
|
||
func testInsert() async { | ||
// GIVEN | ||
let collection = SendableDictionary<String, Int>() | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.setValue(1, for: "a") | ||
collection.setValue(2, for: "b") | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertEqual(collection.count, 2) | ||
} | ||
|
||
func testRemoveAll() async { | ||
// GIVEN | ||
let collection = SendableDictionary<String, Int>() | ||
collection.setValue(1, for: "a") | ||
collection.setValue(2, for: "b") | ||
|
||
// WHEN | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
_ = await t.result | ||
|
||
// THEN | ||
XCTAssertTrue(collection.values.isEmpty) | ||
} | ||
|
||
func testIterator() async { | ||
// GIVEN | ||
let collection = SendableDictionary<String, Int>() | ||
collection.setValue(1, for: "a") | ||
collection.setValue(2, for: "b") | ||
|
||
var iterator = collection.makeIterator() | ||
|
||
// WHEN | ||
// We remove all items in the collection | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
await t.finish() | ||
|
||
// THEN | ||
XCTAssertTrue(collection.values.isEmpty, "The collection is expected to be empty") | ||
|
||
// We can work with the captured enumeration | ||
var isEmpty = true | ||
while let (key, value) = iterator.next() { | ||
isEmpty = false | ||
|
||
if key == "a" { | ||
XCTAssertEqual(value, 1) | ||
} else if key == "b" { | ||
XCTAssertEqual(value, 2) | ||
} else { | ||
XCTFail("unexpected key:\(key) value:\(value)") | ||
} | ||
} | ||
|
||
XCTAssertFalse(isEmpty, "the iterator is not supposed to be empty") | ||
} | ||
|
||
func testEnumerated() async { | ||
// GIVEN | ||
let collection = SendableDictionary<String, Int>() | ||
collection.setValue(1, for: "a") | ||
collection.setValue(2, for: "b") | ||
|
||
let collectionEnumerated = collection.enumerated() | ||
|
||
// WHEN | ||
// We remove all items in the collection | ||
let t = Task.detached { | ||
collection.removeAll() | ||
} | ||
|
||
await t.finish() | ||
|
||
// THEN | ||
XCTAssertTrue(collection.values.isEmpty, "The collection is expected to be empty") | ||
|
||
// We can work with the captured enumeration | ||
var isEmpty = true | ||
for (index, node) in collectionEnumerated { | ||
isEmpty = false | ||
|
||
let key = node.0 | ||
let value = node.1 | ||
|
||
if key == "a" { | ||
XCTAssertEqual(value, 1) | ||
} else if key == "b" { | ||
XCTAssertEqual(value, 2) | ||
} else { | ||
XCTFail("unexpected key:\(key) value:\(value) at index:\(index) ") | ||
} | ||
} | ||
|
||
XCTAssertFalse(isEmpty, "the iterator is not supposed to be empty") | ||
} | ||
} |
Oops, something went wrong.