Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/Atomic.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Atomic"
BuildableName = "Atomic"
BlueprintName = "Atomic"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AtomicTests"
BuildableName = "AtomicTests"
BlueprintName = "AtomicTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Atomic"
BuildableName = "Atomic"
BlueprintName = "Atomic"
ReferencedContainer = "container:">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AtomicTests"
BuildableName = "AtomicTests"
BlueprintName = "AtomicTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Atomic"
BuildableName = "Atomic"
BlueprintName = "Atomic"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Change Log
All notable changes to this project will be documented in this file.

## [Unreleased]

## Added
- Change the locking mechanism from `DispatchSemaphore` to `unfair_lock_os` and add support for `@dynamicMemberLookup`
- Added in Pull Request [#1](https://github.com/space-code/flare/pull/1).

#### 1.x Releases
- `0.0.x` Releases - [0.0.1](#100)

## [0.0.1](https://github.com/space-code/flare/releases/tag/0.0.1)
Released on 2023-06-18.

#### Added
- Initial release of Atomic.
- Added by [Nikita Vasilev](https://github.com/nik3212).
40 changes: 0 additions & 40 deletions Sources/Atomic/Atomic.swift

This file was deleted.

81 changes: 81 additions & 0 deletions Sources/Atomic/Classes/Core/Atomic/Atomic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Concurrency
// Copyright © 2023 Space Code. All rights reserved.
//

import Foundation

// MARK: - Atomic

/// An `Atomic` property wrapper.
@propertyWrapper
@dynamicMemberLookup
public final class Atomic<Value> {
// MARK: Properties

private let lock = UnfairLock()
private var value: Value

/// Automatically gets or sets the value of the variable.
public var wrappedValue: Value {
get { lock.around { value } }
set { lock.around { value = newValue } }
}

// MARK: Initialization

/// Create a new `Atomic` property wrapper with the given value.
///
/// - Parameter value: A value object,
public init(wrappedValue value: Value) {
self.value = value
}

// MARK: Public

/// Reads the current value of the resource and executes a closure with it, ensuring exclusive access.
///
/// - Parameter closure: A closure that takes the current value and returns a result.
///
/// - Returns: The result returned by the closure.
public func read<U>(_ closure: (Value) throws -> U) rethrows -> U {
try lock.around { try closure(self.value) }
}

/// Modifies the resource's value using a closure and ensures exclusive access while doing so.
///
/// - Parameter closure: A closure that takes an inout reference to the current value and returns a result.
///
/// - Returns: The result returned by the closure.
public func write<U>(_ closure: (inout Value) throws -> U) rethrows -> U {
try lock.around { try closure(&self.value) }
}

/// Writes a new value to the resource while ensuring exclusive access.
///
/// - Parameter value: The new value to set.
public func write(_ value: Value) {
write { $0 = value }
}

subscript<Property>(dynamicMember keyPath: WritableKeyPath<Value, Property>) -> Property {
get { lock.around { value[keyPath: keyPath] } }
set { lock.around { value[keyPath: keyPath] = newValue } }
}
}

// MARK: Equatable

extension Atomic: Equatable where Value: Equatable {
public static func == (lhs: Atomic<Value>, rhs: Atomic<Value>) -> Bool {
lhs.read { left in rhs.read { right in left == right }}
}
}

// MARK: Hashable

extension Atomic: Hashable where Value: Hashable {
public func hash(into hasher: inout Hasher) {
read { hasher.combine($0) }
}
}
38 changes: 38 additions & 0 deletions Sources/Atomic/Classes/Helpers/Lock/ILock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Concurrency
// Copyright © 2023 Space Code. All rights reserved.
//

import Foundation

// MARK: - ILock

/// Protocol for locking and unlocking mechanisms.
protocol ILock {
/// Locks a resource to prevent concurrent access.
func lock()

/// Unlocks a previously locked resource, allowing concurrent access.
func unlock()
}

extension ILock {
/// Executes a closure while holding the lock, and automatically unlocks afterward.
///
/// - Parameter closure: The closure to execute.
/// - Returns: The result returned by the closure.
func around<T>(_ closure: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try closure()
}

/// Executes a closure while holding the lock, and automatically unlocks afterward.
///
/// - Parameter closure: The closure to execute.
func around(_ closure: () throws -> Void) rethrows {
lock()
defer { unlock() }
try closure()
}
}
41 changes: 41 additions & 0 deletions Sources/Atomic/Classes/Helpers/Lock/UnfairLock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Concurrency
// Copyright © 2023 Space Code. All rights reserved.
//

import Foundation

// MARK: - UnfairLock

/// A class that uses an `os_unfair_lock` for synchronization.
final class UnfairLock {
// MARK: Properties

/// The underlying `os_unfair_lock` used for synchronization.
private let unfairLock: os_unfair_lock_t

// MARK: Initialization

/// Initializes an UnfairLock instance.
public init() {
unfairLock = .allocate(capacity: 1)
unfairLock.initialize(to: os_unfair_lock())
}

deinit {
unfairLock.deinitialize(count: 1)
unfairLock.deallocate()
}
}

// MARK: ILock

extension UnfairLock: ILock {
func lock() {
os_unfair_lock_lock(unfairLock)
}

func unlock() {
os_unfair_lock_unlock(unfairLock)
}
}
Loading