Concurrency made simple in Swift.
AtomicKit is a Swift framework designed to ease dealing with concurrency in Swift projects.
Coming from Objective-C and C++, I lacked a few features in Swift, like Objective-C's @atomic
or C++'s std::atomic
.
Apple's GCD (Grand Central Dispatch) is absolutely awesome when it comes to synchronization, but you'll have to use it explicitely in Swift.
Writing atomic/dispatched getters and setters is not a big deal, but it currently leads to a lot of boilerplate code, which I like to avoid in my projects.
The AtomicKit project intends to simplify this, by adding easy-to-use synchronization Swift types.
Documentation and API reference can be found at: http://doc.xs-labs.com/AtomicKit/
AtomicKit exposes a few synchronization primitive types, like:
- Mutex
- RecursiveMutex
- UnfairLock
These types conforms to the AtomicKit.Lockable
protocol, which extends NSLocking
, meaning they all have the following methods:
public func lock()
public func unlock()
public func tryLock() -> Bool
This is a wrapper for pthread_mutex_t
.
This version is not recursive.
This is a wrapper for pthread_mutex_t
.
This version is recursive.
This is a wrapper for os_unfair_lock_t
.
AtomicKit provides a Semaphore
class.
Semaphores are initialized with a count, and may be named, in which case they are shared between multiple processes.
The Semaphore
class exposes the following methods:
public init( count: Int32 = 1, name: String? = nil )
public func wait()
public func signal()
public func tryWait() -> Bool
Internally, the Semaphore
class uses POSIX
semaphores for named semaphores, and MACH
semaphores for unnamed semaphores.
AtomicKit supports read-write locking mechanism using the RWLock
class:
public enum Intent
{
case reading
case writing
}
public func lock( for intent: Intent )
public func unlock()
public func tryLock( for intent: Intent ) -> Bool
AtomicKit provides a generic type called Atomic
.
If you're familiar with C++, you can think of it as std::atomic
.
The goal is to provide a thread-safe property type that you can use in your own classes:
class Foo
{
public var bar = Atomic< Bool >( value: false )
}
The example above declares an atomic Boolean
property.
Its value can be get with:
self.bar.get()
And can be set with:
self.bar.set( true )
Internally, Atomic
uses an UnfairLock
.
If more complex stuff is required with the value it holds, the Atomic
generic type lets you execute a custom closure.
let foo = Atomic< String >( value: "hello" )
foo.execute{ ( value: String ) in value.append( ", world" ) }
You can also use a return value from the closure:
let isEmpty = foo.execute{ ( value: String ) -> Bool in value.isEmpty }
Atomicity is guaranteed in both cases.
Under the hood, the Atomic
class inherits from LockingValue
.
LockingValue
works just the same, but takes an extra generic parameter specifying the type of lock used for synchronization.
As mentioned above, Atomic
uses an UnfairLock
.
Using LockingValue
, you can specify another type of locking mechanism, as long as it conforms to the NSLocking
protocol:
let foo = LockingValue< String?, NSRecursiveLock >( value: nil )
Using a locking mechanism is a way to achieve synchronization.
But using dispatch queues also ensure synchronization between multiple concurrent accesses.
AtomicKit supports this through the DispatchedValue
generic class.
A DispatchedValue
is initialized with a default value, and with a dispatch queue:
let foo = DispatchedValue< String >( value: "", queue: DispatchQueue.main )
Then, every call to the get
or set
methods will be executed on the provided queue, thus achieving effective synchronization.
As the Atomic
type, DispatchedValue
also lets you execute custom closures, that will be executed on the provided queue:
let foo = DispatchedValue< String >( value: "hello", queue: DispatchQueue.main )
foo.execute{ ( value: String ) in value.append( ", world" ) }
let isEmpty = foo.execute{ ( value: String ) -> Bool in value.isEmpty }
As it's a Swift generic type, the DispatchedValue
class cannot be used from Objective-C, and so can't be used with KVO (key-value observing).
This might be a problem, especially if your project relies on Cocoa bindings, which use KVO.
AtomicKit solves this by exposing Objective-C and KVO compatible versions of DispatchedValue
, specialized for common Objective-C types:
- DispatchedArrayController
- DispatchedBool
- DispatchedMutableArray
- DispatchedMutableDictionary
- DispatchedMutableSet
- DispatchedNumber
- DispatchedObject
- DispatchedString
- DispatchedTree
All of these types defaults to the main dispatch queue, if none is provided.
This is especially handy when using Cocoa bindings, as changes often needs to occur on the main thread, for UI reasons.
All of these specializations expose a value
property, which is observable using KVO.
It also means you can safely use Cocoa bindings with them.
AtomicKit is released under the terms of the MIT license.
Owner: Jean-David Gadina - XS-Labs
Web: www.xs-labs.com
Blog: www.noxeos.com
Twitter: @macmade
GitHub: github.com/macmade
LinkedIn: ch.linkedin.com/in/macmade/
StackOverflow: stackoverflow.com/users/182676/macmade