Photo by Chu Son on Unsplash

Unsafe Swift: Understanding, usage, best practices and how to

Without any doubts Swift is one of the best safe programming language among many programming languages. The safety comes from powerful constructs like strong typing, type checking at compile time, optionals and few others.

A small amount of Swift Standard Library provides a collection of unsafe API’s. Before going into unsafe world let’s first understand what does a safe code means.

Safe Operations

A piece of code is safe when its behaviour is fully defined covering all cases of inputs. Let’s use the force unwrap of optionals as a trivial example to define what a safe code is

let first: Int? = 43
print(first!) // Prints 43

let second: Int? = nil
print(second!) // Fatal error: Unexpectedly found nil while unwrapping an Optional value

In the first case the optional has a value and it executes correctly. In second case optional is nil and we get a runtime fatal error and code execution stops. This is a defined behaviour of optionals. This covers all the cases of input. In graphical form

Behaviour of safe force-unwrap of optional

Indeed force-unwrapping of optional is a severe programming error, but if we use it we have a well defined behaviour(i.e that our programme will crash).

Unsafe Operations

An unsafe operation is such that it will have undefined behaviour for at least one or more inputs that does not meet expectation of its documented input.

Optional also provides an unsafe force unwrapping operation. Similar to safe force-unwrap operation, unsafe operation required that underlying value to be non-nil. But if code is compiled using optimizations enabled, the unsafe operation does not verify the non-nil requirement, it proceeds with assumption that it contains a non-nil value. Let’s use optional from our last example to see an unsafe behaviour of force unwrap of optionals

let first: Int? = 43
print(first.unsafelyUnwrapped) // Prints 43

let second: Int?  = nil
print(second.unsafelyUnwrapped) // Undefined behaviour

In above example for first case the code execute nicely. While for second case we cannot reason what will happen when we are using an unsafe operation. There are many possibilities

Behaviour of unsafe force-unwrap of optional

It is important to note that there are many possibilities if we break the contract of an unsafe operation. In above example of unsafe force unwrap of optionals; if there is no value, it might give some garbage value back or we get a crash or something else. Also the behaviour may not be same when same code is run again.

The unsafe API does not verifies its requirements and assume that developer has full responsibility of its input i.e to have non-nil value for above example. However we got a helping hand from build tools here. In optimized build the unsafe force unwrap does not performs any non-nil value validation while it does performs validation in unoptimized debug builds.

In Swift standard library the unsafe API’s are prefixed with unsafe naming convention. This prefix will act as precaution to anyone reading or writing the code.

Another example: Unsafe Pointers

Let see another example of unsafe pointers.

let pointer = UnsafeMutablePointer<Int>.allocate(capacity: 1) // allocate the memory pointed by the pointer
pointer.initialize(to: 43) // Initialize the memory with some value
print(pointer.pointee) // Use the value
pointer.deallocate() // Deallocate the memory pointed by pointer. After this point the pointer becomes a dangling pointer
pointer.pointee // Any attempt to access the memory after deallocation then it will result in an undefined behaviour

It is important to note that pointer does not know anything about when it is allocated or deallocated. This is the responsibility of programmer to manage it. If we attempt to access the memory after deallocation then it will result in an undefined behaviour. Xcode provides us with a runtime debugging tool called Address Senitizer to help identify and fix this type of memory issues.

Benefits and usage of Unsafe API’s

The unsafe API’s can used to build code that is still reliable. There are mainly two main usage categories of unsafe API’s.

  1. Interoperability with C and Objective-C
  2. Fine grain control of performance at run time.

Unsafe Swift API’s are used for interoperability with other unsafe programming languages such as C or Objective-C. Lets consider a below C function that processes an int buffer in an array

void foo(const int* start, size_t count)

When this function is imported to Swift it will be in form

func foo(_ start: UnsafePointer<Int32>!, _ count: Int)

Note that the first const int pointer parameter is translated as unsafe implicitly unwrapped optional pointer type.

One of the way to call this C function from within Swift is as below

//1
let array = UnsafeMutablePointer<Int32>.allocate(capacity: 5)

//2
array.initialize(to: 23)
(array + 1).initialize(to: 54)
(array + 2).initialize(to: -43)
(array + 3).initialize(to: 0)
(array + 4).initialize(to: 13)

//3
foo(array, 5)

//4
array.deinitialize(count: 5)

//5
array.deallocate()

