Framework that provides thread-safe (queue-safe) access to the value.
-
Just use specific access functions (
commands) of aQueueSafeValueand don't think about thread synchronization. -
Scheduler organises synchronous and asynchronous
commandsexecuting. -
Command Queueneeded to organize the sequence ofcommands. Allcommandswill be executed in order of priority, one after the other. -
Ability to prioritize updates or access to
QueueSafeValue. This means that somecommandswill run faster than others. -
always returns
Result<Value, QueueSafeValueError> -
atomic command:
queueSafeValue.wait.lowestPriority.get()value processing command in a closure:
queueSafeValue.wait.lowestPriority.get { result in ... }value accessing command in a closure:
queueSafeValue.wait.lowestPriority.set { currentValue in currentVaule = newValue }
queueSafeValue.{schedule}.{priority}.{command}
🇨​​​​​🇴​​​​​🇲​​​​​🇲​​​​​🇦​​​​​🇳​​​​​🇩  🇶​​​​​🇺​​​​​🇪​​​​​🇺​​​​​🇪​​​​​
- stores
commandsand executes them sequentially with the correct priority QueueSafeValuehas a built-incommand queue(priority queue) where allclosures(commands) will be placed and perfomed after
🇨​​​​​🇱​​​​​🇴​​​​​🇸​​​​​🇺​​​​​🇷​​​​​🇪​​​​​​​​​🇸​
- is a closure inside which the value is accessed
- protected from concurrent access to
value(works ascritical section, implementation based onDispatchGroup)
Available command closures:
commandClosure- provides access to thevalueaccessClosure- provides direct access to the value (usinginoutkeyword)commandCompletionClosure- a closure that must always be performed (called) if available as a property inside thecommandClosureoraccessClosure. Executing the closure notifies thecommand queuethat thecommandhas completed. After that, thecommand queuewill unblock the access to the value and execute the nextcommand, if it exists.
Execution method:
completion commandClosure/accessClosure- closure that expects to work with serial code within itself.manualCompletion commandClosure/accessClosure- closure that expects to work with serial / asynchronous code within itself. This closure must be completed manually by calling theCommandCompletionClosure, placed as a property insidecommandClosureoraccessClosure
🇸​​​​​🇨​​​​​đź‡â€‹â€‹â€‹â€‹â€‹đź‡Şâ€‹â€‹â€‹â€‹â€‹đź‡©â€‹â€‹â€‹â€‹â€‹đź‡şâ€‹â€‹â€‹â€‹â€‹đź‡±â€‹â€‹â€‹â€‹â€‹đź‡Şâ€‹â€‹â€‹â€‹â€‹
describes will func be executed synchronously or asynchronously
Available schedules:
wait- (sync) performscommandssequentially. Blocks the queue where this code runs until it completedasync- performs acommandasynchronously of the queue that calls this function
🇵​​​​​🇷​​​​​🇮​​​​​🇴​​​​​🇷​​​​​🇮​​​​​🇹​​​​​🇾​​​​​
describes when (in what order)
commandwill be executed incommand queue
Available priorities:
lowestPriority- acommandwithlowest prioritywill be executed lasthighestPriority- acommandwithhighest prioritywill be executed first
🇨​​​​​🇴​​​​​🇲​​​​​🇲​​​​​🇦​​​​​🇳​​​​​🇩​​​​​
describes what to do with
value(provides access to thevalue)
- returns
CurrentValueorQueueSafeValueError - is used when only the return
valueis required (novalueprocessing)
func get() -> Result<CurrentValue, QueueSafeValueError>Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: true)
DispatchQueue.global(qos: .utility).async {
let result = queueSafeValue.wait.lowestPriority.get()
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: "a")
DispatchQueue.global(qos: .utility).async {
let result = queueSafeSyncedValue.lowestPriority.get()
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- returns
CurrentValueorQueueSafeValueErrorinsidecommandClosure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in thecommandClosure commandClosurewill be completed automatically
func get(completion commandClosure: ((Result<CurrentValue, QueueSafeValueError>) -> Void)?)Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: 6)
DispatchQueue.global(qos: .unspecified).async {
queueSafeValue.wait.lowestPriority.get { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: [1,2,3])
DispatchQueue.global(qos: .utility).async {
queueSafeSyncedValue.lowestPriority.get { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
}- returns
CurrentValueorQueueSafeValueErrorandCommandCompletionClosureinside thecommandClosure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in thecommandClosure - important:
commandClosuremust be completed manually by performing (calling)CommandCompletionClosure
func get(manualCompletion commandClosure: ((Result<CurrentValue, QueueSafeValueError>,
@escaping CommandCompletionClosure) -> Void)?) Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: 4.44)
DispatchQueue.global(qos: .unspecified).async {
queueSafeValue.wait.highestPriority.get { result, done in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: 4.45)
DispatchQueue.global(qos: .utility).async {
queueSafeSyncedValue.highestPriority.get { result, done in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
}- returns
UpdatedValueorQueueSafeValueError - is used when only the set of
valueis required (novalueprocessing)
@discardableResult
func set(newValue: Value) -> Result<UpdatedValue, QueueSafeValueError>Code sample
// Option 1
let queueSafeValue = QueueSafeValue<Int>(value: 1)
DispatchQueue.global(qos: .userInitiated).async {
let result = queueSafeValue.wait.lowestPriority.set(newValue: 2)
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: "b")
DispatchQueue.global(qos: .userInitiated).async {
let result = queueSafeSyncedValue.lowestPriority.set(newValue: "b1")
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- sets
CurrentValueinside theaccessClosure - is used when it is necessary to both read and write a
valueinside one closure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in theaccessClosure - Attention:
accessClosurewill not be run if anyQueueSafeValueErroroccurs
@discardableResult
func set(completion accessClosure: ((inout CurrentValue) -> Void)?) -> Result<UpdatedValue, QueueSafeValueError>Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: 1)
DispatchQueue.main.async {
let result = queueSafeValue.wait.lowestPriority.set { $0 = 3 }
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: ["a":1])
DispatchQueue.main.async {
let result = queueSafeSyncedValue.lowestPriority.set { $0["b"] = 2 }
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- sets
CurrentValueinside theaccessClosure - is used when it is necessary to both read and write a
valueinside one closure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in theaccessClosure - important:
accessClosuremust be completed manually by performing (calling)CommandCompletionClosure - Attention:
accessClosurewill not be run if anyQueueSafeValueErroroccurs.
@discardableResult
func set(manualCompletion accessClosure: ((inout CurrentValue,
@escaping CommandCompletionClosure) -> Void)?) -> Result<UpdatedValue, QueueSafeValueError>Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: "value 1")
DispatchQueue.main.async {
let result = queueSafeValue.wait.lowestPriority.set { currentValue, done in
currentValue = "value 2"
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: "value a")
DispatchQueue.main.async {
let result = queueSafeSyncedValue.lowestPriority.set { currentValue, done in
currentValue = "value b"
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- maps (transforms)
CurrentValuetoMappedValueinside thecommandClosure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in thecommandClosure
func map<MappedValue>(completion commandClosure: ((CurrentValue) -> MappedValue)?) -> Result<MappedValue, QueueSafeValueError>Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: 5)
DispatchQueue.global(qos: .background).async {
let result = queueSafeValue.wait.lowestPriority.map { "\($0)" }
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeSyncedValue = QueueSafeSyncedValue(value: "1")
DispatchQueue.global(qos: .background).async {
let result = queueSafeSyncedValue.lowestPriority.map { Int($0) }
switch result {
case .failure(let error): print(error)
case .success(let value): print(String(describing: value))
}
}- returns
CurrentValueorQueueSafeValueErrorinside thecommandClosure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in thecommandClosure commandClosurewill be completed automatically
func get(completion commandClosure: ((Result<CurrentValue, QueueSafeValueError>) -> Void)?)Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: true)
queueSafeValue.async(performIn: .global(qos: .utility)).highestPriority.get { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeAsyncedValue = QueueSafeAsyncedValue(value: true, queue: .global(qos: .utility))
queueSafeAsyncedValue.highestPriority.get { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- returns
CurrentValueorQueueSafeValueErrorandCommandCompletionClosureinside thecommandClosure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in thecommandClosure - important:
commandClosuremust be completed manually by performing (calling)CommandCompletionClosure
func get(manualCompletion commandClosure: ((Result<CurrentValue, QueueSafeValueError>,
@escaping CommandCompletionClosure) -> Void)?)Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: "test")
queueSafeValue.async(performIn: .global(qos: .utility)).highestPriority.get { result, done in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
// Option 2
let queueSafeAsyncedValue = QueueSafeAsyncedValue(value: "super test", queue: .global(qos: .background))
queueSafeAsyncedValue.highestPriority.get { result, done in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
done() // Must always be executed (called). Can be called in another DispatchQueue.
}- returns
UpdatedValueorQueueSafeValueErrorinside thecommandClosure - is used when only the set of
valueis required (novalueprocessing)
func set(newValue: Value, completion commandClosure: ((Result<UpdatedValue, QueueSafeValueError>) -> Void)? = nil)Code sample
// Option 1
let queueSafeValue = QueueSafeValue(value: 7)
// Without completion block
queueSafeValue.async(performIn: .main).highestPriority.set(newValue: 8)
// With completion block
queueSafeValue.async(performIn: .main).highestPriority.set(newValue: 9) { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2
let queueSafeAsyncedValue = QueueSafeAsyncedValue(value: 7, queue: .global())
// Without completion block
queueSafeAsyncedValue.highestPriority.set(newValue: 8)
// With completion block
queueSafeAsyncedValue.highestPriority.set(newValue: 9) { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- sets
CurrentValueinside theaccessClosure - is used when it is necessary to both read and write a
valueinside one closure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in theaccessClosure - Attention:
accessClosurewill not be run if anyQueueSafeValueErroroccurs
func set(accessClosure: ((inout CurrentValue) -> Void)?,
completion commandClosure: ((Result<UpdatedValue, QueueSafeValueError>) -> Void)? = nil)Code sample
// Option 1.
let queueSafeValue = QueueSafeValue(value: 1)
// Without completion block
queueSafeValue.async(performIn: .background).highestPriority.set { $0 = 10 }
// With completion block
queueSafeValue.async(performIn: .background).highestPriority.set { currentValue in
currentValue = 11
} completion: { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2.
let queueSafeAsyncedValue = QueueSafeAsyncedValue(value: 1, queue: .global(qos: .userInteractive))
// Without completion block
queueSafeAsyncedValue.highestPriority.set { $0 = 10 }
// With completion block
queueSafeAsyncedValue.highestPriority.set { currentValue in
currentValue = 11
} completion: { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- sets
CurrentValueinside theaccessClosure - is used when it is necessary to both read and write a
valueinside one closure - is used as a
critical sectionwhen it is necessary to hold reading / writing of thevaluewhile it is processed in theaccessClosure - important:
accessClosuremust be completed manually by performing (calling)CommandCompletionClosure - Attention:
accessClosurewill not be run if anyQueueSafeValueErroroccurs.
func set(manualCompletion accessClosure: ((inout CurrentValue, @escaping CommandCompletionClosure) -> Void)?,
completion commandClosure: ((Result<UpdatedValue, QueueSafeValueError>) -> Void)? = nil) Code sample
// Option 1.
let queueSafeValue = QueueSafeValue(value: 999.1)
// Without completion block
queueSafeValue.async(performIn: .background).highestPriority.set { currentValue, done in
currentValue = 999.2
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
// With completion block
queueSafeValue.async(performIn: .background).highestPriority.set { currentValue, done in
currentValue = 999.3
done() // Must always be executed (called). Can be called in another DispatchQueue.
} completion: { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}
// Option 2.
let queueSafeAsyncedValue = QueueSafeAsyncedValue(value: 1000.1, queue: .global(qos: .userInteractive))
// Without completion block
queueSafeAsyncedValue.highestPriority.set { currentValue, done in
currentValue = 1000.2
done() // Must always be executed (called). Can be called in another DispatchQueue.
}
// With completion block
queueSafeAsyncedValue.highestPriority.set { currentValue, done in
currentValue = 1000.3
done() // Must always be executed (called). Can be called in another DispatchQueue.
} completion: { result in
switch result {
case .failure(let error): print(error)
case .success(let value): print(value)
}
}- iOS 8.0+
- Xcode 10+
- Swift 5.1+
QueueSafeValue is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'QueueSafeValue'run pod install in your project root folder
To use the installed QueueSafeValue framework, simply import the QueueSafeValue in the swift file in which you are going to apply it.
Vasily Bodnarchuk, https://www.linkedin.com/in/vasily-bodnarchuk/
QueueSafeValue is available under the MIT license. See the LICENSE file for more info.