One alternative (and possibly more direct) approach to your self-answer is to heap-allocate the lock in Swift directly, as opposed to bridging over to Objective-C to do it. The Objective-C approach avoids the issue by calling the lock functions from a different language, with different semantics — C and Objective-C don't move or tombstone value types passed in to functions by inout reference; but you can also avoid the problem in pure Swift, by not taking an inout reference at all:
let lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
lock.initialize(to: .init())
// later:
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
Heap-allocating allows you to pass a pointer directly into the function, and pointers are reference types in Swift — while Swift can move the pointer value itself, the memory it references will remain untouched (and valid).
If you go this route, don't forget to deinitialize and deallocate the memory when you want to tear down the lock:
lock.deinitialize(count: 1)
lock.deallocate()
If you'd like, you can create a similar UnfairLock
interface in Swift, including functionality like your own mutexExecute
:
typealias UnfairLock = UnsafeMutablePointer<os_unfair_lock>
extension UnfairLock {
static func createLock() -> UnfairLock {
let l = UnfairLock.allocate(capacity: 1)
l.initialize(to: .init())
return l
}
static func destructLock(_ lock: UnfairLock) {
lock.deinitialize(count: 1)
lock.deallocate()
}
func whileLocked<T>(_ action: () throws -> T) rethrows -> T {
os_unfair_lock_lock(self)
defer { os_unfair_lock_unlock(self) }
return try action()
}
}
Usage:
init() {
lock = UnfairLock.createLock()
}
deinit {
UnfairLock.destructLock(lock)
}
func performThing() -> Foo {
return lock.whileLocked {
// some operation that returns a Foo
}
}