Below is discussion about each step and what is technically unsafe for each step.

  1. Use static allocate method of UnsafeMutablePointer to create a dynamic buffer to hold the integer values. Unsafe: Note that the lifetime of allocated buffer is not managed by the return pointer. We have to manage it ourself and manually deallocate it at some time otherwise it will stay in memory forever causing memory leak.
  2. Use pointer arthimetic to initialize array with some valid values. Unsafe: Initialization cannot automatically verify that calculated address is within memory range of buffer allocated. If we go out of range we will get an undefined behaviour.
  3. Unsafe: We are assuming that function is not going to take ownership of allocated buffer. This is by seeing the documentation of function or by looking into the implementation of function. The function will only access the buffer during its execution and does not hold on to memory or try to deallocate it .
  4. Unsafe: Deinitialization will only work if it was previously properly initialized with correct data type.
  5. Unsafe: We only deallocate a memory if it was previously allocated and is in deinitialized state.

At each step there are some unchecked assumptions and if we get any one of them wrong we will get an undefined behaviour.

There are two more issues with the above code

  1. The buffer is managed by its start address.
  2. Its length or size is in a separate value and that is scattered all over the code. In above case at least in three separate places.

The quality of code is greatly increased if we can somehow combine start address and size together. To address this Swift standard library provides below four pointer types.

  1. UnsafeBufferPointer<Element>
  2. UnsafeMutableBufferPointer<Element>
  3. UnsafeRawBufferPointer
  4. UnsafeMutableRawBufferPointer

These four buffer pointers are very helpful whenever we need to access the region of memory instead of pointer to individual elements. By combining region and size together these pointers will add some level of safety but developers are still in charge of managing the other aspects of safety. For unoptimized debug build these pointers check for out of bound access for subscript operations; thus having some safety.

Easy access to unsafe API’s in Swift Standard Collection

Some of the safety issues discussed in above example can be simplified by API’s provided by Swift standard collection library. These collection API’s provide an easy way to access their underlaying storage through simple unsafe methods.

Sequence.withContiguousStorageIfAvailable(_:)
MutableCollection.withContiguousMutableStorageIfAvailable(_:)

String.withCString(_:)
String.withUTF8(_:)

Array.withUnsafeBufferPointer(_:)
Array.withUnsafeMutableBufferPointer(_:)
Array.withUnsafeBytes(_:)
Array.withUnsafeMutableBytes(_:)

We can use above and few other methods to simplify our code and limit ourself to smallest number of unsafe code.

Taking our previous example and using the Swift Standard collection unsafe API’s, below will be new code

let array: [Int32] = [23, 54, -43, 0, 13]

array.withUnsafeBufferPointer { buffer in
    foo(buffer.baseAddress!, buffer.count) // The C function call
}

Notice how nicely we have avoided some of the unsafe operations and used only minimum needs unsafe calls. The unsafe operations that we have avoided are

  1. Unsafe static allocation
  2. Unsafe pointer arithematic to initialize
  3. Unsafe deinitialization
  4. Unsafe deallocation

We have only make an unsafe call to C function foo.

There is such a greater need to pass pointers to C functions such that Swift has special syntax to for it. We can simply pass array expecting an unsafe pointer and compiler will automatically generate the code equivalent for withUnsafeBufferPointer.

let array: [Int32] = [23, 54, -43, 0, 13]

foo(array, array.count) // The compiler will generate the code equivalent of withUnsafeBufferPointer 

An important point to remember is that the C function call is still unsafe.

Thank you for your time. If you liked the article please consider sharing it.

Photo by Fahrul Razi on Unsplash

Logging in Swift: exploring various options

One of the important aspects of development is to fix bugs that come along. Surely the use of debugger is the best option to debug and fix the bugs but it is inevitable to use some kind of console output to see the program behaviour.

During debugging apps, it is useful to have events in their natural sequence along with the data associated with event. Using these events and associated data we can recreate the sequence and reason about where the problem occurred. This type of debugging is required when it is not possible to attach debugger e.g when an issue occurred on user device in past time.

Swift being the focus of app development on Apple platforms offers various API’s for logging. We will explore these in below sections.

NSLog()

People from Objective-C era remember the use of NSLog(). It was the most common logging function available when programming using Objective-C language. Its usage is very simple, from documentation it is declared as

void NSLog(NSString *format, ...);

The arguments are an instance NSString object along with format options variable. When used in swift, it is imported as

public func NSLog(_ format: String, _ args: CVarArg...)

A simple function call such as NSLog(@"Hello %@", @"world"); would produce an output like

