refactor: completely remove internal lastValidBattery dictionary for zero-cache live updates
This commit is contained in:
parent
ec302446d9
commit
5ad28b5bd9
1 changed files with 21 additions and 25 deletions
|
|
@ -17,7 +17,6 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
@Published public var isConnected: Bool = false
|
@Published public var isConnected: Bool = false
|
||||||
|
|
||||||
private var timerSource: DispatchSourceTimer?
|
private var timerSource: DispatchSourceTimer?
|
||||||
private var lastValidBattery: [String: Int] = [:]
|
|
||||||
|
|
||||||
private typealias IntBatteryGetter = @convention(c) (AnyObject, Selector) -> Int
|
private typealias IntBatteryGetter = @convention(c) (AnyObject, Selector) -> Int
|
||||||
private typealias BoolGetter = @convention(c) (AnyObject, Selector) -> Bool
|
private typealias BoolGetter = @convention(c) (AnyObject, Selector) -> Bool
|
||||||
|
|
@ -35,7 +34,7 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
stopTracking()
|
stopTracking()
|
||||||
updateDeviceList()
|
updateDeviceList()
|
||||||
|
|
||||||
// Kernel-level DispatchSourceTimer: Immune to RunLoop pauses or UI tracking modes
|
// Kernel-level DispatchSourceTimer: strictly 1 second interval, no caching
|
||||||
let timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.main)
|
let timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.main)
|
||||||
timer.schedule(deadline: .now() + 1.0, repeating: 1.0, leeway: .milliseconds(50))
|
timer.schedule(deadline: .now() + 1.0, repeating: 1.0, leeway: .milliseconds(50))
|
||||||
timer.setEventHandler { [weak self] in
|
timer.setEventHandler { [weak self] in
|
||||||
|
|
@ -55,18 +54,23 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupBluetoothListeners() {
|
private func setupBluetoothListeners() {
|
||||||
NotificationCenter.default.addObserver(
|
let notificationNames = [
|
||||||
self,
|
"IOBluetoothDeviceWasConnectedNotification",
|
||||||
selector: #selector(handleBluetoothChange),
|
"IOBluetoothDeviceWasDisconnectedNotification",
|
||||||
name: NSNotification.Name("IOBluetoothDeviceWasConnectedNotification"),
|
"IOBluetoothDeviceNotification",
|
||||||
object: nil
|
"IOBluetoothDeviceServicesResolvedNotification",
|
||||||
)
|
"IOBluetoothDeviceNameChangedNotification",
|
||||||
NotificationCenter.default.addObserver(
|
"IOBluetoothHandsFreeDeviceDidUpdateNotification"
|
||||||
self,
|
]
|
||||||
selector: #selector(handleBluetoothChange),
|
|
||||||
name: NSNotification.Name("IOBluetoothDeviceWasDisconnectedNotification"),
|
for name in notificationNames {
|
||||||
object: nil
|
NotificationCenter.default.addObserver(
|
||||||
)
|
self,
|
||||||
|
selector: #selector(handleBluetoothChange),
|
||||||
|
name: NSNotification.Name(name),
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleBluetoothChange() {
|
@objc private func handleBluetoothChange() {
|
||||||
|
|
@ -84,11 +88,10 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
var updatedList: [BluetoothDeviceModel] = []
|
var updatedList: [BluetoothDeviceModel] = []
|
||||||
let activeAudioName = getActiveCoreAudioDeviceName()
|
let activeAudioName = getActiveCoreAudioDeviceName()
|
||||||
|
|
||||||
// 1. Scan IOBluetooth paired devices
|
// 1. Scan IOBluetooth paired devices directly (zero caching)
|
||||||
if let pairedDevices = IOBluetoothDevice.pairedDevices() as? [IOBluetoothDevice] {
|
if let pairedDevices = IOBluetoothDevice.pairedDevices() as? [IOBluetoothDevice] {
|
||||||
for pairedDev in pairedDevices {
|
for pairedDev in pairedDevices {
|
||||||
let address = pairedDev.addressString ?? UUID().uuidString
|
let address = pairedDev.addressString ?? UUID().uuidString
|
||||||
// Re-instantiate device to bypass any internal property caching
|
|
||||||
let device = IOBluetoothDevice(addressString: address) ?? pairedDev
|
let device = IOBluetoothDevice(addressString: address) ?? pairedDev
|
||||||
let name = device.nameOrAddress ?? pairedDev.nameOrAddress ?? "Unknown Device"
|
let name = device.nameOrAddress ?? pairedDev.nameOrAddress ?? "Unknown Device"
|
||||||
|
|
||||||
|
|
@ -130,7 +133,7 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. IOKit Registry enrich
|
// 2. IOKit Registry live enrich (zero caching)
|
||||||
enrichFromIORegistry(devices: &updatedList)
|
enrichFromIORegistry(devices: &updatedList)
|
||||||
|
|
||||||
// 3. Sort: Connected audio first
|
// 3. Sort: Connected audio first
|
||||||
|
|
@ -247,8 +250,6 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
?? (fallbackDevice != nil ? getBatteryValue(from: fallbackDevice!, selectorName: selectorName) : nil)
|
?? (fallbackDevice != nil ? getBatteryValue(from: fallbackDevice!, selectorName: selectorName) : nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = model.address
|
|
||||||
|
|
||||||
if isMulti {
|
if isMulti {
|
||||||
model.leftBattery = query(selectorName: "batteryPercentLeft")
|
model.leftBattery = query(selectorName: "batteryPercentLeft")
|
||||||
model.rightBattery = query(selectorName: "batteryPercentRight")
|
model.rightBattery = query(selectorName: "batteryPercentRight")
|
||||||
|
|
@ -262,12 +263,7 @@ public final class BluetoothTracker: ObservableObject {
|
||||||
?? query(selectorName: "batteryPercentCombined")
|
?? query(selectorName: "batteryPercentCombined")
|
||||||
?? query(selectorName: "batteryPercentLeft")
|
?? query(selectorName: "batteryPercentLeft")
|
||||||
|
|
||||||
if let single = single {
|
model.singleBattery = single
|
||||||
lastValidBattery[addr] = single
|
|
||||||
model.singleBattery = single
|
|
||||||
} else if let cached = lastValidBattery[addr] {
|
|
||||||
model.singleBattery = cached
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue