1+ // installAppStream.swift
12import Foundation
23import Combine
34import IDeviceSwift
45
5- public func installAppWithStatus( from ipaURL: URL , viewModel: InstallerViewModel ) async throws {
6- var cancellables = Set < AnyCancellable > ( )
6+ public func installAppWithStatusStream( from ipaURL: URL ) -> AsyncThrowingStream < InstallerStatusViewModel . InstallerStatus , Error > {
7+ AsyncThrowingStream { continuation in
8+ // create a local view model for the InstallationProxy internals
9+ let localVM = InstallerStatusViewModel ( )
10+ var cancellables = Set < AnyCancellable > ( )
711
8- viewModel. $uploadProgress
9- . sink { progress in
10- DispatchQueue . main. async {
11- let percent = Int ( progress * 100 )
12- viewModel. status = . uploading( percent: percent)
13- viewModel. progress = 0.5 + ( progress * 0.25 )
12+ // Observe progress and forward into the AsyncStream
13+ localVM. $uploadProgress
14+ . sink { progress in
15+ let pct = Int ( progress * 100 )
16+ continuation. yield ( . uploading( percent: pct) )
1417 }
15- }
16- . store ( in: & cancellables)
18+ . store ( in: & cancellables)
1719
18- viewModel. $installProgress
19- . sink { progress in
20- DispatchQueue . main. async {
21- let percent = Int ( progress * 100 )
22- viewModel. status = . installing( percent: percent)
23- viewModel. progress = 0.75 + ( progress * 0.25 )
20+ localVM. $installProgress
21+ . sink { progress in
22+ let pct = Int ( progress * 100 )
23+ continuation. yield ( . installing( percent: pct) )
2424 }
25- }
26- . store ( in: & cancellables)
25+ . store ( in: & cancellables)
2726
28- // DON'T reassign viewModel.status inside a sink on viewModel.$status —
29- // that just creates a loop. If you want to react to status changes elsewhere,
30- // observe it and map to UI strings there.
27+ // Optionally map other status changes
28+ localVM. $status
29+ . sink { status in
30+ continuation. yield ( status)
31+ }
32+ . store ( in: & cancellables)
3133
32- let installer = InstallationProxy ( viewModel: viewModel)
33- try await installer. install ( at: ipaURL)
34+ Task {
35+ do {
36+ let installer = InstallationProxy ( viewModel: localVM)
37+ try await installer. install ( at: ipaURL)
38+ continuation. yield ( . success)
39+ continuation. finish ( )
40+ } catch {
41+ // forward failure as an enum + error finishing
42+ continuation. yield ( . failure( message: error. localizedDescription) )
43+ continuation. finish ( throwing: error)
44+ }
45+ }
46+
47+ // on stream termination, cancel Combine sinks
48+ continuation. onTermination = { @Sendable _ in
49+ cancellables. forEach { $0. cancel ( ) }
50+ }
51+ }
3452}
0 commit comments