2020-07-04 17:44:26.571573+0500 TestApp[26137:159243] Hello world

It’s out put is formatted and shows date along with current time stamp in system timezone.

NSLog is a handy solution but it has a downside of performance issues as it runs synchronously. Meaning it blocks the calling thread until NSLog returns. So too much calling it on main thread causes significant app lag. Although using NSLog during development has benefit, but when shipping the app and in release mode the excessive usage of NSLog will have some performance issues. In order to minimze the performance impact of NSLog we can use preprocessor directives to use it only in debug mode

#if DEBUG
#define Log(...) NSLog(__VA_ARGS__)
#else
#define Log(...)
#endif

Now our log usage will become

Log(@"Hello world");

Another aspect of NSLog is that it only works when device is connected to machine.

print() / debugPrint()

From swift documentation the print function Writes the textual representation of given items into the standard output. The print function is declared as

func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

We use print by calling it with items need to be printed.

print(1, 2, 3, 4, 5)
// The output is 
1 2 3 4 5

The default separator is a space and default terminator is new line. We can easily substitute the separator and terminator parameters.

print(1, 2, 3, 4, 5, separator: ",")
// The output is
1,2,3,4,5

The debugPrint behaves similar to print but is most suitable for showing the debugging info to standard output. It is declared in Swift standard library as

func debugPrint(_ items: Any..., separator: String = " ", terminator: String = "\n")

One thing to note is that debugPrint, contrary to its name has no relation with debug or release build.

We can use print and debugPrint with custom objects as well. print uses description from CustomStringConvertible protocol and debugPrint uses debugDescription from CustomDebugStringConvertible respectively.

struct Person {
    let name: String
    let height: Int // In centimetre
}
extension Person: CustomStringConvertible {
    var description: String {
        "\(name)" + "\n" + "\(height)"
    }
}
extension Person: CustomDebugStringConvertible {
    var debugDescription: String {
        "Name:\(name)" + "\n" + "Height:\(height)"
    }
}
let person = Person(name: "Khurram", height: 173)
print(person)
// output is
Khurram
173

debugPrint(person)
// output is
Name:Khurram
Height:173

Just like NSLog, print and debugPrint are only available when device is connected to computer.

OSLog

OSLog is a unified logging system and is available starting from iOS 10.0, macOS 10.12, watchOS 3.0, tvOS 10.0. It supersedes ASL(Apple Logging System) and SysLog API’s. The unified logging system stores the log messages in memory and in data store. The data store is not a text based log files but an optimized date store for persisting the log messages.

In its simplest form we can use it as

os_log("Hello, world")

It is important to note that os_log function expects a string of type StaticString and not standard Swift String type. A StaticString is a type that is designed to represent text that is know at compile time. That means we cannot do string interpolation like

os_log(.info, log: .network, "New used logged in with name \(user.name)") // Error

Instead we can work around this problem as below

let message = "New used logged in with name \(user.name)"
os_log(.info, log: .network, "%@", message)

However the logging can be customized in many ways that will help in identifying the bugs correctly. Below are some concepts related to the unified logging system.

Subsystem

Create unique subsystem for each component of the app. A subsystem is a logical component of app e.g networking component and data persistence component are perfect candidates for a subsystems. We can create a subsystem matching bundle identifier of target.

import os.log

extension OSLog {
    private static var subsystem = Bundle.main.bundleIdentifier!
}

Category

If we need more granularity we can define categories within each subsystem. Categories will help in filtering the messages.

import os.log

extension OSLog {
    private static var subsystem = Bundle.main.bundleIdentifier!
    static let network = OSLog(subsystem: subsystem, category: "network")
}

Log Levels

We log messages for interesting events occurring in app using a suitable log level. A log level dictates the condition under which the messages should be logged. It is the indication of significance of a message. Below are the log level supported by API.

  • default: This is the default log level when we do not define any log level during logging a message. It is better to use and specify a log level explicitly.
  • info: Use this level when the information is helpful but is not required during debugging process.
  • debug: Use this level in active development phase while debugging.
  • error: Used when program encounters an error state such as when network load request failed.
  • fault: Used in conditions when program enters a state which prohibit it to perform any use full task i.e a system level failure.

Parameters

We can specify different parameters during logging. These parameters have two different privacy levels.

  • private: A private parameter is logged using %{private}@ specifier.
  • public: A public parameter is logged using %{public}@ specifier.

By default the privacy level for dynamic string is private.

