mirror of https://github.com/oxen-io/session-ios

9 changed files with 487 additions and 3 deletions
@ -0,0 +1,319 @@
|
||||
// |
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
@objc |
||||
public enum AtomicError: Int, Error { |
||||
case invalidTransition |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
private class Atomics { |
||||
fileprivate static let fairQueue = DispatchQueue(label: "Atomics") |
||||
fileprivate static let unfairLock = UnfairLock() |
||||
|
||||
// Never instantiate this class. |
||||
private init() {} |
||||
|
||||
class func perform<T>(isFair: Bool = false, _ block: () throws -> T) rethrows -> T { |
||||
if isFair { |
||||
return try fairQueue.sync(execute: block) |
||||
} else { |
||||
return try unfairLock.withLock(block) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
// Provides Objective-C compatibility for the most common atomic value type. |
||||
@objc |
||||
public class AtomicBool: NSObject { |
||||
private let value = AtomicValue<Bool>(false) |
||||
|
||||
@objc(initWithValue:) |
||||
public required init(_ value: Bool) { |
||||
self.value.set(value) |
||||
} |
||||
|
||||
@objc |
||||
public func get() -> Bool { |
||||
return value.get() |
||||
} |
||||
|
||||
@objc |
||||
public func set(_ value: Bool) { |
||||
self.value.set(value) |
||||
} |
||||
|
||||
// Sets value to "toValue" IFF it currently has "fromValue", |
||||
// otherwise throws. |
||||
private func transition(from fromValue: Bool, to toValue: Bool) throws { |
||||
return try value.transition(from: fromValue, to: toValue) |
||||
} |
||||
|
||||
@objc |
||||
public func tryToSetFlag() -> Bool { |
||||
do { |
||||
try transition(from: false, to: true) |
||||
return true |
||||
} catch { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
@objc |
||||
public func tryToClearFlag() -> Bool { |
||||
do { |
||||
try transition(from: true, to: false) |
||||
return true |
||||
} catch { |
||||
return false |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
@objc |
||||
public class AtomicUInt: NSObject { |
||||
private let value = AtomicValue<UInt>(0) |
||||
|
||||
@objc |
||||
public required init(_ value: UInt = 0) { |
||||
self.value.set(value) |
||||
} |
||||
|
||||
@objc |
||||
public func get() -> UInt { |
||||
return value.get() |
||||
} |
||||
|
||||
@objc |
||||
public func set(_ value: UInt) { |
||||
self.value.set(value) |
||||
} |
||||
|
||||
@discardableResult |
||||
@objc |
||||
public func increment() -> UInt { |
||||
return value.map { $0 + 1 } |
||||
} |
||||
|
||||
@discardableResult |
||||
@objc |
||||
public func decrementOrZero() -> UInt { |
||||
return value.map { max($0, 1) - 1 } |
||||
} |
||||
|
||||
@discardableResult |
||||
@objc |
||||
public func add(_ delta: UInt) -> UInt { |
||||
return value.map { $0 + delta } |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
public final class AtomicValue<T> { |
||||
private var value: T |
||||
|
||||
public required convenience init(_ value: T) { |
||||
self.init(value, allowOptionalType: false) |
||||
} |
||||
|
||||
fileprivate init(_ value: T, allowOptionalType: Bool) { |
||||
self.value = value |
||||
} |
||||
|
||||
public func get() -> T { |
||||
Atomics.perform { |
||||
return self.value |
||||
} |
||||
} |
||||
|
||||
public func set(_ value: T) { |
||||
Atomics.perform { |
||||
self.value = value |
||||
} |
||||
} |
||||
|
||||
// Transform the current value using a block. |
||||
@discardableResult |
||||
public func map(_ block: @escaping (T) -> T) -> T { |
||||
Atomics.perform { |
||||
let newValue = block(self.value) |
||||
self.value = newValue |
||||
return newValue |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
extension AtomicValue: Codable where T: Codable { |
||||
public convenience init(from decoder: Decoder) throws { |
||||
let singleValueContainer = try decoder.singleValueContainer() |
||||
self.init(try singleValueContainer.decode(T.self)) |
||||
} |
||||
|
||||
public func encode(to encoder: Encoder) throws { |
||||
var singleValueContainer = encoder.singleValueContainer() |
||||
try singleValueContainer.encode(value) |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
extension AtomicValue where T: Equatable { |
||||
// Sets value to "toValue" IFF it currently has "fromValue", |
||||
// otherwise throws. |
||||
public func transition(from fromValue: T, to toValue: T) throws { |
||||
try Atomics.perform { |
||||
guard self.value == fromValue else { |
||||
throw AtomicError.invalidTransition |
||||
} |
||||
self.value = toValue |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
public final class AtomicOptional<T> { |
||||
fileprivate let value = AtomicValue<T?>(nil, allowOptionalType: true) |
||||
|
||||
public required init(_ value: T?) { |
||||
self.value.set(value) |
||||
} |
||||
|
||||
public func get() -> T? { |
||||
return value.get() |
||||
} |
||||
|
||||
public func set(_ value: T?) { |
||||
self.value.set(value) |
||||
} |
||||
} |
||||
|
||||
extension AtomicOptional: Codable where T: Codable { |
||||
public convenience init(from decoder: Decoder) throws { |
||||
let singleValueContainer = try decoder.singleValueContainer() |
||||
|
||||
if singleValueContainer.decodeNil() { |
||||
self.init(nil) |
||||
} else { |
||||
self.init(try singleValueContainer.decode(T.self)) |
||||
} |
||||
} |
||||
|
||||
public func encode(to encoder: Encoder) throws { |
||||
var singleValueContainer = encoder.singleValueContainer() |
||||
try singleValueContainer.encode(value) |
||||
} |
||||
} |
||||
|
||||
extension AtomicOptional where T: Equatable { |
||||
// Sets value to "toValue" IFF it currently has "fromValue", |
||||
// otherwise throws. |
||||
public func transition(from fromValue: T, to toValue: T) throws { |
||||
try value.transition(from: fromValue, to: toValue) |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
public class AtomicArray<T> { |
||||
|
||||
private var values: [T] |
||||
|
||||
public required init(_ values: [T] = []) { |
||||
self.values = values |
||||
} |
||||
|
||||
public func get() -> [T] { |
||||
Atomics.perform { |
||||
values |
||||
} |
||||
} |
||||
|
||||
public func set(_ values: [T]) { |
||||
Atomics.perform { |
||||
self.values = values |
||||
} |
||||
} |
||||
|
||||
public func append(_ value: T) { |
||||
Atomics.perform { |
||||
values.append(value) |
||||
} |
||||
} |
||||
|
||||
public var first: T? { |
||||
Atomics.perform { |
||||
values.first |
||||
} |
||||
} |
||||
|
||||
public var popHead: T? { |
||||
Atomics.perform { |
||||
values.removeFirst() |
||||
} |
||||
} |
||||
|
||||
public func pushTail(_ value: T) { |
||||
append(value) |
||||
} |
||||
} |
||||
|
||||
extension AtomicArray where T: Equatable { |
||||
public func remove(_ valueToRemove: T) { |
||||
Atomics.perform { |
||||
self.values = self.values.filter { (value: T) -> Bool in |
||||
valueToRemove != value |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
public class AtomicDictionary<Key: Hashable, Value> { |
||||
private var values: [Key: Value] |
||||
|
||||
public required init(_ values: [Key: Value] = [:]) { |
||||
self.values = values |
||||
} |
||||
|
||||
public subscript(_ key: Key) -> Value? { |
||||
set { Atomics.perform { self.values[key] = newValue } } |
||||
get { Atomics.perform { self.values[key] } } |
||||
} |
||||
|
||||
public func get() -> [Key: Value] { |
||||
Atomics.perform { self.values } |
||||
} |
||||
|
||||
public func set(_ values: [Key: Value]) { |
||||
Atomics.perform { self.values = values } |
||||
} |
||||
} |
||||
|
||||
// MARK: - |
||||
|
||||
public class AtomicSet<T: Hashable> { |
||||
private var values = Set<T>() |
||||
|
||||
public required init() {} |
||||
|
||||
public func insert(_ value: T) { |
||||
Atomics.perform { _ = self.values.insert(value) } |
||||
} |
||||
|
||||
public func contains(_ value: T) -> Bool { |
||||
Atomics.perform { self.values.contains(value) } |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
/// An Objective-C wrapper around os_unfair_lock. This is a non-FIFO, priority preserving lock. See: os/lock.h
|
||||
///
|
||||
/// @discussion Why is this necessary? os_unfair_lock has some unexpected behavior in Swift. These problems arise
|
||||
/// from Swift's handling of inout C structs. Passing the underlying struct as an inout parameter results in
|
||||
/// surprising Law of Exclusivity violations. There are two ways to work around this: Manually allocate heap storage
|
||||
/// in Swift or bridge to Objective-C. I figured bridging a simple struct is a bit easier to read.
|
||||
///
|
||||
/// Note: Errors with unfair lock are fatal and will terminate the process.
|
||||
NS_SWIFT_NAME(UnfairLock) |
||||
@interface UnfairLock : NSObject <NSLocking> |
||||
|
||||
/// Locks the lock. Blocks if the lock is held by another thread.
|
||||
/// Forwards to os_unfair_lock_lock() defined in os/lock.h
|
||||
- (void)lock; |
||||
|
||||
/// Unlocks the lock. Fatal error if the lock is owned by another thread.
|
||||
/// Forwards to os_unfair_lock_unlock() defined in os/lock.h
|
||||
- (void)unlock; |
||||
|
||||
/// Attempts to lock the lock. Returns YES if the lock was successfully acquired.
|
||||
/// Forwards to os_unfair_lock_trylock() defined in os/lock.h
|
||||
- (BOOL)tryLock NS_SWIFT_NAME(tryLock()); |
||||
// Note: NS_SWIFT_NAME is required to prevent bridging from renaming to `try()`.
|
||||
|
||||
/// Fatal assert that the lock is owned by the current thread.
|
||||
/// Forwards to os_unfair_lock_assert_owner defined in os/lock.h
|
||||
- (void)assertOwner; |
||||
|
||||
/// Fatal assert that the lock is not owned by the current thread.
|
||||
/// Forwards to os_unfair_lock_assert_not_owner defined in os/lock.h
|
||||
- (void)assertNotOwner; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,46 @@
|
||||
// |
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved. |
||||
// |
||||
|
||||
#import <SessionUtilitiesKit/UnfairLock.h> |
||||
#import <os/lock.h> |
||||
|
||||
@implementation UnfairLock { |
||||
os_unfair_lock _lock; |
||||
} |
||||
|
||||
- (instancetype)init |
||||
{ |
||||
self = [super init]; |
||||
if (self) { |
||||
_lock = OS_UNFAIR_LOCK_INIT; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (void)lock |
||||
{ |
||||
os_unfair_lock_lock(&_lock); |
||||
} |
||||
|
||||
- (void)unlock |
||||
{ |
||||
os_unfair_lock_unlock(&_lock); |
||||
} |
||||
|
||||
- (BOOL)tryLock |
||||
{ |
||||
return os_unfair_lock_trylock(&_lock); |
||||
} |
||||
|
||||
- (void)assertOwner |
||||
{ |
||||
os_unfair_lock_assert_owner(&_lock); |
||||
} |
||||
|
||||
- (void)assertNotOwner |
||||
{ |
||||
os_unfair_lock_assert_not_owner(&_lock); |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,51 @@
|
||||
// |
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
public extension UnfairLock { |
||||
|
||||
/// Acquires and releases the lock around the provided closure. Blocks the current thread until the lock can be |
||||
/// acquired. |
||||
@objc |
||||
@available(swift, obsoleted: 1.0) |
||||
final func withLockObjc(_ criticalSection: () -> Void) { |
||||
withLock(criticalSection) |
||||
} |
||||
|
||||
/// Acquires and releases the lock around the provided closure. Blocks the current thread until the lock can be |
||||
/// acquired. |
||||
final func withLock<T>(_ criticalSection: () throws -> T) rethrows -> T { |
||||
lock() |
||||
defer { unlock() } |
||||
|
||||
return try criticalSection() |
||||
} |
||||
|
||||
/// Acquires and releases the lock around the provided closure. Returns without performing the closure if the lock |
||||
/// can not be acquired. |
||||
/// - Returns: `true` if the lock was acquired and the closure was invoked. `false` if the lock could not be |
||||
/// acquired. |
||||
@discardableResult |
||||
final func tryWithLock(_ criticalSection: () throws -> Void) rethrows -> Bool { |
||||
guard tryLock() else { return false } |
||||
defer { unlock() } |
||||
|
||||
try criticalSection() |
||||
return true |
||||
} |
||||
|
||||
/// Acquires and releases the lock around the provided closure. Returns without performing the closure if the lock |
||||
/// can not be acquired. |
||||
/// - Returns: nil if the lock could not be acquired. Otherwise, returns the returns the result of the provided |
||||
/// closure |
||||
@discardableResult |
||||
final func tryWithLock<T>(_ criticalSection: () throws -> T) rethrows -> T? { |
||||
guard tryLock() else { return nil } |
||||
defer { unlock() } |
||||
|
||||
return try criticalSection() |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue