Renuka Pandey
Renuka Pandey

Reputation: 1810

'OSSpinLock' was deprecated in iOS 10.0: Use os_unfair_lock() from <os/lock.h> instead

I went through this Question but the provided solution didn't work. Can someone please explain any alternative approach or proper implementation using os_unfair_lock()?

when I am using 'OS_UNFAIR_LOCK_INIT', it seems unavailable.

enter image description here

Thanks!

Upvotes: 5

Views: 4113

Answers (2)

Kamran
Kamran

Reputation: 15238

Below solution isn't recommended from iOS 16+ as per the documentation here

You can use os_unfair_lock as below,

var unfairLock = os_unfair_lock_s()

os_unfair_lock_lock(&unfairLock)
os_unfair_lock_unlock(&unfairLock)

Upvotes: -3

Rob
Rob

Reputation: 437432

In iOS 16 (and macOS 13) and later, you should use OSAllocatedUnfairLock. As the documentation says:

it’s unsafe to use os_unfair_lock from Swift because it’s a value type and, therefore, doesn’t have a stable memory address. That means when you call os_unfair_lock_lock or os_unfair_lock_unlock and pass a lock object using the & operator, the system may lock or unlock the wrong object.

Instead, use OSAllocatedUnfairLock, which avoids that pitfall because it doesn’t function as a value type, despite being a structure. All copied instances of an OSAllocatedUnfairLock control the same underlying lock allocation.

So, if you have a counter that you want to interact with in a thread-safe manner:

import os.lock

let counter = OSAllocatedUnfairLock(initialState: 0)

...

counter.withLock { value in
    value += 1
}

...

counter.withLock { value in
    print(value)
}

For support of earlier OS versions, see my original answer below.


In Concurrent Programming With GCD in Swift 3, they warn us that we cannot use os_unfair_lock directly in Swift because “Swift assumes that anything that is struct can be moved, and that doesn't work with a mutex or with a lock.”

In that video, the speaker suggests that if you must use os_unfair_lock, that you put this in an Objective-C class (which won't move the struct). Or if you look at some of the stdlib code, they show you can stay in Swift, but use a UnsafeMutablePointer instead of the struct directly. (Thanks to bscothern for confirming this pattern.)

So, for example, you can write an UnfairLock class that avoids this problem:

final class UnfairLock: NSLocking {
    private let unfairLock: UnsafeMutablePointer<os_unfair_lock> = {
        let pointer = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
        pointer.initialize(to: os_unfair_lock())
        return pointer
    }()

    deinit {
        unfairLock.deinitialize(count: 1)
        unfairLock.deallocate()
    }

    func lock() {
        os_unfair_lock_lock(unfairLock)
    }

    func tryLock() -> Bool {
        os_unfair_lock_trylock(unfairLock)
    }

    func unlock() {
        os_unfair_lock_unlock(unfairLock)
    }
}

Then you can do things like:

let lock = UnfairLock()

And then use lock and unlock like you would with NSLock, but using the more efficient os_unfair_lock behind the scenes:

lock.lock()
// critical section here
lock.unlock()

And because this UnfairLock conforms to NSLocking, you can use extensions designed for that. E.g., there is a very useful Foundation extension withLock for the NSLocking protocol. This is a useful way of ensuring that locks and unlocks are properly scoped/balanced, thereby avoiding the brittleness of the above lock/unlock pattern. You can do things like:

lock.withLock {
    // critical section here
}

That will lock before the “critical section” runs, and unlock when complete, ensuring that everything is properly balanced, even if you have some “early exits” inside the critical section.

But, going back to the original question, never use os_unfair_lock from Swift without something like the above or as contemplated in that video, both of which provide a stable memory address for the lock.

Upvotes: 17

Related Questions