Multiple CBCentralManager instances in one app: how do they behave?

I've seen many different answers over time to how multiple CBCentralManager instances in one app behave.


In older versions of the iOS documentation, it was explicitly documented as not supported. That warning is no longer present.


My questions:


- what's the current iOS 9 behavior?

- what have the behaviors been over time in previous iOS versions?

- has Apple or an Apple employee backed up those answers?

Accepted Reply

Technically you can have more than one CBCentralManager instance in a single app.

If you end up having multiples due to some 3rd party library creating their own, just the existence of them will be OK besides the resource cost of each instance.

Outside the obvious resource hits (memory, CPU to process BLE events, etc.), it will also increase the messaging queue activity. These hits are low, but not negligible. So in the end, how many, and what do they do are the key questions.


What makes this a difficult question to answer is, while it is OK to design an app to use multiple managers without bad side effects and works efficiently, unfortunately that is frequently not the case. Through bad code or willful abuse there will be an app that creates hundreds of instances repeatedly doing one thing or another and will cripple BLE operations, and even worse crash the stack. I've seen this happen.


The important question becomes why do you need to do this. For example if your app is communicating with more than one type of peripheral at the same time - say, a heart monitor, a lost-keys tag, and say a thermostat - it would be an OK coding practice to create 3 separate delegate classes with their own managers; because each delegate would be handling each device completely differently.

Think how you create different ViewControllers for different types of views.


On the other end of the spectrum, if you are communicating with multiple peripherals of the same type, it is not a good practice to create a manager for each peripheral. This will not gain you anything, but each instance will have a resource cost as I explained above.


To refer you to the paragraph you quoted: "When your app implements the central role and adds support for state preservation and restoration, the system saves the state of your central manager object when the system is about to terminate your app to free up memory (if your app has multiple central managers, you can choose which ones you want the system to keep track of)."

Even outside of CoreBluetooth, it is always better to be a smaller target when the system comes knocking to free up memory. So, my personal suggestion would be to only use as many objects in your app as necessary.


To answer your specific questions:

- does altering the state on one (e.g. starting a scan) alter the state on another? Simple answer, if and only if not all of the managers are interacting with the same peripheral, is NO. They are independent. But if they are interacting with the same peripheral, the effects will depend on what is going on. If one manager is scanning, but the second one decides to connect, then the advertising will stop and the first manager will not discover the device. And so on...

- can CBPeripheral instances be shared between CBCentralManager instances? You can share the property data. If you are asking about the CBPeripheral object, you cannot. When a central manager connects to a peripheral, there will be a message link created, and any events from the peripheral will be routed to that particular central. Central managers can create their own instances of CPBeripheral for the same peripheral, and if both need to receive the same events (read above and decide whether this is necessary or a good idea), they will need to connect individually.

- can NSUUIDs of peripherals be shared between CBCentralManager instances? While this may work in some cases, there will be conditions that it will not. The UUID of peripherals can change and is assumed only temporary identifiers. Technically, two centrals can discover the same peripheral with different UUIDs

- is there any harm to having a series of short-lived CBCentralManager instances existing along side a long-lived instance? I would first ask, what is the point? I will refer you to my opening paragraphs again. Maybe the question you shoudl ask is "what is the benefit?"

- ... and for all of these questions, did iOS always behave this way? If not, what was first iOS version that did? Unfortunately I cannot answer this question. A lot has changed with CoreBluetooth between iOS 7 and iOS 9, including a lot of fixes that changed behaviour. Best would be to test your app against these versions and determine if the behaviour suits your use case.


From your description of what you are trying to accomplish, it sounds like you are trying to solve a code architecture issue, and do not necessarily need multiple Central Managers. You can create a singleton class that handles CoreBluetooth with a single manager, which then would dispatch different events (scanning, connecting, etc.) to the appropriate handler classes.

Replies

Can you please explain what you mean by "behave"?

What have you tried and what did you see different than what you expected?

What are you trying to accomplish?

In the broadest sense, the effect of having multiple CBCentralManager instances in one app is not documented. Thus it's not clear if having multiple CBCentralManager instances might cause issues within CoreBluetooth (e.g. surface bugs in CoreBluetooth).


