Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ jobs:
runs-on: macos-latest

steps:
- uses: actions/checkout@v4
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
- uses: actions/checkout@v4
- name: Setup Swift 6.2
uses: swift-actions/setup-swift@v3
with:
swift-version: "6.2" # Specify the desired Swift version
- name: Get Swift version
run: swift --version # Verify the installed Swift version
- name: Build Swift Package
run: swift build -v
- name: Run Swift Package Tests
run: swift test -v
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
2 changes: 1 addition & 1 deletion Sources/BLECombineKit/Central/BLECentralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import CoreBluetooth
import Foundation

/// Interface definining the Bluetooth Central Manager that provides Combine APIs.
public protocol BLECentralManager: AnyObject {
public protocol BLECentralManager: AnyObject, Sendable {
/// Reference to the actual Bluetooth Manager, which is conveniently wrapped.
var associatedCentralManager: CBCentralManagerWrapper { get }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ typealias DidDiscoverAdvertisementDataResult = (
peripheral: CBPeripheralWrapper, advertisementData: [String: Any], rssi: NSNumber
)

final class BLECentralManagerDelegate: NSObject, CBCentralManagerDelegate {
final class BLECentralManagerDelegate: NSObject, CBCentralManagerDelegate, @unchecked Sendable {

let didConnectPeripheral = PassthroughSubject<CBPeripheralWrapper, BLEError>()
let didDisconnectPeripheral = PassthroughSubject<CBPeripheralWrapper, Never>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation

/// Interface for wrapping the CBCentralManager.
/// This interface is critical in order to mock the CBCentralManager calls.
public protocol CBCentralManagerWrapper {
public protocol CBCentralManagerWrapper: Sendable {
/// The CBCentralManager this interface wraps to.
/// Note that CBCentralManager conforms to CBCentralManagerWrapper and this getter interface is a convenient way to avoid an expensive downcast. That is, if you need a fixed reference to the CBCentralManager object do not run `let validManager = manager as? CBCentralManager`, simply run `let validManager = manager.wrappedManager` which will run significantly faster.
var wrappedManager: CBCentralManager? { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Combine
import CoreBluetooth
import Foundation

final class StandardBLECentralManager: BLECentralManager {
final class StandardBLECentralManager: BLECentralManager, @unchecked Sendable {
/// The wrapped CBCentralManager.
let associatedCentralManager: CBCentralManagerWrapper

Expand Down
4 changes: 2 additions & 2 deletions Sources/BLECombineKit/Characteristic/BLECharacteristic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
//

import Combine
import CoreBluetooth
@preconcurrency import CoreBluetooth

public struct BLECharacteristic {
public struct BLECharacteristic: Sendable {
public let value: CBCharacteristic
private let peripheral: BLEPeripheral

Expand Down
2 changes: 1 addition & 1 deletion Sources/BLECombineKit/Data/BLEData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

public struct BLEData {
public struct BLEData: Sendable {
public let value: Data

public init(value: Data) {
Expand Down
8 changes: 4 additions & 4 deletions Sources/BLECombineKit/Error/BLEError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
// Copyright © 2020 Henry Serrano. All rights reserved.
//

import CoreBluetooth
@preconcurrency import CoreBluetooth
import Foundation

extension NSError: Identifiable {
extension NSError: @retroactive Identifiable {

}

extension CBError: Hashable, Identifiable {
extension CBError: @retroactive Hashable, @retroactive Identifiable {
public var id: Self { self }
}

extension CBATTError: Hashable, Identifiable {
extension CBATTError: @retroactive Hashable, @retroactive Identifiable {
public var id: Self { self }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import CoreBluetooth

public protocol BLEATTRequest {
public protocol BLEATTRequest: Sendable {
/// Reference to the actual request. Use this getter to obtain the CBATTRequest if needed. Note that CBATTRequest conforms to BLEATTRequest.
var associatedRequest: CBATTRequest? { get }

Expand Down
2 changes: 1 addition & 1 deletion Sources/BLECombineKit/Peripheral Manager/BLECentral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import CoreBluetooth

public protocol BLECentral {
public protocol BLECentral: Sendable {
/// Reference to the actual central. Use this getter to obtain the CBCentral if needed. Note that CBCentral conforms to BLECentral.
var associatedCentral: CBCentral? { get }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import CoreBluetooth
/// ```
/// cancellable.cancel()
/// ```
public protocol BLEPeripheralManager {
public protocol BLEPeripheralManager: Sendable {
var state: CBManagerState { get }

func observeState() -> AnyPublisher<CBManagerState, Never>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
///
/// See original source on [GitHub](https://github.com/Polidea/RxBluetoothKit/blob/2a95bce60fb569df57d7bec41d215fe58f56e1d4/Source/CBPeripheralManagerDelegateWrapper.swift).
///
final class BLEPeripheralManagerDelegate: NSObject, CBPeripheralManagerDelegate {
final class BLEPeripheralManagerDelegate: NSObject, CBPeripheralManagerDelegate, @unchecked Sendable {

let didUpdateState = PassthroughSubject<CBManagerState, Never>()
let isReady = PassthroughSubject<Void, Never>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Combine
import CoreBluetooth

final class StandardBLEPeripheralManager: BLEPeripheralManager {
final class StandardBLEPeripheralManager: BLEPeripheralManager, @unchecked Sendable {

/// Implementation of the CBPeripheralManager.
public let manager: CBPeripheralManager
Expand Down
2 changes: 1 addition & 1 deletion Sources/BLECombineKit/Peripheral/BLEPeripheral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import CoreBluetooth
import Foundation

/// Interface definining the Bluetooth Peripheral that provides Combine APIs.
public protocol BLEPeripheral {
public protocol BLEPeripheral: Sendable {
/// Reference to the actual Bluetooth peripheral, via a wrapper.
var associatedPeripheral: CBPeripheralWrapper { get }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ typealias DidWriteValueForCharacteristicResult = (
peripheral: CBPeripheralWrapper, characteristic: CBCharacteristic, error: BLEError?
)

final class BLEPeripheralDelegate: NSObject {
final class BLEPeripheralDelegate: NSObject, @unchecked Sendable {

/// Subject for the name update callback.
let didUpdateName = PassthroughSubject<DidUpdateName, Never>()
Expand Down
18 changes: 12 additions & 6 deletions Sources/BLECombineKit/Peripheral/BLEPeripheralProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@

import Foundation

protocol BLEPeripheralProvider {
protocol BLEPeripheralProvider: Sendable {
func provide(
for peripheralWrapper: CBPeripheralWrapper
) -> BLETrackedPeripheral
}

final class StandardBLEPeripheralProvider: BLEPeripheralProvider {
final class StandardBLEPeripheralProvider: BLEPeripheralProvider, @unchecked Sendable {

private lazy var queue = DispatchQueue(
label: String(describing: StandardBLEPeripheralProvider.self),
attributes: .concurrent
)

private lazy var peripherals = [UUID: StandardBLEPeripheral]()
private var peripherals = [UUID: StandardBLEPeripheral]()

private weak var centralManager: BLECentralManager?
weak var centralManager: BLECentralManager?

init(centralManager: BLECentralManager?) {
self.centralManager = centralManager
Expand Down Expand Up @@ -55,9 +55,15 @@ final class StandardBLEPeripheralProvider: BLEPeripheralProvider {
centralManager: centralManager,
delegate: peripheralDelegate
)
queue.async(flags: .barrier) { [weak self] in
self?.peripherals[peripheralWrapper.identifier] = blePeripheral
let uncheckedPeripheralWrapper = UncheckedSendable(peripheralWrapper)
queue.sync(flags: .barrier) {
self.peripherals[uncheckedPeripheralWrapper.value.identifier] = blePeripheral
}
return blePeripheral
}
}

private struct UncheckedSendable<T>: @unchecked Sendable {
let value: T
init(_ value: T) { self.value = value }
}
2 changes: 1 addition & 1 deletion Sources/BLECombineKit/Peripheral/CBPeripheralWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import CoreBluetooth
import Foundation

public protocol CBPeripheralWrapper {
public protocol CBPeripheralWrapper: Sendable {
/// The CBCentralManager this interface wraps to.
/// Note that CBPeripheral conforms to CBPeripheralWrapper and this getter interface is a convenient way to avoid an expensive downcast. That is, if you need a fixed reference to the CBPeripheral object do not run `let validPeripheral = peripheral as? CBPeripheral`, simply run `let validPeripheral = peripheral.wrappedPeripheral` which will run significantly faster.
var wrappedPeripheral: CBPeripheral? { get }
Expand Down
5 changes: 4 additions & 1 deletion Sources/BLECombineKit/Peripheral/StandardBLEPeripheral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Combine
import CoreBluetooth

final class StandardBLEPeripheral: BLETrackedPeripheral {
final class StandardBLEPeripheral: BLETrackedPeripheral, @unchecked Sendable {

/// Subject used for tracking the lateset connection state.
let connectionState = CurrentValueSubject<Bool, Never>(false)
Expand All @@ -20,7 +20,10 @@ final class StandardBLEPeripheral: BLETrackedPeripheral {
/// Reference to the wrapper delegate used for tracking BLE events.
private let delegate: BLEPeripheralDelegate

// Following SE-0481, this will need to become `weak let` to conform with strict concurrency.
// See https://github.com/swiftlang/swift-evolution/blob/main/proposals/0481-weak-let.md
/// Reference to te BLECentralManager.
// nonisolated(unsafe)
private weak var centralManager: BLECentralManager?

/// Cancellable reference to the connect publisher.
Expand Down
4 changes: 2 additions & 2 deletions Sources/BLECombineKit/Service/BLEService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
//

import Combine
import CoreBluetooth
@preconcurrency import CoreBluetooth
import Foundation

public struct BLEService {
public struct BLEService: Sendable {
public let value: CBService
private let peripheral: BLEPeripheral

Expand Down
13 changes: 10 additions & 3 deletions Sources/BLECombineKit/Utils/Publisher+AsyncStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
// Copyright © 2024 Henry Serrano. All rights reserved.
//

import Combine
@preconcurrency import Combine

extension AnyPublisher {
extension AnyPublisher where Output: Sendable {
var asyncThrowingStream: AsyncThrowingStream<Output, Error> {
return AsyncThrowingStream { continuation in
let cancellable =
Expand All @@ -23,9 +23,16 @@ extension AnyPublisher {
} receiveValue: { data in
continuation.yield(data)
}

let uncheckedCancellable = UncheckedSendable(cancellable)
continuation.onTermination = { _ in
cancellable.cancel()
uncheckedCancellable.value.cancel()
}
}
}
}

private struct UncheckedSendable<T>: @unchecked Sendable {
let value: T
init(_ value: T) { self.value = value }
}
10 changes: 5 additions & 5 deletions Tests/BLECombineKitTests/Mocks/BLEPeripheralMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
// Copyright © 2020 Henry Serrano. All rights reserved.
//

import Combine
import CoreBluetooth
@preconcurrency import Combine
@preconcurrency import CoreBluetooth
import Foundation

@testable import BLECombineKit

struct SetNotifyValueWasCalledStackValue: Equatable {
struct SetNotifyValueWasCalledStackValue: Equatable, @unchecked Sendable {
let enabled: Bool
let characteristic: CBCharacteristic
}

final class MockBLEPeripheral: BLEPeripheral, BLETrackedPeripheral {
final class MockBLEPeripheral: BLEPeripheral, BLETrackedPeripheral, @unchecked Sendable {
let connectionState = CurrentValueSubject<Bool, Never>(false)
var associatedPeripheral: CBPeripheralWrapper

Expand Down Expand Up @@ -134,7 +134,7 @@ final class MockBLEPeripheral: BLEPeripheral, BLETrackedPeripheral {

}

final class MockCBPeripheralWrapper: CBPeripheralWrapper {
final class MockCBPeripheralWrapper: CBPeripheralWrapper, @unchecked Sendable {
var wrappedPeripheral: CBPeripheral?

var state = CBPeripheralState.connected
Expand Down
6 changes: 3 additions & 3 deletions Tests/BLECombineKitTests/Mocks/MockBLECentralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
// Copyright © 2020 Henry Serrano. All rights reserved.
//

import Combine
import CoreBluetooth
@preconcurrency import Combine
@preconcurrency import CoreBluetooth
import Foundation

@testable import BLECombineKit

final class MockBLECentralManager: BLECentralManager {
final class MockBLECentralManager: BLECentralManager, @unchecked Sendable {

private var _state = CurrentValueSubject<CBManagerState, Never>(CBManagerState.unknown)
var state: AnyPublisher<CBManagerState, Never> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

@testable import BLECombineKit

final class MockBLEPeripheralProvider: BLEPeripheralProvider {
final class MockBLEPeripheralProvider: BLEPeripheralProvider, @unchecked Sendable {
var buildBLEPeripheralWasCalledCount = 0
var blePeripheral: BLETrackedPeripheral?

Expand All @@ -23,7 +23,7 @@ final class MockBLEPeripheralProvider: BLEPeripheralProvider {
}

/// Internal only: Used for returning nil peripheral on multiple build calls
final class MockArrayBLEPeripheralBuilder: BLEPeripheralProvider {
final class MockArrayBLEPeripheralBuilder: BLEPeripheralProvider, @unchecked Sendable {
var buildBLEPeripheralWasCalledCount = 0
var blePeripherals = [BLETrackedPeripheral]()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// Copyright © 2021 Henry Serrano. All rights reserved.
//

import CoreBluetooth
@preconcurrency import CoreBluetooth
import Foundation

@testable import BLECombineKit

final class MockCBCentralManagerWrapper: CBCentralManagerWrapper {
final class MockCBCentralManagerWrapper: CBCentralManagerWrapper, @unchecked Sendable {
var wrappedManager: CBCentralManager?

var isScanning: Bool = false
Expand Down
9 changes: 5 additions & 4 deletions Tests/BLECombineKitTests/Mocks/MockPeripheralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
//

import BLECombineKit
import CoreBluetooth
@preconcurrency import CoreBluetooth
import Foundation

final class MockCBPeripheralManager: CBPeripheralManager {
final class MockCBPeripheralManager: CBPeripheralManager, @unchecked Sendable {
struct UpdateValueStackValue: Equatable {
let value: Data
let characteristic: CBMutableCharacteristic
Expand Down Expand Up @@ -53,15 +54,15 @@ final class MockCBPeripheralManager: CBPeripheralManager {
}
}

final class MockBLECentral: BLECentral {
final class MockBLECentral: BLECentral, @unchecked Sendable {
var associatedCentral: CBCentral?

var identifier = UUID()

var maximumUpdateValueLength: Int = 0
}

final class MockBLEATTRequest: BLEATTRequest {
final class MockBLEATTRequest: BLEATTRequest, @unchecked Sendable {
var associatedRequest: CBATTRequest?

var centralWrapper: BLECentral = MockBLECentral()
Expand Down
Loading