From b9e5618fef04999d04a564808d24f599a5611584 Mon Sep 17 00:00:00 2001 From: Konstantin Polin Date: Sat, 3 Jan 2026 13:54:49 -0800 Subject: [PATCH] Implemented escape hatch --- README.md | 29 +++++++++++++++++++ .../ActorCoreBluetooth/BluetoothCentral.swift | 14 +++++++++ .../ConnectedPeripheral.swift | 8 +++++ .../BluetoothCharacteristic.swift | 8 +++++ .../SendableWrappers/BluetoothService.swift | 8 +++++ .../DiscoveredPeripheral.swift | 8 +++++ 6 files changed, 75 insertions(+) diff --git a/README.md b/README.md index c71ad9a..580d875 100644 --- a/README.md +++ b/README.md @@ -425,6 +425,35 @@ func setupLogging() { } ``` +## Advanced Features + +### Escape Hatch: Accessing CoreBluetooth Objects + +For advanced use cases, you can access underlying CoreBluetooth objects directly: + +```swift +@MainActor +func useEscapeHatch() async throws { + let central = BluetoothCentral() + let devices = try await central.scanForPeripherals(timeout: 5.0) + guard let device = devices.first else { return } + let peripheral = try await central.connect(device, timeout: 10.0) + + // Access underlying CBPeripheral + let cbPeripheral = peripheral.underlyingPeripheral() + let maxWriteLength = cbPeripheral.maximumWriteValueLength(for: .withResponse) +} +``` + +**Available methods:** +- `underlyingCentralManager()` → `CBCentralManager?` +- `underlyingPeripheral(for:)` → `CBPeripheral?` +- `underlyingPeripheral()` → `CBPeripheral` +- `underlyingService()` → `CBService` +- `underlyingCharacteristic()` → `CBCharacteristic` + +⚠️ **Warning**: Bypasses actor-based safety. Don't modify delegates or use for primary API operations. + ## Installation ### Swift Package Manager diff --git a/Sources/ActorCoreBluetooth/BluetoothCentral.swift b/Sources/ActorCoreBluetooth/BluetoothCentral.swift index fb89f56..3d7b6ea 100644 --- a/Sources/ActorCoreBluetooth/BluetoothCentral.swift +++ b/Sources/ActorCoreBluetooth/BluetoothCentral.swift @@ -644,6 +644,20 @@ public final class BluetoothCentral { logger?.centralNotice("BluetoothCentral cleanup completed") } + // MARK: - Escape Hatch: CoreBluetooth Object Access + + /// Access the underlying CBCentralManager for advanced use cases. + /// - Warning: Bypasses actor-based safety. Don't modify delegates. + public func underlyingCentralManager() -> CBCentralManager? { + return cbCentralManager + } + + /// Access the underlying CBPeripheral for a connected peripheral. + /// - Warning: Bypasses actor-based safety. Don't modify delegates. + public func underlyingPeripheral(for peripheralID: UUID) -> CBPeripheral? { + return connectedPeripherals[peripheralID] + } + // MARK: - Internal Delegate Handling Methods /// Handle peripheral state changes, managing both operation completion and state monitoring diff --git a/Sources/ActorCoreBluetooth/ConnectedPeripheral.swift b/Sources/ActorCoreBluetooth/ConnectedPeripheral.swift index b8596e9..87c12b7 100644 --- a/Sources/ActorCoreBluetooth/ConnectedPeripheral.swift +++ b/Sources/ActorCoreBluetooth/ConnectedPeripheral.swift @@ -570,6 +570,14 @@ public final class ConnectedPeripheral { return servicesWithCharacteristics } + // MARK: - Escape Hatch: CoreBluetooth Object Access + + /// Access the underlying CBPeripheral for advanced use cases. + /// - Warning: Bypasses actor-based safety. Don't modify delegates. + public func underlyingPeripheral() -> CBPeripheral { + return cbPeripheral + } + // MARK: - Internal Delegate Handling Methods // Called by delegate proxy when services are discovered diff --git a/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothCharacteristic.swift b/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothCharacteristic.swift index d425ff3..db30832 100644 --- a/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothCharacteristic.swift +++ b/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothCharacteristic.swift @@ -23,4 +23,12 @@ public struct BluetoothCharacteristic: Sendable { self.value = cbCharacteristic.value self.cbCharacteristic = Unchecked(cbCharacteristic) } + + // MARK: - Escape Hatch: CoreBluetooth Object Access + + /// Access the underlying CBCharacteristic for advanced use cases. + /// - Warning: Bypasses actor-based safety. + public func underlyingCharacteristic() -> CBCharacteristic { + return cbCharacteristic.value + } } diff --git a/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothService.swift b/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothService.swift index a66b485..35f4ac2 100644 --- a/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothService.swift +++ b/Sources/ActorCoreBluetooth/SendableWrappers/BluetoothService.swift @@ -23,4 +23,12 @@ public struct BluetoothService: Sendable { self.characteristics = characteristics self.cbService = Unchecked(cbService) } + + // MARK: - Escape Hatch: CoreBluetooth Object Access + + /// Access the underlying CBService for advanced use cases. + /// - Warning: Bypasses actor-based safety. + public func underlyingService() -> CBService { + return cbService.value + } } diff --git a/Sources/ActorCoreBluetooth/SendableWrappers/DiscoveredPeripheral.swift b/Sources/ActorCoreBluetooth/SendableWrappers/DiscoveredPeripheral.swift index 376cef3..f7a3d30 100644 --- a/Sources/ActorCoreBluetooth/SendableWrappers/DiscoveredPeripheral.swift +++ b/Sources/ActorCoreBluetooth/SendableWrappers/DiscoveredPeripheral.swift @@ -25,4 +25,12 @@ public struct DiscoveredPeripheral: Sendable { self.advertisementData = AdvertisementData(cbAdvertisementData: advertisementData) self.cbPeripheral = Unchecked(cbPeripheral) } + + // MARK: - Escape Hatch: CoreBluetooth Object Access + + /// Access the underlying CBPeripheral for advanced use cases. + /// - Warning: Bypasses actor-based safety. Don't modify delegates. + public func underlyingPeripheral() -> CBPeripheral { + return cbPeripheral.value + } }