os_log("Processing user: %{public}@", log: OSLog.network, type: .info, user.name)
os_log("Processing user: %{private}@", log: OSLog.network, type: .info, user.name)
os_log("Processing user: %@", log: OSLog.network, type: .info, user.name)

Both xcode and console app will show output when debugger is attached

2020-07-06 17:44:00.829304+0500 TestApp[715:354321] [network] Processing user: Khurram
2020-07-06 17:44:00.829425+0500 TestApp[715:354321] [network] Processing user: Khurram
2020-07-06 17:44:00.829472+0500 TestApp[715:354321] [network] Processing user: Khurram

However if debugger is not attached the console app will not show the private data.

17:44:07.565336+0500 TestApp Processing user: Khurram
17:44:07.565531+0500 TestApp Processing user: <private>
17:44:07.565557+0500 TestApp Processing user: <private>

SwiftLog

SwiftLog is an open source logging API package for Swift. This is a cross platform API that will work on Apple platforms as well as non-apple platforms(e.g Linux). To use it add

https://github.com/apple/swift-log.git

as dependency in your Swift Package Manager. SwiftLog is a front end for Swift logging API and will pick up required backend automatically depending on platform it is running on. It has also support for different log levels. Its usage is very simple.

let logger = Logger(label: Bundle.main.bundleIdentifier!)
logger.info("An important event")
logger.error("User not authenticated")
logger.critical("Something extra ordinary occurred")
// the output is
2020-07-06T18:19:22+0500 info: An important event
2020-07-06T18:19:22+0500 error: User not authenticated
2020-07-06T18:19:22+0500 critical: Something extra ordinary occurred

Logger

Logger is a new logging API from Apple and is available on platforms starting from iOS 14.0, macOS 10.16, watchOS 7.0 and tvOS 14.0 and later versions. It was revealed in WWDC 2020. Its usage semantics are similar to OSLog.

let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
logger.log("A new event")
logger.info("Another event")
logger.debug("Making network call")

It has also excellent support for categories, log levels and privacy.

logger.log("New user logged in with name \(user.name, privacy: .public)")

Conclusion

  • Do not use NSLog due to its performance overhead.
  • Use print and debugPrint very occasionally.
  • Always use OSLog when developing apps, as it comes with benefits like customisation(subsystems, categories, log levels and privacy)
  • If targeting iOS 14.0, macOS 10.16, watch OS 7.0 and tvOS 14.0 use newer Logger API.
  • If developing a cross-platform product use SwiftLog.

Important Links

  • NSLog https://developer.apple.com/documentation/foundation/1395275-nslog
  • print https://developer.apple.com/documentation/swift/1541053-print
  • debugPrint https://developer.apple.com/documentation/swift/1539920-debugprint
  • OSLog https://developer.apple.com/documentation/os/logging
  • SwiftLog https://github.com/apple/swift-log

If you liked the article please consider sharing by using the share button below.

Please subscribe to mailing list to get latest updates immediately.

How to use Swift literals with custom types

Literals are combination of raw letters, characters and numbers that enable types to be constructed easily, below is a simple example of some common literals

let myString = "Hello World"
let myNumber = 34
let myDouble = 28.75

In above code example we have created three different variables using string, integer and double literals. In almost all programming languages there is support for literals and swift is of no difference. Swift even goes one step ahead, through use of its protocol oriented approach which allows any type to be able to initialise from literals.

Swift provides a set of protocols to work with literals as part of standard library here. All literal types from swift standard library can be divided into three different categories. Collection literals(arrays and dictionaries), value literals (int, float, bool and nil) and string literals.

Let’s consider a few examples how we can use swift initialisation with literals protocols to work with any custom type.

String Literals

struct Person {
    let firstName: String
    let lastName: String
}

Without using literal, an instance of above structure can be created as below

let person = Person(firstName: "Khurram", lastName: "Shehzad")

In order for our custom type to be able to initialize with literal we have to adopt to the literal protocol from which we want our custom type to be initialized from. For Person struct we will use ExpressibleByStringLiteral protocol to make it work with string literals.

struct Person: ExpressibleByStringLiteral {
    typealias StringLiteralType = String
    init(stringLiteral value: StringLiteralType) {
        let components = value.components(separatedBy: .whitespaces)
        firstName = components[0]
        lastName = components[1]
    }
    let firstName: String
    let lastName: String
}

Now we can easily create an instance of Person using string literal

let person: Person = "Khurram Shehzad"

