Skip to content

Commit 6b7ed98

Browse files
committed
CaptureService: implemented clean exit for all notification loops
1 parent 36742eb commit 6b7ed98

File tree

1 file changed

+16
-3
lines changed

1 file changed

+16
-3
lines changed

PhotoCamera/Sources/CaptureService.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//
66

77
import Foundation
8+
import Combine
89
@preconcurrency import AVFoundation
910
import UIKit.UIImage
1011

@@ -46,6 +47,12 @@ actor CaptureService {
4647
/// A serial dispatch queue to use for capture control actions.
4748
private let sessionQueue = DispatchSerialQueue(label: "com.melikyan.CameraView")
4849

50+
/// Cancel the async notification loops using this collection; internal.
51+
private var cancellables = Set<AnyCancellable>()
52+
53+
/// In addition to `cancelables`, subject area change notifications should be reset every time an input device is changed.
54+
private var subjectAreaChangeTask: Task<Void, Never>?
55+
4956
/// Sets the session queue as the actor's executor.
5057
nonisolated var unownedExecutor: UnownedSerialExecutor {
5158
sessionQueue.asUnownedSerialExecutor()
@@ -251,16 +258,19 @@ actor CaptureService {
251258
private func observeSubjectAreaChanges(of device: AVCaptureDevice) {
252259
// Cancel the previous observation task.
253260
subjectAreaChangeTask?.cancel()
254-
subjectAreaChangeTask = Task { [weak self] in
261+
let task = Task { [weak self] in
255262
// Signal true when this notification occurs.
256263
for await _ in NotificationCenter.default.notifications(named: AVCaptureDevice.subjectAreaDidChangeNotification, object: device).compactMap({ _ in true }) {
257264
guard let self else { return }
258265
// Perform a system-initiated focus and expose.
259266
try? await focusAndExpose(at: CGPoint(x: 0.5, y: 0.5), isUserInitiated: false)
260267
}
261268
}
269+
subjectAreaChangeTask = task
270+
AnyCancellable {
271+
task.cancel()
272+
}.store(in: &cancellables)
262273
}
263-
private var subjectAreaChangeTask: Task<Void, Never>?
264274

265275
private func focusAndExpose(at devicePoint: CGPoint, isUserInitiated: Bool) throws {
266276
// Configure the current device.
@@ -304,7 +314,7 @@ actor CaptureService {
304314

305315
/// Observe capture-related notifications.
306316
private func observeNotifications() {
307-
Task { [weak self] in
317+
let task = Task { [weak self] in
308318
for await error in NotificationCenter.default.notifications(named: AVCaptureSession.runtimeErrorNotification)
309319
.compactMap({ $0.userInfo?[AVCaptureSessionErrorKey] as? AVError }) {
310320
// If the system resets media services, the capture session stops running.
@@ -316,6 +326,9 @@ actor CaptureService {
316326
}
317327
}
318328
}
329+
AnyCancellable {
330+
task.cancel()
331+
}.store(in: &cancellables)
319332
}
320333

321334
// MARK: - Photo capture

0 commit comments

Comments
 (0)