Skip to content

Commit fd11eaf

Browse files
authored
chore: Add LockAndExecute util (#1352)
* chore: Add NSLocking.execute utility extension * chore: Update plugins to use NSLocking.execute utility
1 parent a74be69 commit fd11eaf

File tree

15 files changed

+239
-226
lines changed

15 files changed

+239
-226
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@
601601
FA6BC87F235F5DAE0001A882 /* APICategoryInterceptorBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6BC87E235F5DAE0001A882 /* APICategoryInterceptorBehavior.swift */; };
602602
FA76A2D12342B1A600B91ADB /* StorageCategory+HubPayloadEventName.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA76A2D02342B1A600B91ADB /* StorageCategory+HubPayloadEventName.swift */; };
603603
FA76A2D32342B47100B91ADB /* HubPayloadEventName.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA76A2D22342B47100B91ADB /* HubPayloadEventName.swift */; };
604+
FA81688226BB2E07007CEBB6 /* NSLocking+Execute.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA81688126BB2E07007CEBB6 /* NSLocking+Execute.swift */; };
604605
FA87609324E45091004148C6 /* AmplifyConfigurationInitFromFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA87609024E44CCC004148C6 /* AmplifyConfigurationInitFromFileTests.swift */; };
605606
FA8EE779238627040097E4F1 /* Model+ModelName.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8EE778238627040097E4F1 /* Model+ModelName.swift */; };
606607
FA8EE77D238627350097E4F1 /* Model+Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8EE77C238627350097E4F1 /* Model+Subscript.swift */; };
@@ -1582,6 +1583,7 @@
15821583
FA6BC87E235F5DAE0001A882 /* APICategoryInterceptorBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICategoryInterceptorBehavior.swift; sourceTree = "<group>"; };
15831584
FA76A2D02342B1A600B91ADB /* StorageCategory+HubPayloadEventName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageCategory+HubPayloadEventName.swift"; sourceTree = "<group>"; };
15841585
FA76A2D22342B47100B91ADB /* HubPayloadEventName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HubPayloadEventName.swift; sourceTree = "<group>"; };
1586+
FA81688126BB2E07007CEBB6 /* NSLocking+Execute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocking+Execute.swift"; sourceTree = "<group>"; };
15851587
FA87609024E44CCC004148C6 /* AmplifyConfigurationInitFromFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplifyConfigurationInitFromFileTests.swift; sourceTree = "<group>"; };
15861588
FA8EE772238621320097E4F1 /* AnyModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyModelTests.swift; sourceTree = "<group>"; };
15871589
FA8EE776238626D60097E4F1 /* AnyModel+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyModel+Codable.swift"; sourceTree = "<group>"; };
@@ -3305,7 +3307,6 @@
33053307
FA5704C8245F3C6900392C19 /* AmplifyInProcessReportingOperation.swift */,
33063308
FA249EE624C5FA49009B3CE8 /* AmplifyInProcessReportingOperation+Combine.swift */,
33073309
FA56F72622B14BF70039754A /* AmplifyOperation.swift */,
3308-
FA9F939526BAF73F00805607 /* OperationCancelledError.swift */,
33093310
FAA7A5A524C0CC8F00CA863F /* AmplifyOperation+Combine.swift */,
33103311
FA5704CA245F58C600392C19 /* AmplifyOperation+Hub.swift */,
33113312
FAB9D810233BF5F600928AA9 /* AmplifyOperationContext.swift */,
@@ -3323,11 +3324,13 @@
33233324
FA09B9402321BB78000E064D /* JSONValue.swift */,
33243325
FACD264F2386E9410068FBE6 /* JSONValue+KeyPath.swift */,
33253326
FACD264E2386E9410068FBE6 /* JSONValue+Subscript.swift */,
3327+
FA9F939526BAF73F00805607 /* OperationCancelledError.swift */,
33263328
FAE4145E23999BC900CE94C2 /* Result+Void.swift */,
33273329
FA56F72422B14B6A0039754A /* Resumable.swift */,
33283330
B9FAA174238EFC59009414B4 /* String+Extensions.swift */,
33293331
B9A329CB243559BF00C5B80C /* TimeInterval+Helper.swift */,
33303332
219A88EC23F3309800BBC5F2 /* Tree.swift */,
3333+
FA81688326BB3B40007CEBB6 /* Internal */,
33313334
);
33323335
path = Support;
33333336
sourceTree = "<group>";
@@ -3384,6 +3387,14 @@
33843387
path = ClientBehavior;
33853388
sourceTree = "<group>";
33863389
};
3390+
FA81688326BB3B40007CEBB6 /* Internal */ = {
3391+
isa = PBXGroup;
3392+
children = (
3393+
FA81688126BB2E07007CEBB6 /* NSLocking+Execute.swift */,
3394+
);
3395+
path = Internal;
3396+
sourceTree = "<group>";
3397+
};
33873398
FA8EE775238626C70097E4F1 /* AnyModel */ = {
33883399
isa = PBXGroup;
33893400
children = (
@@ -3674,9 +3685,9 @@
36743685
isa = PBXGroup;
36753686
children = (
36763687
FACA35EA2326B217000E74F6 /* AmplifyConfigurationInitializationTests.swift */,
3677-
FA1B964D24BF5FA70002B90A /* AmplifyOperationCombineTests.swift */,
3678-
FA58456824DA29B00028D65A /* AmplifyInProcessReportingOperationCombineTests.swift */,
36793688
FA58456A24DA31370028D65A /* AmplifyInProcessReportingOperationChainedTests.swift */,
3689+
FA58456824DA29B00028D65A /* AmplifyInProcessReportingOperationCombineTests.swift */,
3690+
FA1B964D24BF5FA70002B90A /* AmplifyOperationCombineTests.swift */,
36803691
B9DCA262240F217C00075E22 /* AnyEncodableTests.swift */,
36813692
FACF520823298C1200646E10 /* AtomicDictionaryTests.swift */,
36823693
FAAFAF3023904B75002CF932 /* AtomicValue+BoolTests.swift */,
@@ -5205,6 +5216,7 @@
52055216
6BDD6320256353F0008D70DF /* Evaluable.swift in Sources */,
52065217
21FFF997230C96CB005878EA /* StorageRemoveOperation.swift in Sources */,
52075218
FAC2351C227A053D00424678 /* APICategoryPlugin.swift in Sources */,
5219+
FA81688226BB2E07007CEBB6 /* NSLocking+Execute.swift in Sources */,
52085220
FA176ED52385012000C5C5F9 /* DataStoreCategory+HubPayloadEventName.swift in Sources */,
52095221
210DBC162332B3CB009B9E51 /* StorageDownloadDataOperation.swift in Sources */,
52105222
B9A329CC243559BF00C5B80C /* TimeInterval+Helper.swift in Sources */,

Amplify/Core/Support/AtomicDictionary.swift

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,62 +17,48 @@ final class AtomicDictionary<Key: Hashable, Value> {
1717
}
1818

1919
var count: Int {
20-
lock.lock()
21-
defer {
22-
lock.unlock()
20+
lock.execute {
21+
value.count
2322
}
24-
return value.count
2523
}
2624

2725
var keys: [Key] {
28-
lock.lock()
29-
defer {
30-
lock.unlock()
26+
lock.execute {
27+
Array(value.keys)
3128
}
32-
return Array(value.keys)
3329
}
3430

3531
var values: [Value] {
36-
lock.lock()
37-
defer {
38-
lock.unlock()
32+
lock.execute {
33+
Array(value.values)
3934
}
40-
return Array(value.values)
4135
}
4236

4337
// MARK: - Functions
4438

4539
func getValue(forKey key: Key) -> Value? {
46-
lock.lock()
47-
defer {
48-
lock.unlock()
40+
lock.execute {
41+
value[key]
4942
}
50-
return value[key]
5143
}
5244

5345
func removeAll() {
54-
lock.lock()
55-
defer {
56-
lock.unlock()
46+
lock.execute {
47+
value = [:]
5748
}
58-
value = [:]
5949
}
6050

6151
@discardableResult
6252
func removeValue(forKey key: Key) -> Value? {
63-
lock.lock()
64-
defer {
65-
lock.unlock()
53+
lock.execute {
54+
value.removeValue(forKey: key)
6655
}
67-
return value.removeValue(forKey: key)
6856
}
6957

7058
func set(value: Value, forKey key: Key) {
71-
lock.lock()
72-
defer {
73-
lock.unlock()
59+
lock.execute {
60+
self.value[key] = value
7461
}
75-
self.value[key] = value
7662
}
7763

7864
}

Amplify/Core/Support/AtomicValue+Bool.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ extension AtomicValue where Value == Bool {
1616
/// print(atomicBool.get()) // prints "false"
1717
/// ```
1818
public func getAndToggle() -> Value {
19-
lock.lock()
20-
defer {
21-
lock.unlock()
19+
lock.execute {
20+
let oldValue = value
21+
value.toggle()
22+
return oldValue
2223
}
23-
let oldValue = value
24-
value.toggle()
25-
return oldValue
2624
}
2725
}

Amplify/Core/Support/AtomicValue+Numeric.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,17 @@
88
extension AtomicValue where Value: Numeric {
99
/// Increments the current value by `amount` and returns the incremented value
1010
public func increment(by amount: Value = 1) -> Value {
11-
lock.lock()
12-
defer {
13-
lock.unlock()
11+
lock.execute {
12+
value += amount
13+
return value
1414
}
15-
value += amount
16-
return value
1715
}
1816

1917
/// Decrements the current value by `amount` and returns the decremented value
2018
public func decrement(by amount: Value = 1) -> Value {
21-
lock.lock()
22-
defer {
23-
lock.unlock()
19+
lock.execute {
20+
value -= amount
21+
return value
2422
}
25-
value -= amount
26-
return value
2723
}
2824
}

Amplify/Core/Support/AtomicValue+RangeReplaceableCollection.swift

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,26 @@
77

88
extension AtomicValue where Value: RangeReplaceableCollection {
99
public func append(_ newElement: Value.Element) {
10-
lock.lock()
11-
defer {
12-
lock.unlock()
10+
lock.execute {
11+
value.append(newElement)
1312
}
14-
value.append(newElement)
1513
}
1614

1715
public func append<S>(contentsOf sequence: S) where S: Sequence, S.Element == Value.Element {
18-
lock.lock()
19-
defer {
20-
lock.unlock()
16+
lock.execute {
17+
value.append(contentsOf: sequence)
2118
}
22-
value.append(contentsOf: sequence)
2319
}
2420

2521
public func removeFirst() -> Value.Element {
26-
lock.lock()
27-
defer {
28-
lock.unlock()
22+
lock.execute {
23+
value.removeFirst()
2924
}
30-
return value.removeFirst()
3125
}
3226

3327
public subscript(_ key: Value.Index) -> Value.Element {
34-
lock.lock()
35-
defer {
36-
lock.unlock()
28+
lock.execute {
29+
value[key]
3730
}
38-
return value[key]
3931
}
4032
}

Amplify/Core/Support/AtomicValue.swift

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,59 +7,55 @@
77

88
import Foundation
99

10+
/// A class that wraps access to its underlying value with an NSLocking instance.
1011
public final class AtomicValue<Value> {
11-
let lock = NSLock()
12-
12+
let lock: NSLocking
1313
var value: Value
1414

1515
public init(initialValue: Value) {
16+
self.lock = NSLock()
1617
self.value = initialValue
1718
}
1819

1920
public func get() -> Value {
20-
lock.lock()
21-
defer {
22-
lock.unlock()
21+
lock.execute {
22+
value
2323
}
24-
return value
2524
}
2625

2726
public func set(_ newValue: Value) {
28-
lock.lock()
29-
defer {
30-
lock.unlock()
27+
lock.execute {
28+
value = newValue
3129
}
32-
value = newValue
3330
}
3431

3532
/// Sets AtomicValue to `newValue` and returns the old value
3633
public func getAndSet(_ newValue: Value) -> Value {
37-
lock.lock()
38-
defer {
39-
lock.unlock()
34+
lock.execute {
35+
let oldValue = value
36+
value = newValue
37+
return oldValue
4038
}
41-
let oldValue = value
42-
value = newValue
43-
return oldValue
4439
}
4540

4641
/// Performs `block` with the current value, preventing other access until the block exits.
4742
public func atomicallyPerform(block: (Value) -> Void) {
48-
lock.lock()
49-
defer {
50-
lock.unlock()
43+
lock.execute {
44+
block(value)
5145
}
52-
block(value)
5346
}
5447

5548
/// Performs `block` with an `inout` value, preventing other access until the block exits,
5649
/// and enabling the block to mutate the value
50+
///
51+
/// - Warning: The AtomicValue lock is not reentrant. Specifically, it is not
52+
/// possible to call outside the block to `get` an AtomicValue (e.g., via a
53+
/// convenience property) while inside the `with` block. Attempting to do so will
54+
/// cause a deadlock.
5755
public func with(block: (inout Value) -> Void) {
58-
lock.lock()
59-
defer {
60-
lock.unlock()
56+
lock.execute {
57+
block(&value)
6158
}
62-
block(&value)
6359
}
6460

6561
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
/// - Warning: Although this has `public` access, it is intended for internal use
11+
/// and should not be used directly by host applications. The behaviors and names of
12+
/// this type may change without warning.
13+
extension NSLocking {
14+
/// Execute `block` after obtaining a lock on `lock`.
15+
///
16+
/// - Warning: Although this has `public` access, it is intended for internal use
17+
/// and should not be used directly by host applications. The behaviors and names of
18+
/// this type may change without warning.
19+
/// - Parameters:
20+
/// - block: The block to execute
21+
public func execute(
22+
_ block: BasicThrowableClosure
23+
) rethrows {
24+
try execute(input: (), block: block)
25+
}
26+
27+
/// Execute `block` after obtaining a lock on `lock`, returning the output of
28+
/// `block`
29+
///
30+
/// - Warning: Although this has `public` access, it is intended for internal use
31+
/// and should not be used directly by host applications. The behaviors and names of
32+
/// this type may change without warning.
33+
/// - Parameters:
34+
/// - block: The block to execute
35+
public func execute<Output>(
36+
_ block: () throws -> Output
37+
) rethrows -> Output {
38+
try execute(input: (), block: block)
39+
}
40+
41+
private func execute<Input, Output>(
42+
input: Input,
43+
block: (Input) throws -> Output
44+
) rethrows -> Output {
45+
lock()
46+
defer { self.unlock() }
47+
return try block(input)
48+
}
49+
50+
}

0 commit comments

Comments
 (0)