Please note that we have explicitly mentioned the type. If we do not mention the type the swift compiler will use default type. For ExpressibleByStringLiteral the default type is String. Other protocols in for String literals are

Value Literals

Similarly we can have our custom types adopt to value literal as well. In the next example we have our custom type Temperature that is initialized from a Int value literal namely ExpressibleByIntegerLiteral

struct Temperature: ExpressibleByIntegerLiteral {
    typealias IntegerLiteralType = Int
    init(integerLiteral value: Int) {
        t = value
    }   
    let t: Int
}

let waterFreezing: Temperature = 0

Swift type inference system can work with literals when used with custom data types, as below

func waterBoilingTemperature() -> Temperature {
    return 100
}
let waterBoiling = waterBoilingTemperature() 

Note that swift type inference system will work here, as type is clearly evident from return type of function and we do not have a need to mention type explicitly at calling side.

Other protocols in value primitive literals are

Collection Literals

Swift standard library has also support for array and dictionary literals in ExpressibleByArrayLiteral and ExpressibleByDictionaryLiteral protocols respectively. Below is a simple example to use array literal.

class LinkedList: ExpressibleByArrayLiteral {
    var first: Node?
    class Node {
        var value: Int?
        var next: Node?
    }
    typealias ArrayLiteralElement = Int
    required init(arrayLiteral elements: Int...) {
        var current: Node?
        for element in elements {
            if first == nil {
                first = Node()
                first?.value = element
                current = first
            } else {
                let node = Node()
                node.value = element
                current?.next = node
                current = node
            }
        }
    }
}

let linkedList: LinkedList = [1, 2, 3, 4, 5]

Here we are initialising our custom linked list from swift array literal.

Similarly we can use ExpressibleByDictionaryLiteral for initialising from dictionary literal as well. Below is an example of utilising dictionary literal in custom type.

struct SolarSystem {
    struct Planet {
        let name: String
        let moonsCount: Int
    }
    let planets: [Planet]
}

extension SolarSystem: ExpressibleByDictionaryLiteral {
    typealias Key = String
    typealias Value = Int
    init(dictionaryLiteral elements: (String, Int)...) {
        var planets = [Planet]()
        for element in elements {
            planets.append(Planet(name: element.0, 
                                  moonsCount: element.1))
        }
        self.planets = planets
    }
}

let ourSolarSystem: SolarSystem = [
   "Mercury": 0, 
   "Venus": 0, 
   "Earth": 1, 
   "Mars": 2, 
   "Jupiter": 79, 
   "Saturn": 82, 
   "Uranus": 27, 
   "Neptune": 14]

For a complete list of available literals that you can use for you own custom types please see documentation here.

All the above code examples can be found here.

If you liked the article please consider sharing by using the share button below. Also please subscribe to mailing list to get latest updates immediately.

Atomic Types in Swift

In today modern apps multithreading is a de-facto standard for achieving concurrency. Multithreading comes with its own set of complexities i.e data race, dead lock, live lock etc. Apple provides various technologies for achieving multithreading on its platforms(macOS, iOS, iPadOS, watchOS and tvOS). These technologies include classic pthread, Thread(NSThread), NSOperationQueue and DispatchQueue.

One of the important aspect of multi threading is atomic types. Atomic types are language primitives that provide access to value with guarantee that no data race conditions arise.

Popular programming languages like C++ and Java have well established support for atomic types. C++ has std::atomic as part of standard library and Java has Atomic Variables as part of java.util.concurrent.atomic package.

Unfortunately as of writing there is no language support for atomic types in Swift. To demonstrate how an atomic type can be implemented in Swift we will start with a simple use case

// A protocol to be used in our atomic type
protocol Operatable {
    func add(other: Self) -> Self
}

final class Atomic<T: Operatable> {
    
    private var t: T
    
    init(_ t: T) {
        self.t = t
    }
    
    func incrementAndGet(by other: T) -> T {
        // Critical section starts
        // The change to t should be atomic here
        let result = t.add(other: other)
        t = result
        // Critical section ends
        return result
    }
}

In the above code snippet the implementation of incrementAndGet method is not thread safe. Below is our test code

// We will only use Int for our demo but it can be made to work with any other type
extension Int: Operatable {
    func add(other: Int) -> Int {
        return self + other
    }
}

func test() {
    let atomicInt = Atomic<Int>(0)
    for i in 1...5 {
        let thread = Thread {
            print("\(Thread.current.name!):Value:\(atomicInt.incrementAndGet(by: 1))")
        }
        thread.name = "Thread: \(i)"
        thread.start()
    }
    Thread.sleep(forTimeInterval: 2)
}

