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
10 changes: 4 additions & 6 deletions LibreLoop/LibreLoopCGMManager/LibreLoopCGMManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public final class LibreLoopCGMManager: CGMManager {
recentSamples = []
hasIssuedReScanAlert = false
lastSensorAttention = nil
retractAllAlerts()
Task { await retractAllAlerts() }
// Emit .sensorEnd before we blank state so the event's
// deviceIdentifier still resolves to the session that's ending.
// Matches the .sensorStart we emitted at pairing time so Loop's
Expand Down Expand Up @@ -848,7 +848,7 @@ public final class LibreLoopCGMManager: CGMManager {
}
}

public func delete(completion: @escaping () -> Void) {
public func delete() async {
// The central is shared and outlives this manager, so a deleted manager
// must stop driving it — otherwise its event listener + reconnect loop
// keep issuing connect/scan on the shared central and fight whatever
Expand Down Expand Up @@ -876,10 +876,8 @@ public final class LibreLoopCGMManager: CGMManager {
scanner.cancelConnection(peripheral)
}
}
// Retract while the delegate reference is still good: notifying it
// releases this manager.
retractAllAlerts()
notifyDelegateOfDeletion(completion: completion)
await retractAllAlerts()
await cgmManagerDelegate?.cgmManagerWantsDeletion(self)
}
}

Expand Down
10 changes: 3 additions & 7 deletions LibreLoop/Pairing/LibreLoopCGMManager+Pairing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,14 @@ extension LibreLoopCGMManager {
return activatedAt
}

/// Captures the delegate strongly so the retraction still lands if this
/// manager is released immediately afterwards.
func retractAllAlerts() {
func retractAllAlerts() async {
let delegate = cgmManagerDelegate
let identifiers = Self.allAlertIdentifiers.map {
Alert.Identifier(managerIdentifier: pluginIdentifier, alertIdentifier: $0)
}
llog("alerts: retracting \(identifiers.count) identifier(s)")
Task {
for identifier in identifiers {
await delegate?.retractAlert(identifier: identifier)
}
for identifier in identifiers {
await delegate?.retractAlert(identifier: identifier)
}
}

Expand Down
7 changes: 3 additions & 4 deletions LibreLoopTests/LibreLoopCGMManagerStateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,16 @@ final class LibreLoopAlertRetractionTests: XCTestCase {
})
}

func testDeleteRetractsEveryAlertAndNotifiesDelegate() {
func testDeleteRetractsEveryAlertBeforeNotifyingDelegate() async {
let retractions = expectRetractions()
let deletion = expectation(description: "delegate notified of deletion")
let completed = expectation(description: "delete completion called")
let delegate = RetractionRecordingDelegate(retractionExpectation: retractions,
deletionExpectation: deletion)
let manager = makeManager(delegate: delegate)

manager.delete { completed.fulfill() }
await manager.delete()

wait(for: [retractions, deletion, completed], timeout: 5)
await fulfillment(of: [retractions, deletion], timeout: 0, enforceOrder: true)
assertRetractedEverything(delegate)
}

Expand Down
13 changes: 5 additions & 8 deletions LibreLoopUI/LibreLoopCGMManager/LibreLoopUICoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,12 @@ final class LibreLoopUICoordinator: UINavigationController, CGMManagerOnboarding
self.completionDelegate?.completionNotifyingDidComplete(self)
},
replaceSensor: { [weak self] in self?.startReplacementPairing() },
// `delete` runs the manager's teardown and notifies the delegate
// itself; `notifyDelegateOfDeletion` alone skips the teardown.
deleteCGM: { [weak self] in
self?.cgmManager?.delete {
DispatchQueue.main.async {
guard let self = self else { return }
self.completionDelegate?.completionNotifyingDidComplete(self)
self.dismiss(animated: true)
}
Task {
await self?.cgmManager?.delete()
guard let self = self else { return }
self.completionDelegate?.completionNotifyingDidComplete(self)
self.dismiss(animated: true)
}
}
)
Expand Down