How should I archive the Bluetooth incoming data processing app?
I am working on a project where I have an application that receives data over BLE from a wearable peripheral, but I am struggling with how to archive the application. I currently have one BLEManager class that gets data in the background all the time and then uses NSNotificationCenter to send it to the active view controller. This works, but it gets messy and not ideal as I have multiple view controllers that process the data the same way and then just display it differently. In addition, there are some settings related to data processing that can be changed in the application and should be the same everywhere. It would be nice if BLEManager would send data to the central processing class,and then the processed data was sent to the active view controller, but I'm not sure what is the best way to set this.
I could include all the processing in the BLEManager class, but then it would be rather bloated and unintuitive and it would be nice to work with forward movement. If I create a separate processing class that is a property of the BLEManager then I have to go through the BLEManager if I want to get or change any variables in the processing class from anywhere else, which is annoying. I could make a singleton handling class that receives data from the BLEManager and then sends it to the active VC, but I've seen people say to avoid singles, so I'm hesitant to use another one, although it looks like this might be a good solution.
Is there a standard or recommended way to archive an iOS app to handle incoming data from Bluetooth and then send it where needed?
source to share
In cases where you are building a large system to communicate with BLE (others as well) BLEManager
shouldn't be anything. It should be an object that is capable of handling all of the Bluetooth communication (and I'm sure it already does) and communicating the appropriate data via delegation.
At this point, you will need an interface for this object, which in your case would be best single. This class should have a minimal interface to whatever you need to manipulate this data, and shouldn't expose BLEManager
at all. This means making all methods, such as initialization BLEManager
, that make sense in terms of use. In some cases that will copy the part of the interface you already made in BLEManager
, and some methods will most likely just forward the message to the manager.
This interface class may seem a little gimmicky in the beginning, but it will save your life in the end. What you should add to this class are all other possible attachments like local database manager, API manager, analytics, mock ...
So, for your specific case, it would seem your task list is:
- Create a class
BLEManager
that can communicate over Bluetooth. - Create a data manager class that can handle, process and store data from the required inputs such as
BLEManager
- Create a singleton interface that includes all the nesting needed by the core, such as the
BLEManager
data manager. Singleton should provide an open interface that makes sense from a usage point of view and includes a delegation or notification system.
The interface should look something like this:
Heading
@protocol CoreManagerDelegate
- (void)coreManager:(CoreManager *)sender updatedData:(id)data;
@optional
- (void)coreManager:(CoreManager *)sender encounteredIssue:(NSError *)issue;
- (void)coreManager:(CoreManager *)sender changedConnectionState:(BluetoothConnectionState)isConnected; // BluetoothConnectionStateUnkown, BluetoothConnectionStateDisabled, BluetoothConnectionStateNotPaired, BluetoothConnectionStateConnected...
@end
@interface CoreManager : Singleton
@property (weak) id<CoreManagerDelegate> delegate;
- (void)initialize;
- (void)fetchBluetoothData;
- (id)getCurrentData;
@end
Source
@interface CoreManager()
@property BLEManager *bluetoothManager;
@property DataManager *dataManager;
@end
@implementation CoreManager
#pragma mark - Bluetooth manager attachment
- (void)fetchBluetoothData {
[self.bluetoothManager fetchSomeData];
}
#pragma mark Delegate
- (void)BLEManager:(BLEManager *)sender receivedNewData:(id)data {
// do data validation, log if needed, report...
[self.dataManager insertNewData:data];
}
- (void)BLEManager:(BLEManager *)sender encounteredIssue:(NSError *)issue {
// modify and evaluate the error if needed
if([self.delegate respondsToSelector:@selector(coreManager:encounteredIssue:)]) {
[self.delegate coreManager:self encounteredIssue:issue];
}
else {
// log error ?
}
// send the error to a remote service
}
#pragma mark - Data manager attachment
- (id)getCurrentData {
return [self.dataManager currentData];
}
#pragma mark Delegate
- (void)dataManager:(DataManager *)sender updatedData:(id)processedData {
[self.delegate coreManager:self updatedData:processedData];
}
- (void)dataManager:(BLEManager *)sender encounteredIssue:(NSError *)issue {
// modify and evaluate the error if needed
if([self.delegate respondsToSelector:@selector(coreManager:encounteredIssue:)]) {
[self.delegate coreManager:self encounteredIssue:issue];
}
else {
// log error ?
}
// send the error to a remote service
}
@end
This will now maintain very clean and flexible code. This will prevent any unnecessary inflation of the class or file. This is a very good opportunity for testing and mocking. Adding additional functionality will be transparent from the point of view of use: for example, the implementation of the master data will not change the part of the user interface of your project.
Types id
should be replaced with specific custom objects, if possible. Even errors and logs are best customizable, so you can easily track them through the app, enable and disable them as you see fit.
Note. If you think this is too much of a change to convert your bluetooth manager to a regular object instead of a singleton at this point, you can still leave it single and just override the getter to return a generic instance.
I'm sure there are many other good practices out there, but this is the one I use mostly and has proven to be the best so far. Hope this helps you.
source to share
I don't know of a standard design pattern for this, but I have seen Singleton BLEManager in widespread use. I understand that you only need one BLE channel related object.
To keep your BLEManager too bloated, you can use a delegate pattern with a different protocol for one BLE message you need to process. This way, your BLEManager simply passes messages through delegates that handle each message type.
This will give you better separation of concerns so your BLEManager doesn't bloat. The BLEManager will then become a dumb walkway with the real work done by the message handler delegates.
source to share