mirror of https://github.com/oxen-io/session-ios
Add missing dependency & fix various errors
parent
c672e306df
commit
6b8b934556
@ -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