In 2013, someone on the Apple Bluetooth list mentioned that it would cause issues in CoreBluetooth to have multiple CBCentralManager instances:

http://lists.apple.com/archives/bluetooth-dev/2013/May/msg00013.html


Here's a Stack Overflow post mentioning that the iOS docs used to say that it was not supported, but that this warning was removed for iOS 8.3

http://stackoverflow.com/questions/30309401/multiple-cbcentralmanager-instances


Here's a Stack Overflow post mentioning that it is supported, but you can't have more than one instance connect to the same peripheral:

http://stackoverflow.com/questions/32221338/are-multiple-instances-of-cbcentralmanager-objects-supported?lq=1


The only reference I can see to multiple CoreBluetooth instances in Apple's documentation is in the CoreBluetooth Programming guide:

https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetooth_concepts.pdf


"When your app implements the central role and adds support for state preservation and restoration, the system saves the state of your central manager object when the system is about to terminate your app to free up memory (if your app has multiple central managers, you can choose which ones you want the system to keep track of)."


So architecturally, it's unclear how they interact. My specific behavior questions are:

- does altering the state on one (e.g. starting a scan) alter the state on another?

- can CBPeripheral instances be shared between CBCentralManager instances?

- can NSUUIDs of peripherals be shared between CBCentralManager instances?

- is there any harm to having a series of short-lived CBCentralManager instances existing along side a long-lived instance?


... and for all of these questions, did iOS always behave this way? If not, what was first iOS version that did? (This is in order to support iOS 8 or iOS 7 users)


In terms of what I'm trying to accomplish: at first this came up because I was refactoring some Bluetooth code. It has a monolithic class that handles connecting to known peripherals & scanning for new ones. I felt like this was insufficiently separating concerns in my app, and that scanning should be in its own class. I was considering giving that scanning class its own, short-lived CBCentralManager. But then I realized I have no idea if that would actually work, abandoned that approach, and asked this question.


But after asking this question, I realized that vendors may ship binary libraries to access Bluetooth devices which also have their own CBCentralManager instances. If that happened, a developer would want to know how that library's use of a CBCentralManager would affect their own code which may have its own CBCentralManager.

Has Apple got back to you on this? It has been more than 10 days since they asked you to clarify...


I have similar issues understanding this. The guidance that they provide seems incomplete and there is no official apple demo code for this. Would really benefit from having one.

Dear Apple,


have you seen the update from the user on this?


We would really benefit from having a demo code on CoreBluetooth and state preservation and one on CoreBluetooth and multiple CBCentralManagers.


The first demo could also have a mode that emits BLE signals so developers can various test state preservation / restoration use cases.

The debugger will give you some insight to a few things when discovering peripherals. Look at the private ivar/properties on cbperipheral, they all point to a cbcentralmanager, likewise the cbcentralmanager points to a CoreBluetooth XPC process running on the OS. You could learn by experimentation, i.e. several scanners, scan for different UUIDs, do some unit testing performance. Compare it against a single scanner that has a filter for both UUIDS.


I'm very much interested in performance differences with multiple simultaneous CBCentralManagers running.

No response yet.

That definitely yields interesting information. I'd love to get official documentation too though. I ship commercial products and if I rely only on inferences from private information, I would possibly ship unreliable software.

I guess it takes too much time to answer all the questions without a ticket.. will someone use one of the support tickets then share the answer here?

Technically you can have more than one CBCentralManager instance in a single app.

If you end up having multiples due to some 3rd party library creating their own, just the existence of them will be OK besides the resource cost of each instance.

Outside the obvious resource hits (memory, CPU to process BLE events, etc.), it will also increase the messaging queue activity. These hits are low, but not negligible. So in the end, how many, and what do they do are the key questions.


What makes this a difficult question to answer is, while it is OK to design an app to use multiple managers without bad side effects and works efficiently, unfortunately that is frequently not the case. Through bad code or willful abuse there will be an app that creates hundreds of instances repeatedly doing one thing or another and will cripple BLE operations, and even worse crash the stack. I've seen this happen.


