Skip to content

Commit 2f1490a

Browse files
NickKibishNick Kibysh
and
Nick Kibysh
authored
KVO Support (#96)
* added @objc specificator to 'isScanning' property to make it trackable by KVO * added @objc to internal property * change the nature of `isScanning` property: now it's not a computed value but dynamic property which gives an ability to track it using KVO. In `CBMCentralManagerNative` added observer that pass updates from `CBCentralManager.isScanning` to `CBMCentralManagerNative.isScanning` * added observer for peripheral state added `value` for async getting first value from peripheral stream * code cleanup --------- Co-authored-by: Nick Kibysh <nick.kibysh@gmail.com>
1 parent bc042dc commit 2f1490a

File tree

4 files changed

+37
-20
lines changed

4 files changed

+37
-20
lines changed

CoreBluetoothMock/CBMCentralManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ open class CBMCentralManager: NSObject {
9292

9393
/// Whether or not the central is currently scanning.
9494
@available(iOS 9.0, *)
95-
open var isScanning: Bool { return false }
95+
@objc dynamic open internal(set) var isScanning: Bool = false
9696

9797
/// The current authorization status for using Bluetooth.
9898
///

CoreBluetoothMock/CBMCentralManagerMock.swift

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ open class CBMCentralManagerMock: CBMCentralManager {
8181
// ...stop scanning if state changed to any other state
8282
// than `.poweredOn`. Also, forget all peripherals.
8383
if manager.state != .poweredOn {
84-
manager._isScanning = false
84+
manager.isScanning = false
8585
manager.scanFilter = nil
8686
manager.scanOptions = nil
8787
manager.peripherals.values.forEach { $0.closeManager() }
@@ -123,6 +123,7 @@ open class CBMCentralManagerMock: CBMCentralManager {
123123
/// - config: Advertisement configuration to start.
124124
/// - mock: The advertising mock peripheral.
125125
private static func startAdvertising(_ config: CBMAdvertisementConfig, for mock: CBMPeripheralSpec) {
126+
126127
// A valid advertising config is a single time advertisement (delay > 0),
127128
// or a periodic one (interval > 0) (or both - delayed periodic advertisement).
128129
// Not to be mistaken with "Periodic Advertisement" from Advertising Extension.
@@ -287,21 +288,17 @@ open class CBMCentralManagerMock: CBMCentralManager {
287288
CBMCentralManagerMock.managers.contains { $0.ref == self }
288289
}
289290
}
290-
/// A flag set to true when the manager is scanning for mock Bluetooth LE devices.
291-
private var _isScanning: Bool
292291

293292
// MARK: - Initializers
294293

295294
public init() {
296-
self._isScanning = false
297295
self.queue = DispatchQueue.main
298296
super.init(true)
299297
initialize()
300298
}
301299

302300
public init(delegate: CBMCentralManagerDelegate?,
303301
queue: DispatchQueue?) {
304-
self._isScanning = false
305302
self.queue = queue ?? DispatchQueue.main
306303
super.init(true)
307304
self.delegate = delegate
@@ -312,7 +309,6 @@ open class CBMCentralManagerMock: CBMCentralManager {
312309
public init(delegate: CBMCentralManagerDelegate?,
313310
queue: DispatchQueue?,
314311
options: [String : Any]?) {
315-
self._isScanning = false
316312
self.queue = queue ?? DispatchQueue.main
317313
super.init(true)
318314
self.delegate = delegate
@@ -652,9 +648,7 @@ open class CBMCentralManagerMock: CBMCentralManager {
652648
}
653649
return CBMCentralManagerMock.managerState
654650
}
655-
open override var isScanning: Bool {
656-
return _isScanning
657-
}
651+
658652

659653
@available(iOS, introduced: 13.0, deprecated: 13.1)
660654
@available(macOS, introduced: 10.15)
@@ -685,15 +679,15 @@ open class CBMCentralManagerMock: CBMCentralManager {
685679
options: [String : Any]? = nil) {
686680
// Central manager must be in powered on state.
687681
guard ensurePoweredOn() else { return }
688-
_isScanning = true
682+
isScanning = true
689683
scanFilter = serviceUUIDs
690684
scanOptions = options
691685
}
692686

693687
open override func stopScan() {
694688
// Central manager must be in powered on state.
695689
guard ensurePoweredOn() else { return }
696-
_isScanning = false
690+
isScanning = false
697691
scanFilter = nil
698692
scanOptions = nil
699693
peripherals.values.forEach { $0.wasScanned = false }
@@ -879,7 +873,7 @@ open class CBMCentralManagerMock: CBMCentralManager {
879873
/// This implementation will be used when creating peripherals by ``CBMCentralManagerMock``.
880874
///
881875
/// Unless required, this class should not be accessed directly, but rather by the common protocol ``CBMPeripheral``.
882-
open class CBMPeripheralMock: CBMPeer, CBMPeripheral {
876+
@objc open class CBMPeripheralMock: CBMPeer, CBMPeripheral {
883877
/// The parent central manager.
884878
private let manager: CBMCentralManagerMock
885879
/// The dispatch queue to call delegate methods on.
@@ -923,7 +917,7 @@ open class CBMPeripheralMock: CBMPeer, CBMPeripheral {
923917
return _canSendWriteWithoutResponse
924918
}
925919
open private(set) var ancsAuthorized: Bool = false
926-
open fileprivate(set) var state: CBMPeripheralState = .disconnected
920+
@objc dynamic open fileprivate(set) var state: CBMPeripheralState = .disconnected
927921
open private(set) var services: [CBMService]? = nil
928922

929923
// MARK: Initializers

CoreBluetoothMock/CBMCentralManagerNative.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import CoreBluetooth
3535
///
3636
/// This manager can only interact with physical Bluetooth LE devices.
3737
public class CBMCentralManagerNative: CBMCentralManager {
38-
private var manager: CBCentralManager!
38+
var observation: NSKeyValueObservation?
39+
@objc dynamic private var manager: CBCentralManager!
3940
private var wrapper: CBCentralManagerDelegate!
4041
private var peripherals: [UUID : CBMPeripheralNative] = [:]
4142

@@ -140,7 +141,12 @@ public class CBMCentralManagerNative: CBMCentralManager {
140141

141142
@available(iOS 9.0, *)
142143
public override var isScanning: Bool {
143-
return manager.isScanning
144+
get {
145+
manager.isScanning
146+
}
147+
set {
148+
149+
}
144150
}
145151

146152
@available(iOS, introduced: 13.0, deprecated: 13.1)
@@ -168,6 +174,8 @@ public class CBMCentralManagerNative: CBMCentralManager {
168174
self.wrapper = CBMCentralManagerDelegateWrapper(self)
169175
self.manager = CBCentralManager()
170176
self.manager.delegate = wrapper
177+
178+
self.addManagerObserver()
171179
}
172180

173181
public init(delegate: CBMCentralManagerDelegate?,
@@ -176,6 +184,8 @@ public class CBMCentralManagerNative: CBMCentralManager {
176184
self.wrapper = CBMCentralManagerDelegateWrapper(self)
177185
self.manager = CBCentralManager(delegate: wrapper, queue: queue)
178186
self.delegate = delegate
187+
188+
self.addManagerObserver()
179189
}
180190

181191
@available(iOS 7.0, *)
@@ -189,6 +199,8 @@ public class CBMCentralManagerNative: CBMCentralManager {
189199
CBMCentralManagerDelegateWrapper(self)
190200
self.manager = CBCentralManager(delegate: wrapper, queue: queue, options: options)
191201
self.delegate = delegate
202+
203+
self.addManagerObserver()
192204
}
193205

194206
public override func scanForPeripherals(withServices serviceUUIDs: [CBMUUID]?,
@@ -238,6 +250,15 @@ public class CBMCentralManagerNative: CBMCentralManager {
238250
manager.registerForConnectionEvents(options: options)
239251
}
240252
#endif
253+
254+
/// Add observer for `\.CBCentralManager.isScanning` and change `Self.isScanning` correspondingly.
255+
private func addManagerObserver() {
256+
observation = observe(\.manager?.isScanning, options: [.old, .new]) { _, change in
257+
change.newValue?.flatMap { [weak self] new in
258+
self?.isScanning = new
259+
}
260+
}
261+
}
241262
}
242263

243264
/// A native implementation of ``CBMPeripheral`` that will proxy all requests to an underlying `CBPeripheral`.
@@ -505,7 +526,7 @@ public class CBMPeripheralNative: CBMPeer, CBMPeripheral {
505526
}
506527
#endif
507528

508-
fileprivate let peripheral: CBPeripheral
529+
public let peripheral: CBPeripheral
509530

510531
fileprivate init(_ peripheral: CBPeripheral) {
511532
self.peripheral = peripheral

CoreBluetoothMock/CBMPeripheralPreview.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import CoreBluetooth
3939
/// All ``CBMService``s are available immediately, without the need for service discovery.
4040
/// Bluetooth LE operations are NO OP. The device does not need to be scanned to
4141
/// be retrievable by any ``CBMCentralManagerMock`` instance.
42-
open class CBMPeripheralPreview: CBMPeripheral {
42+
@objc open class CBMPeripheralPreview: NSObject, CBMPeripheral {
4343
private let mock: CBMPeripheralSpec
4444

4545
public let identifier: UUID
@@ -49,7 +49,7 @@ open class CBMPeripheralPreview: CBMPeripheral {
4949
public var services: [CBMService]?
5050

5151
public var delegate: CBMPeripheralDelegate?
52-
public internal(set) var state: CBMPeripheralState
52+
@objc dynamic public internal(set) var state: CBMPeripheralState
5353

5454
public let canSendWriteWithoutResponse: Bool = true
5555
public let ancsAuthorized: Bool = false
@@ -63,6 +63,7 @@ open class CBMPeripheralPreview: CBMPeripheral {
6363
self.mock = mock
6464
self.identifier = mock.identifier
6565
self.state = state
66+
super.init()
6667
self.services = mock.services?.map { CBMService(copy: $0, for: self) }
6768
CBMCentralManagerMock.registerForPreviews(self)
6869
}
@@ -118,7 +119,7 @@ open class CBMPeripheralPreview: CBMPeripheral {
118119
fatalError("Not available")
119120
}
120121
}
121-
122+
/*
122123
extension CBMPeripheralPreview: Hashable {
123124

124125
public static func == (lhs: CBMPeripheralPreview, rhs: CBMPeripheralPreview) -> Bool {
@@ -130,3 +131,4 @@ extension CBMPeripheralPreview: Hashable {
130131
}
131132

132133
}
134+
*/

0 commit comments

Comments
 (0)