Reputation: 313
I'm looking at the open source Bluefruit code as an example, specifically the BleManager class that is the interface with the CBCentralManager. I've emailed the author and they didn't respond:
It looks like the class is designed in a singleton architecture ("shared" on line 23) and so is constructed lazily when the first "BleManager.connect() is called elsewhere in the code.
What confuses me is in the init(), there's a semaphore "wait" function:
override init() {
super.init()
centralManagerPoweredOnSemaphore.wait()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
}
I'm really only used to semaphores in a DispatchQueue.async() thread. Isn't this wait() function being called on a main thread? Won't that lock it up? The .signal() call that will unblock that .wait() call is on line 289:
extension BleManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
DLog("centralManagerDidUpdateState: \(central.state.rawValue)")
// Unlock state lock if we have a known state
if central.state == .poweredOn || central.state == .poweredOff || central.state == .unsupported || central.state == .unauthorized {
centralManagerPoweredOnSemaphore.signal()
}
So once the system centralManager updates the BLE utility state, this will get called, and as long as it's not unknown, .signal() will get called and the rest of the init() will run. I've used print statements to confirm this is how it works. centralManagerPoweredOnSemaphore.wait() is called, then centralManagerDidUpdateState(), and then the rest of the init(). I still don't understand this though:
In the end it works though, I just wonder how the exact sequence of things is actually running under the hood to make it run properly.
Upvotes: 1
Views: 376
Reputation: 115051
They are using the semaphore to block any operations on the BleManager
object until the central exits the .unknown
state.
The semaphore is created with an initial value of 1. The wait
in the init
will decrement it to 0, and not block. The central is then initialised and at some point the powered on state is entered. At this point the signal
will return the semaphore to 1.
Now looking at the other functions, such as the connect
function (line 167) you will see the first thing they do is wait
and then signal
the semaphore. Consider what happens in two different states:
.poweredOn
state - the semaphore count is 0 so the wait
blocks. Assuming the BLE state becomes .poweredOn
at some point, the wait
will end and the semaphore is immediately released and then the connect
function proceeds..poweredOn
state - the semaphore count is 1, so the wait
does not block, the semaphore is then immediately released and the connect
function proceeds.The other functions work in a similar way.
The advantage of this approach is that the calling code doesn't need to keep checking the state; it can access the sharedInstance
and then immediately call startScan
without needing to check that the Central is powered on.
Upvotes: 3