Skip to content

Commit 87aa5e1

Browse files
committed
Change the locking mechanism from DispatchSemaphore to unfair_lock_os (#2)
* Implement `os_unfair_lock` * Update `CHANGELOG.md`
1 parent c99f771 commit 87aa5e1

7 files changed

Lines changed: 341 additions & 42 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1410"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "Atomic"
18+
BuildableName = "Atomic"
19+
BlueprintName = "Atomic"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
<BuildActionEntry
24+
buildForTesting = "YES"
25+
buildForRunning = "YES"
26+
buildForProfiling = "NO"
27+
buildForArchiving = "NO"
28+
buildForAnalyzing = "YES">
29+
<BuildableReference
30+
BuildableIdentifier = "primary"
31+
BlueprintIdentifier = "AtomicTests"
32+
BuildableName = "AtomicTests"
33+
BlueprintName = "AtomicTests"
34+
ReferencedContainer = "container:">
35+
</BuildableReference>
36+
</BuildActionEntry>
37+
</BuildActionEntries>
38+
</BuildAction>
39+
<TestAction
40+
buildConfiguration = "Debug"
41+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
42+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
43+
shouldUseLaunchSchemeArgsEnv = "YES"
44+
codeCoverageEnabled = "YES"
45+
onlyGenerateCoverageForSpecifiedTargets = "YES">
46+
<CodeCoverageTargets>
47+
<BuildableReference
48+
BuildableIdentifier = "primary"
49+
BlueprintIdentifier = "Atomic"
50+
BuildableName = "Atomic"
51+
BlueprintName = "Atomic"
52+
ReferencedContainer = "container:">
53+
</BuildableReference>
54+
</CodeCoverageTargets>
55+
<Testables>
56+
<TestableReference
57+
skipped = "NO">
58+
<BuildableReference
59+
BuildableIdentifier = "primary"
60+
BlueprintIdentifier = "AtomicTests"
61+
BuildableName = "AtomicTests"
62+
BlueprintName = "AtomicTests"
63+
ReferencedContainer = "container:">
64+
</BuildableReference>
65+
</TestableReference>
66+
</Testables>
67+
</TestAction>
68+
<LaunchAction
69+
buildConfiguration = "Debug"
70+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
71+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
72+
launchStyle = "0"
73+
useCustomWorkingDirectory = "NO"
74+
ignoresPersistentStateOnLaunch = "NO"
75+
debugDocumentVersioning = "YES"
76+
debugServiceExtension = "internal"
77+
allowLocationSimulation = "YES">
78+
</LaunchAction>
79+
<ProfileAction
80+
buildConfiguration = "Release"
81+
shouldUseLaunchSchemeArgsEnv = "YES"
82+
savedToolIdentifier = ""
83+
useCustomWorkingDirectory = "NO"
84+
debugDocumentVersioning = "YES">
85+
<MacroExpansion>
86+
<BuildableReference
87+
BuildableIdentifier = "primary"
88+
BlueprintIdentifier = "Atomic"
89+
BuildableName = "Atomic"
90+
BlueprintName = "Atomic"
91+
ReferencedContainer = "container:">
92+
</BuildableReference>
93+
</MacroExpansion>
94+
</ProfileAction>
95+
<AnalyzeAction
96+
buildConfiguration = "Debug">
97+
</AnalyzeAction>
98+
<ArchiveAction
99+
buildConfiguration = "Release"
100+
revealArchiveInOrganizer = "YES">
101+
</ArchiveAction>
102+
</Scheme>

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Change Log
2+
All notable changes to this project will be documented in this file.
3+
4+
## [Unreleased]
5+
6+
## Added
7+
- Change the locking mechanism from `DispatchSemaphore` to `unfair_lock_os` and add support for `@dynamicMemberLookup`
8+
- Added in Pull Request [#1](https://github.com/space-code/flare/pull/1).
9+
10+
#### 1.x Releases
11+
- `0.0.x` Releases - [0.0.1](#100)
12+
13+
## [0.0.1](https://github.com/space-code/flare/releases/tag/0.0.1)
14+
Released on 2023-06-18.
15+
16+
#### Added
17+
- Initial release of Atomic.
18+
- Added by [Nikita Vasilev](https://github.com/nik3212).

Sources/Atomic/Atomic.swift

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// Concurrency
3+
// Copyright © 2023 Space Code. All rights reserved.
4+
//
5+
6+
import Foundation
7+
8+
// MARK: - Atomic
9+
10+
/// An `Atomic` property wrapper.
11+
@propertyWrapper
12+
@dynamicMemberLookup
13+
public final class Atomic<Value> {
14+
// MARK: Properties
15+
16+
private let lock = UnfairLock()
17+
private var value: Value
18+
19+
/// Automatically gets or sets the value of the variable.
20+
public var wrappedValue: Value {
21+
get { lock.around { value } }
22+
set { lock.around { value = newValue } }
23+
}
24+
25+
// MARK: Initialization
26+
27+
/// Create a new `Atomic` property wrapper with the given value.
28+
///
29+
/// - Parameter value: A value object,
30+
public init(wrappedValue value: Value) {
31+
self.value = value
32+
}
33+
34+
// MARK: Public
35+
36+
/// Reads the current value of the resource and executes a closure with it, ensuring exclusive access.
37+
///
38+
/// - Parameter closure: A closure that takes the current value and returns a result.
39+
///
40+
/// - Returns: The result returned by the closure.
41+
public func read<U>(_ closure: (Value) throws -> U) rethrows -> U {
42+
try lock.around { try closure(self.value) }
43+
}
44+
45+
/// Modifies the resource's value using a closure and ensures exclusive access while doing so.
46+
///
47+
/// - Parameter closure: A closure that takes an inout reference to the current value and returns a result.
48+
///
49+
/// - Returns: The result returned by the closure.
50+
public func write<U>(_ closure: (inout Value) throws -> U) rethrows -> U {
51+
try lock.around { try closure(&self.value) }
52+
}
53+
54+
/// Writes a new value to the resource while ensuring exclusive access.
55+
///
56+
/// - Parameter value: The new value to set.
57+
public func write(_ value: Value) {
58+
write { $0 = value }
59+
}
60+
61+
subscript<Property>(dynamicMember keyPath: WritableKeyPath<Value, Property>) -> Property {
62+
get { lock.around { value[keyPath: keyPath] } }
63+
set { lock.around { value[keyPath: keyPath] = newValue } }
64+
}
65+
}
66+
67+
// MARK: Equatable
68+
69+
extension Atomic: Equatable where Value: Equatable {
70+
public static func == (lhs: Atomic<Value>, rhs: Atomic<Value>) -> Bool {
71+
lhs.read { left in rhs.read { right in left == right }}
72+
}
73+
}
74+
75+
// MARK: Hashable
76+
77+
extension Atomic: Hashable where Value: Hashable {
78+
public func hash(into hasher: inout Hasher) {
79+
read { hasher.combine($0) }
80+
}
81+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Concurrency
3+
// Copyright © 2023 Space Code. All rights reserved.
4+
//
5+
6+
import Foundation
7+
8+
// MARK: - ILock
9+
10+
/// Protocol for locking and unlocking mechanisms.
11+
protocol ILock {
12+
/// Locks a resource to prevent concurrent access.
13+
func lock()
14+
15+
/// Unlocks a previously locked resource, allowing concurrent access.
16+
func unlock()
17+
}
18+
19+
extension ILock {
20+
/// Executes a closure while holding the lock, and automatically unlocks afterward.
21+
///
22+
/// - Parameter closure: The closure to execute.
23+
/// - Returns: The result returned by the closure.
24+
func around<T>(_ closure: () throws -> T) rethrows -> T {
25+
lock()
26+
defer { unlock() }
27+
return try closure()
28+
}
29+
30+
/// Executes a closure while holding the lock, and automatically unlocks afterward.
31+
///
32+
/// - Parameter closure: The closure to execute.
33+
func around(_ closure: () throws -> Void) rethrows {
34+
lock()
35+
defer { unlock() }
36+
try closure()
37+
}
38+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// Concurrency
3+
// Copyright © 2023 Space Code. All rights reserved.
4+
//
5+
6+
import Foundation
7+
8+
// MARK: - UnfairLock
9+
10+
/// A class that uses an `os_unfair_lock` for synchronization.
11+
final class UnfairLock {
12+
// MARK: Properties
13+
14+
/// The underlying `os_unfair_lock` used for synchronization.
15+
private let unfairLock: os_unfair_lock_t
16+
17+
// MARK: Initialization
18+
19+
/// Initializes an UnfairLock instance.
20+
public init() {
21+
unfairLock = .allocate(capacity: 1)
22+
unfairLock.initialize(to: os_unfair_lock())
23+
}
24+
25+
deinit {
26+
unfairLock.deinitialize(count: 1)
27+
unfairLock.deallocate()
28+
}
29+
}
30+
31+
// MARK: ILock
32+
33+
extension UnfairLock: ILock {
34+
func lock() {
35+
os_unfair_lock_lock(unfairLock)
36+
}
37+
38+
func unlock() {
39+
os_unfair_lock_unlock(unfairLock)
40+
}
41+
}

0 commit comments

Comments
 (0)