Skip to content

Commit

Permalink
Merge pull request #19 from StarryInternet/central-refactor
Browse files Browse the repository at this point in the history
central refactor
  • Loading branch information
klundberg authored Jul 19, 2023
2 parents 48635dc + 73787f5 commit 0240f8f
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 172 deletions.
10 changes: 4 additions & 6 deletions CombineCoreBluetooth.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
EB443FCC27C6BDE10005CCEA /* Live+CentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB443FB427C6BDE00005CCEA /* Live+CentralManager.swift */; };
EB443FCD27C6BDE10005CCEA /* Interface+CentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB443FB527C6BDE00005CCEA /* Interface+CentralManager.swift */; };
EB443FCE27C6BDE10005CCEA /* Mock+CentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB443FB627C6BDE00005CCEA /* Mock+CentralManager.swift */; };
EB443FCF27C6BDE10005CCEA /* Convenience+CentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB443FB727C6BDE00005CCEA /* Convenience+CentralManager.swift */; };
EB443FDD27C6C1940005CCEA /* CombineCoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB443F8E27C6BCA70005CCEA /* CombineCoreBluetooth.framework */; };
EB443FE627C6C1B40005CCEA /* CentralManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB443FE527C6C1B40005CCEA /* CentralManagerTests.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -83,10 +82,9 @@
EB443FB027C6BDE00005CCEA /* Mock+Central.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Mock+Central.swift"; sourceTree = "<group>"; };
EB443FB127C6BDE00005CCEA /* Live+Central.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Live+Central.swift"; sourceTree = "<group>"; };
EB443FB227C6BDE00005CCEA /* Interface+Central.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Interface+Central.swift"; sourceTree = "<group>"; };
EB443FB427C6BDE00005CCEA /* Live+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Live+CentralManager.swift"; sourceTree = "<group>"; };
EB443FB527C6BDE00005CCEA /* Interface+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Interface+CentralManager.swift"; sourceTree = "<group>"; };
EB443FB427C6BDE00005CCEA /* Live+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "Live+CentralManager.swift"; sourceTree = "<group>"; tabWidth = 2; };
EB443FB527C6BDE00005CCEA /* Interface+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = "Interface+CentralManager.swift"; sourceTree = "<group>"; tabWidth = 2; };
EB443FB627C6BDE00005CCEA /* Mock+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Mock+CentralManager.swift"; sourceTree = "<group>"; };
EB443FB727C6BDE00005CCEA /* Convenience+CentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Convenience+CentralManager.swift"; sourceTree = "<group>"; };
EB443FD127C6BE440005CCEA /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/CoreBluetooth.framework; sourceTree = DEVELOPER_DIR; };
EB443FD327C6BE5A0005CCEA /* Combine.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Combine.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/Combine.framework; sourceTree = DEVELOPER_DIR; };
EB443FD927C6C1940005CCEA /* CombineCoreBluetooth Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CombineCoreBluetooth Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -150,7 +148,9 @@
EB443F8F27C6BCA70005CCEA /* Products */,
EB443FD027C6BE440005CCEA /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
EB443F8F27C6BCA70005CCEA /* Products */ = {
isa = PBXGroup;
Expand Down Expand Up @@ -245,7 +245,6 @@
EB443FB427C6BDE00005CCEA /* Live+CentralManager.swift */,
EB443FB527C6BDE00005CCEA /* Interface+CentralManager.swift */,
EB443FB627C6BDE00005CCEA /* Mock+CentralManager.swift */,
EB443FB727C6BDE00005CCEA /* Convenience+CentralManager.swift */,
);
path = CentralManager;
sourceTree = "<group>";
Expand Down Expand Up @@ -427,7 +426,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB443FCF27C6BDE10005CCEA /* Convenience+CentralManager.swift in Sources */,
EB443FCD27C6BDE10005CCEA /* Interface+CentralManager.swift in Sources */,
0023587228ACBF3300E42C0B /* CentralManagerError.swift in Sources */,
EB443FC127C6BDE10005CCEA /* Peer.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,68 @@ import CoreBluetooth
import Foundation

public struct CentralManager {
#if os(macOS) && !targetEnvironment(macCatalyst)
#if os(macOS) && !targetEnvironment(macCatalyst)
public typealias Feature = Never
#else
#else
public typealias Feature = CBCentralManager.Feature
#endif

#endif
let delegate: Delegate?

let _state: () -> CBManagerState
let _authorization: () -> CBManagerAuthorization
let _isScanning: () -> Bool

@available(macOS, unavailable)

let _supportsFeatures: (_ feature: Feature) -> Bool

let _retrievePeripheralsWithIdentifiers: ([UUID]) -> [Peripheral]
let _retrieveConnectedPeripheralsWithServices: ([CBUUID]) -> [Peripheral]
let _scanForPeripheralsWithServices: (_ serviceUUIDs: [CBUUID]?, _ options: ScanOptions?) -> AnyPublisher<PeripheralDiscovery, Never>
let _connectToPeripheral: (Peripheral, _ options: PeripheralConnectionOptions?) -> AnyPublisher<Peripheral, Error>
let _scanForPeripheralsWithServices: (_ serviceUUIDs: [CBUUID]?, _ options: ScanOptions?) -> Void
let _stopScan: () -> Void

let _connectToPeripheral: (Peripheral, _ options: PeripheralConnectionOptions?) -> Void
let _cancelPeripheralConnection: (_ peripheral: Peripheral) -> Void
let _registerForConnectionEvents: (_ options: [CBConnectionEventMatchingOption : Any]?) -> Void

public let didUpdateState: AnyPublisher<CBManagerState, Never>
public let willRestoreState: AnyPublisher<[String: Any], Never>
public let didConnectPeripheral: AnyPublisher<Peripheral, Never>
public let didFailToConnectPeripheral: AnyPublisher<(Peripheral, Error?), Never>
public let didDisconnectPeripheral: AnyPublisher<(Peripheral, Error?), Never>

@available(macOS, unavailable)

public let connectionEventDidOccur: AnyPublisher<(CBConnectionEvent, Peripheral), Never>
public let didDiscoverPeripheral: AnyPublisher<PeripheralDiscovery, Never>

@available(macOS, unavailable)

public let didUpdateACNSAuthorizationForPeripheral: AnyPublisher<Peripheral, Never>

public var state: CBManagerState {
_state()
}

public var authorization: CBManagerAuthorization {
_authorization()
}

public var isScanning: Bool {
_isScanning()
}

@available(macOS, unavailable)
public func supports(_ features: Feature) -> Bool {
#if os(macOS) && !targetEnvironment(macCatalyst)
#if os(macOS) && !targetEnvironment(macCatalyst)
// do nothing
#else
#else
return _supportsFeatures(features)
#endif
#endif
}

public func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [Peripheral] {
_retrievePeripheralsWithIdentifiers(identifiers)
}

public func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> [Peripheral] {
_retrieveConnectedPeripheralsWithServices(serviceUUIDs)
}

/// Starts scanning for peripherals that are advertising any of the services listed in `serviceUUIDs`
///
/// To stop scanning for peripherals, cancel the subscription made to the returned publisher.
Expand All @@ -73,47 +73,86 @@ public struct CentralManager {
/// - options: An optional dictionary specifying options for the scan.
/// - Returns: A publisher that sends values anytime peripherals are discovered that match the given service UUIDs.
public func scanForPeripherals(withServices serviceUUIDs: [CBUUID]?, options: ScanOptions? = nil) -> AnyPublisher<PeripheralDiscovery, Never> {
_scanForPeripheralsWithServices(serviceUUIDs, options)
didDiscoverPeripheral
.handleEvents(receiveSubscription: { _ in
_scanForPeripheralsWithServices(serviceUUIDs, options)
}, receiveCancel: {
_stopScan()
})
.shareCurrentValue()
.eraseToAnyPublisher()
}

public func connect(_ peripheral: Peripheral, options: PeripheralConnectionOptions? = nil) -> AnyPublisher<Peripheral, Error> {
_connectToPeripheral(peripheral, options)
Publishers.Merge(
didConnectPeripheral
.filter { $0 == peripheral }
.setFailureType(to: Error.self),
didFailToConnectPeripheral
.filter { p, _ in p == peripheral }
.tryMap { p, error in
throw CentralManagerError.failedToConnect(error as NSError?)
}
)
.prefix(1)
.handleEvents(receiveSubscription: { _ in
_connectToPeripheral(peripheral, options)
}, receiveCancel: {
_cancelPeripheralConnection(peripheral)
})
.shareCurrentValue()
.eraseToAnyPublisher()
}

public func cancelPeripheralConnection(_ peripheral: Peripheral) {
_cancelPeripheralConnection(peripheral)
}

public func registerForConnectionEvents(options: [CBConnectionEventMatchingOption: Any]? = nil) {
_registerForConnectionEvents(options)
}


/// Monitors connection events to the given peripheral and represents them as a publisher that sends `true` on connect and `false` on disconnect.
/// - Parameter peripheral: The peripheral to monitor for connection events.
/// - Returns: A publisher that sends `true` on connect and `false` on disconnect for the given peripheral.
public func monitorConnection(for peripheral: Peripheral) -> AnyPublisher<Bool, Never> {
Publishers.Merge(
didConnectPeripheral
.filter { p in p == peripheral }
.map { _ in true },
didDisconnectPeripheral
.filter { (p, error) in p == peripheral }
.map { _ in false }
)
.eraseToAnyPublisher()
}

/// Configuration options used when creating a `CentralManager`.
public struct CreationOptions {
/// If true, display a warning dialog to the user when the `CentralManager` is instantiated if Bluetooth is powered off
public var showPowerAlert: Bool?
/// A unique identifier for the Central Manager that's being instantiated. This identifier is used by the system to identify a specific CBCentralManager instance for restoration and, therefore, must remain the same for subsequent application executions in order for the manager to be restored.
public var restoreIdentifierKey: String?

public init(showPowerAlert: Bool? = nil, restoreIdentifierKey: String? = nil) {
self.showPowerAlert = showPowerAlert
self.restoreIdentifierKey = restoreIdentifierKey
}
}

/// Options used when scanning for peripherals.
public struct ScanOptions {
/// Whether or not the scan should filter duplicate peripheral discoveries
public var allowDuplicates: Bool?
/// Causes the scan to also look for peripherals soliciting any of the services contained in the list.
public var solicitedServiceUUIDs: [CBUUID]?

public init(allowDuplicates: Bool? = nil, solicitedServiceUUIDs: [CBUUID]? = nil) {
self.allowDuplicates = allowDuplicates
self.solicitedServiceUUIDs = solicitedServiceUUIDs
}
}

/// Options used when connecting to a given `Peripheral`
public struct PeripheralConnectionOptions {
/// If true, indicates that the system should display a connection alert for a given peripheral, if the application is suspended when a successful connection is made.
Expand All @@ -124,12 +163,23 @@ public struct CentralManager {
public var notifyOnNotification: Bool?
/// The number of seconds for the system to wait before starting a connection.
public var startDelay: TimeInterval?

public init(notifyOnConnection: Bool? = nil, notifyOnDisconnection: Bool? = nil, notifyOnNotification: Bool? = nil, startDelay: TimeInterval? = nil) {
self.notifyOnConnection = notifyOnConnection
self.notifyOnDisconnection = notifyOnDisconnection
self.notifyOnNotification = notifyOnNotification
self.startDelay = startDelay
}
}

class Delegate: NSObject {
let didUpdateState: PassthroughSubject<CBManagerState, Never> = .init()
let willRestoreState: PassthroughSubject<[String: Any], Never> = .init()
let didConnectPeripheral: PassthroughSubject<Peripheral, Never> = .init()
let didFailToConnectPeripheral: PassthroughSubject<(Peripheral, Error?), Never> = .init()
let didDisconnectPeripheral: PassthroughSubject<(Peripheral, Error?), Never> = .init()
let connectionEventDidOccur: PassthroughSubject<(CBConnectionEvent, Peripheral), Never> = .init()
let didDiscoverPeripheral: PassthroughSubject<PeripheralDiscovery, Never> = .init()
let didUpdateACNSAuthorizationForPeripheral: PassthroughSubject<Peripheral, Never> = .init()
}
}
Loading

0 comments on commit 0240f8f

Please sign in to comment.