Below is one of the possible output of above test code

Thread: 4: critical section started
Thread: 5: critical section started
Thread: 2: critical section started
Thread: 3: critical section started
Thread: 4: critical section ended
Thread: 5: critical section ended
Thread: 2: critical section ended
Thread: 1: critical section started
Thread: 3: critical section ended
Thread: 1: critical section ended

The is exactly the behaviour we do not want. What we want is at any given time there will at most one thread in critical section, thus it will make our type atomic. In below sections we will discuss various approaches to make the Atomic class truly atomic.

Using NSLock

We can achieve atomicity using NSLock from Foundation framework. Below is a simple implementation of Atomic class using NSLock.

final class NSAtomic<T: Operatable> {
    
    private var t: T
    private let lock = NSLock()
    init(_ t: T) {
        self.t = t
    }
    
    func incrementAndGet(by other: T) -> T {
        // Critical section starts
        // The change to t should be atomic here
        lock.lock()
        print("\(Thread.current.name!): critical section started")
        let result = t.add(other: other)
        t = result
        print("\(Thread.current.name!): critical section ended")
        lock.unlock()
        // Critical section ends
        return result
    }
}

func testNSAtomic() {
    print("test with NSLock")
    let atomicInt = NSAtomic<Int>(0)
    for i in 1...5 {
        let thread = Thread {
            _ = atomicInt.incrementAndGet(by: 1)
        }
        thread.name = "Thread: \(i)"
        thread.start()
    }
    Thread.sleep(forTimeInterval: 2)
}

We have just added a NSLock instance variable to our atomic type. Below is the output of using our NSAtomic class.

Thread: 1: critical section started
Thread: 1: critical section ended
Thread: 2: critical section started
Thread: 2: critical section ended
Thread: 3: critical section started
Thread: 3: critical section ended
Thread: 4: critical section started
Thread: 4: critical section ended
Thread: 5: critical section started
Thread: 5: critical section ended

The is the correct behaviour we want. Each thread wait outside critical section if there is another thread currently inside critical section. We have ensured that our value t is only changed by only one thread at any given time.

Using Grand Central Dispatch (GCD)

Using GCD is Apple recommended way of achieving concurrency. We will modify our Atomic class and implement it using a serial dispatch queue.

final class GCDAtomic<T: Operatable> {
    
    private let initial: T
    private var t: T
    private let dispatchQueue = DispatchQueue(label: "tech.swiftx.dispatchQueue")
    init(_ t: T) {
        self.t = t
        initial = t
    }
    
    func incrementAndGet(by other: T) -> T {
        
        var result = initial
        dispatchQueue.sync {
            // Critical section starts
            // The change to t should be atomic here
         
            print("\(Thread.current.name!): critical section started")
            result = t.add(other: other)
            t = result
            print("\(Thread.current.name!): critical section ended")
            
            // Critical section ends
        }
        return result
    }
}

Below is the output of using GCDAtomic class

Thread: 1: critical section started
Thread: 1: critical section ended
Thread: 5: critical section started
Thread: 5: critical section ended
Thread: 3: critical section started
Thread: 3: critical section ended
Thread: 4: critical section started
Thread: 4: critical section ended
Thread: 2: critical section started
Thread: 2: critical section ended

Please note that the order of thread execution is not relevant. The important factor is that at any give time only one of the thread is executing in critical section. This has also same behaviour as using NSLock.

All the above code snippets can be found here.

This a lot of boilerplate code. Luckily you dont have to implement atomic behaviour from scratch for your swift project. There is a popular open source library for working with atomics types in swift named swift-atomics.(I am not associated with swift-atomics library).

Towards Native Swift Atomics

As of writing there is a proposal SE-0282 in swift evolution that promises atomic types in swift as part of swift standard library. If the proposal is accepted we will see first class atomic types in swift in future swift versions.

Important Links

NSLock: https://developer.apple.com/documentation/foundation/nslock

GCD: https://developer.apple.com/documentation/DISPATCH

Swift-atomics: https://github.com/glessard/swift-atomics

SE-0283: https://github.com/apple/swift-evolution/blob/master/proposals/0282-atomics.md

Gist: https://bit.ly/3gD9QV6

std::atomic: https://en.cppreference.com/w/cpp/atomic/atomic

java.util.concurrent.atomic: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

If you liked the article please consider sharing it with other people. Also please subscribe to my mailing list to get latest updates immediately.