QueueSafeValue
Framework that provides thread-safe (queue-safe) access to the value.
Advantages
-
EmbeddedDispatchSemaphore
Just use specific access functions (
commands
) of aQueueSafeValue
and don't think about thread synchronization. -
Built-inscheduler
Scheduler organises synchronous and asynchronous
commands
executing. -
EmbeddedComand Queue
(Priority queue
)Command Queue
needed to organize the sequence ofcommands
. Allcommands
will be executed in order of priority, one after the other. -
Prioritycommand
executionAbility to prioritize updates or access to
QueueSafeValue
. This means that somecommands
will 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
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
Request components:
๐ธโโโโโ๐จโโโโโ๐ญโโโโโ๐ชโโโโโ๐ฉโโโโโ๐บโโโโโ๐ฑโโโโโ๐ชโโโโโ
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
)
Available synchronous commands:
get
value synchronously
1. - 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)
}
}
get
value synchronously inside the commandClosure
2. - 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)
}
}
}
get
value synchronously inside the commandClosure
with manual completion
3. - 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.
}
}
set
value synchronously
4. - 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)
}
}
set
value synchronously inside the accessClosure
5. - 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)
}
}
set
value synchronously inside the accessClosure
with manual completion
6. - 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)
}
}
map
value synchronously inside the commandClosure
7. - 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))
}
}
Available asynchronous commands:
get
value ssynchronously inside commandClosure
1. - 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)
}
}
get
value ssynchronously inside the commandClosure
with manual completion
2. - 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.
}
set
value ssynchronously
3. - 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)
}
}
set
value ssynchronously inside the accessClosure
4. - 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)
}
}
set
value ssynchronously inside the accessClosure
with manual completion
5. - 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)
}
}
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.