QueueSafeValue
Framework that provides thread-safe (queue-safe) access to the value.
Advantages
-
Embedded
DispatchSemaphoreJust use specific access functions (
commands) of aQueueSafeValueand don't think about thread synchronization. -
Built-in
schedulerScheduler organises synchronous and asynchronous
commandsexecuting. -
Embedded
Comand Queue(Priority queue)Command Queueneeded to organize the sequence ofcommands. Allcommandswill be executed in order of priority, one after the other. -
Priority
commandexecutionAbility to prioritize updates or access to
QueueSafeValue. This means that somecommandswill run faster than others. -
Doesn't increment object reference count
-
Always returns a result and avoids returning optionals
always returns
Result<Value, QueueSafeValueError> -
Available different value manipulation commands
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 }
Documentation
Base structure of command
queueSafeValue.{schedule}.{priority}.{command}
Definitions:
๐จโโโโโ๐ดโโโโโ๐ฒโโโโโ๐ฒโโโโโ๐ฆโโโโโ๐ณโโโโโ๐ฉ ย ๐ถโโโโโ๐บโโโโโ๐ชโโโโโ๐บโโโโโ๐ชโโโโโ
- 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
Request components:
๐ธโโโโโ๐จโโโโโ๐ญโโโโโ๐ชโโโโโ๐ฉโโโโโ๐บโโโโโ๐ฑโโโโโ๐ชโโโโโ
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)
Available synchronous commands:
1. get value synchronously
- 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)
}
}2. get value synchronously inside the commandClosure
- 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)
}
}
}3. get value synchronously inside the commandClosure with manual completion
- 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.
}
}4. set value synchronously
- 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)
}
}5. set value synchronously inside the accessClosure
- 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)
}
}6. set value synchronously inside the accessClosure with manual completion
- 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)
}
}7. map value synchronously inside the commandClosure
- 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))
}
}Available asynchronous commands:
1. get value ssynchronously inside commandClosure
- 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)
}
}2. get value ssynchronously inside the commandClosure with manual completion
- 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.
}3. set value ssynchronously
- 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)
}
}4. set value ssynchronously inside the accessClosure
- 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)
}
}5. set value ssynchronously inside the accessClosure with manual completion
- 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)
}
}Requirements
- iOS 8.0+
- Xcode 10+
- Swift 5.1+
Installation
Step 1:
QueueSafeValue is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'QueueSafeValue'Step 2:
run pod install in your project root folder
Step 3:
To use the installed QueueSafeValue framework, simply import the QueueSafeValue in the swift file in which you are going to apply it.
Author
Vasily Bodnarchuk, https://www.linkedin.com/in/vasily-bodnarchuk/
License
QueueSafeValue is available under the MIT license. See the LICENSE file for more info.