The important question becomes why do you need to do this. For example if your app is communicating with more than one type of peripheral at the same time - say, a heart monitor, a lost-keys tag, and say a thermostat - it would be an OK coding practice to create 3 separate delegate classes with their own managers; because each delegate would be handling each device completely differently.

Think how you create different ViewControllers for different types of views.


On the other end of the spectrum, if you are communicating with multiple peripherals of the same type, it is not a good practice to create a manager for each peripheral. This will not gain you anything, but each instance will have a resource cost as I explained above.


To refer you to the paragraph you quoted: "When your app implements the central role and adds support for state preservation and restoration, the system saves the state of your central manager object when the system is about to terminate your app to free up memory (if your app has multiple central managers, you can choose which ones you want the system to keep track of)."

Even outside of CoreBluetooth, it is always better to be a smaller target when the system comes knocking to free up memory. So, my personal suggestion would be to only use as many objects in your app as necessary.


To answer your specific questions:

- does altering the state on one (e.g. starting a scan) alter the state on another? Simple answer, if and only if not all of the managers are interacting with the same peripheral, is NO. They are independent. But if they are interacting with the same peripheral, the effects will depend on what is going on. If one manager is scanning, but the second one decides to connect, then the advertising will stop and the first manager will not discover the device. And so on...

- can CBPeripheral instances be shared between CBCentralManager instances? You can share the property data. If you are asking about the CBPeripheral object, you cannot. When a central manager connects to a peripheral, there will be a message link created, and any events from the peripheral will be routed to that particular central. Central managers can create their own instances of CPBeripheral for the same peripheral, and if both need to receive the same events (read above and decide whether this is necessary or a good idea), they will need to connect individually.

- can NSUUIDs of peripherals be shared between CBCentralManager instances? While this may work in some cases, there will be conditions that it will not. The UUID of peripherals can change and is assumed only temporary identifiers. Technically, two centrals can discover the same peripheral with different UUIDs

- is there any harm to having a series of short-lived CBCentralManager instances existing along side a long-lived instance? I would first ask, what is the point? I will refer you to my opening paragraphs again. Maybe the question you shoudl ask is "what is the benefit?"

- ... and for all of these questions, did iOS always behave this way? If not, what was first iOS version that did? Unfortunately I cannot answer this question. A lot has changed with CoreBluetooth between iOS 7 and iOS 9, including a lot of fixes that changed behaviour. Best would be to test your app against these versions and determine if the behaviour suits your use case.


From your description of what you are trying to accomplish, it sounds like you are trying to solve a code architecture issue, and do not necessarily need multiple Central Managers. You can create a singleton class that handles CoreBluetooth with a single manager, which then would dispatch different events (scanning, connecting, etc.) to the appropriate handler classes.

Thanks! This is a perfect and helpful answer.

Thanks! This is a very helpful answer. 非常感谢这个回答🙂

I posted this question yesterday in the Bluetooth forum and I'll repost it here; I believe that scanning with multiple CBCentralManagers intermittently results in a double KVO removal exception (grrrr KVO!) on iOS 11. I filed a bug back in August with a test application which reproduces the crash.


We develop 2 separate SDKs for communicating with two different classes of BLE-based devices; for device discovery, each SDK will create a CBCentralManager and begin scanning, reporting newly found devices to the client etc. When we attempt to scan with both SDKs simultaneously, in effect creating two spearate CBCentralManager instances, we get intermittent yet reliable crashing, which logs the folowing:


*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <CBPeripheral 0x1c010b760> for the key path "delegate" from <CBPeripheral 0x1c010b760> because it is not registered as an observer.'


I filed this as bug with Apple, radr://problem/33929866 back in August but haven't heard back from them. I even included a sample application that reproduces the problem.


My question: is this usage pattern -- instantiating two central manager objects and scanning at the same time -- supported by Apple (i.e. the crash is a bug) or is this just simply an undocumented no-no? A workaround I wish to avoid is to resort to instantiating one CBCentralManager as an external global and divising some method to dispatch its messages to multiple clients, as this introduces external dependencies into our SDKs.

Thanks in advance,
Joe