Framework that provides thread-safe (queue-safe) access to the value.
-
Just use specific access functions (
commands
) of aQueueSafeValue
and don't think about thread synchronization. -
Scheduler organises synchronous and asynchronous
commands
executing. -
Command Queue
needed to organize the sequence ofcommands
. Allcommands
will be executed in order of priority, one after the other. -
Ability to prioritize updates or access to
QueueSafeValue
. This means that somecommands
will 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
commands
and executes them sequentially with the correct priority QueueSafeValue
has 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 thevalue
accessClosure
- provides direct access to the value (usinginout
keyword)commandCompletionClosure
- a closure that must always be performed (called) if available as a property inside thecommandClosure
oraccessClosure
. Executing the closure notifies thecommand queue
that thecommand
has completed. After that, thecommand queue
will 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 insidecommandClosure
oraccessClosure
describes will func be executed synchronously or asynchronously
Available schedules:
wait
- (sync) performscommands
sequentially. Blocks the queue where this code runs until it completedasync
- performs acommand
asynchronously of the queue that calls this function
describes when (in what order)
command
will be executed incommand queue
Available priorities:
lowestPriority
- acommand
withlowest priority
will be executed lasthighestPriority
- acommand
withhighest priority
will be executed first
describes what to do with
value
(provides access to thevalue
)
- returns
CurrentValue
orQueueSafeValueError
- is used when only the return
value
is required (novalue
processing)
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
CurrentValue
orQueueSafeValueError
insidecommandClosure
- is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in thecommandClosure
commandClosure
will 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
CurrentValue
orQueueSafeValueError
andCommandCompletionClosure
inside thecommandClosure
- is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in thecommandClosure
- important:
commandClosure
must 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
UpdatedValue
orQueueSafeValueError
- is used when only the set of
value
is required (novalue
processing)
@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
CurrentValue
inside theaccessClosure
- is used when it is necessary to both read and write a
value
inside one closure - is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in theaccessClosure
- Attention:
accessClosure
will not be run if anyQueueSafeValueError
occurs
@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
CurrentValue
inside theaccessClosure
- is used when it is necessary to both read and write a
value
inside one closure - is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in theaccessClosure
- important:
accessClosure
must be completed manually by performing (calling)CommandCompletionClosure
- Attention:
accessClosure
will not be run if anyQueueSafeValueError
occurs.
@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)
CurrentValue
toMappedValue
inside thecommandClosure
- is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while 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
CurrentValue
orQueueSafeValueError
inside thecommandClosure
- is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in thecommandClosure
commandClosure
will 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
CurrentValue
orQueueSafeValueError
andCommandCompletionClosure
inside thecommandClosure
- is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in thecommandClosure
- important:
commandClosure
must 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
UpdatedValue
orQueueSafeValueError
inside thecommandClosure
- is used when only the set of
value
is required (novalue
processing)
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
CurrentValue
inside theaccessClosure
- is used when it is necessary to both read and write a
value
inside one closure - is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in theaccessClosure
- Attention:
accessClosure
will not be run if anyQueueSafeValueError
occurs
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
CurrentValue
inside theaccessClosure
- is used when it is necessary to both read and write a
value
inside one closure - is used as a
critical section
when it is necessary to hold reading / writing of thevalue
while it is processed in theaccessClosure
- important:
accessClosure
must be completed manually by performing (calling)CommandCompletionClosure
- Attention:
accessClosure
will not be run if anyQueueSafeValueError
occurs.
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.