Customize handling of asynchronous events by combining event-processing operators using Combine.

Combine Documentation

Posts under Combine tag

28 Posts
Sort by:
Post not yet marked as solved
2 Replies
244 Views
I am maintaining a macOS app, a GUI on top of a command line tool. A Process() object is used to kick off the command line tool with arguments. And completion handlers are triggered for post actions when command line tool is completed. My question is: I want to refactor the process to use async and await, and not use completion handlers. func execute(command: String, arguments:[String]) -> async { let task = Process() task.launchPath = command task.arguments = arguments ... do { try task.run() } catch let e { let error = e propogateerror(error: error) } ... } ... and like this in calling the process await execute(..) Combine is used to monitor the ProcessTermination: NotificationCenter.default.publisher( for: Process.didTerminateNotification) .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .sink { _ in ..... // Release Combine subscribers self.subscriptons.removeAll() }.store(in: &subscriptons) Using Combine works fine by using completion handlers, but not by refactor to async. What is the best way to refactor the function? I know there is a task.waitUntilExit(), but is this 100% bulletproof? Will it always wait until external task is completed?
Posted
by thomaeve.
Last updated
.
Post not yet marked as solved
2 Replies
128 Views
I'm just putting this here for visibility, I already submitted FB13688825. If you say this: Task { for await tracks in avPlayerItem.publisher(for: \.tracks, options: [.initial]).values { print("*** fired with: \(tracks.description)") } } ...it fires once with: "*** fired with: []" If you say this: avPlayerItem.publisher(for: \.tracks).sink { [weak self] tracks in print("*** fired with: \(tracks.description)") }.store(in: &subscriptions) ...you get, as expected, multiple fires, most with data in them such as: *** fired with: [<AVPlayerItemTrack: 0x10a9869a0, assetTrack = <AVAssetTrack: 0x10a9869f0... I think it's a bug but I'm just going to go back to the "old way" for now. No emergency.
Posted
by Suges.
Last updated
.
Post not yet marked as solved
2 Replies
1.7k Views
Hi, I'm trying to use async/await for KVO and it seems something is broken. For some reason, it doesn't go inside for in body when I'm changing the observed property. import Foundation import PlaygroundSupport class TestObj: NSObject {   @objc dynamic var count = 0 } let obj = TestObj() Task {   for await value in obj.publisher(for: \.count).values {     print(value)   } } Task.detached {   try? await Task.sleep(for: .microseconds(100))   obj.count += 1 } Task.detached {   try? await Task.sleep(for: .microseconds(200))   obj.count += 1 } PlaygroundPage.current.needsIndefiniteExecution = true Expected result: 0, 1, 2 Actual result: 0 Does anyone know what is wrong here?
Posted
by sviat_sem.
Last updated
.
Post marked as solved
1 Replies
230 Views
class MyModel: ObservableObject { @Published var selection = FamilyActivitySelection() init() { $selection.sink { newSelection in print(newSelection) } } } class MyView: View { @StateObject var model = MyModel() // some body // .... // my method func removeToken(token: ApplicationToken) { model.selection.applicationTokens.remove(token) } } I am using the above code. When I call removeToken, the callback from the sink (which is registered in init() of MyModel) is called without any changes. newSelection still contains the token that I removed. Currently, I am using the additional code below to work around the problem. .onChange(of: model.selection.applicationTokens) { newSet in model.selection.applicationTokens = newSet } Should I use the workaround solution, or am I missing something?
Posted
by wurikiji.
Last updated
.
Post not yet marked as solved
1 Replies
313 Views
It feels like this should be easy, but I'm having conceptual problems about how to do this. Any help would be appreciated. I have a sample app below that works exactly as expected. I'm able to use the Slider and Stepper to generate inputs to a function that uses CoreImage filters to manipulate my input image. This all works as expected, but it's doing some O(n) CI work on the main thread, and I want to move it to a background thread. I'm pretty sure this can be done using combine, here is the pseudo code I imagine would work for me: func doStuff() { // subscribe to options changes // .receive on background thread // .debounce // .map { model.inputImage.combine(options: $0) // return an object on the main thread. // update an @Published property? } Below is the POC code for my project. Any guidance as to where I should use combine to do this would be greatly appreciate. (Also, please let me know if you think combine is not the best way to tackle this. I'd be open to alternative implementations.) struct ContentView: View { @State private var options = CombineOptions.basic @ObservedObject var model = Model() var body: some View { VStack { Image(uiImage: enhancedImage) .resizable() .aspectRatio(contentMode: .fit) Slider(value: $options.scale) Stepper(value: $options.numberOfImages, label: { Text("\(options.numberOfImages)")}) } } private var enhancedImage: UIImage { return model.inputImage.combine(options: options) } } class Model: ObservableObject { let inputImage: UIImage = UIImage.init(named: "IMG_4097")! } struct CombineOptions: Codable, Equatable { static let basic: CombineOptions = .init(scale: 0.3, numberOfImages: 10) var scale: Double var numberOfImages: Int }
Posted Last updated
.
Post marked as solved
7 Replies
789 Views
Given that SwiftUI and modern programming idioms promote asynchronous activity, and observing a data model and reacting to changes, I wonder why it's so cumbersome in Swift at this point. Like many, I have run up against the problem where you perform an asynchronous task (like fetching data from the network) and store the result in a published variable in an observed object. This would appear to be an extremely common scenario at this point, and indeed it's exactly the one posed in question after question you find online about this resulting error: Publishing changes from background threads is not allowed Then why is it done? Why aren't the changes simply published on the main thread automatically? Because it isn't, people suggest a bunch of workarounds, like making the enclosing object a MainActor. This just creates a cascade of errors in my application; but also (and I may not be interpreting the documentation correctly) I don't want the owning object to do everything on the main thread. So the go-to workaround appears to be wrapping every potentially problematic setting of a variable in a call to DispatchQueue.main. Talk about tedious and error-prone. Not to mention unmaintainable, since I or some future maintainer may be calling a function a level or two or three above where a published variable is actually set. And what if you decide to publish a variable that wasn't before, and now you have to run around checking every potential change to it? Is this not a mess?
Posted Last updated
.
Post not yet marked as solved
3 Replies
3.8k Views
I am working on a library, a Swift package. We have quite a few properties on various classes that can change and we think the @Published property wrapper is a good way to annotate these properties as it offers a built-in way to work with SwiftUI and also Combine. Many of our properties can change on background threads and we've noticed that we get a purple runtime issue when setting the value from a background thread. This is a bit problematic for us because the state did change on a background thread and we need to update it at that time. If we dispatch it to the main queue and update it on the next iteration, then our property state doesn't match what the user expects. Say they "load" or "start" something asynchronously, and that finishes, the status should report "loaded" or "started", but that's not the case if we dispatch it to the main queue because that property doesn't update until the next iteration of the run loop. There also isn't any information in the documentation for @Published that suggests that you must update it on the main thread. I understand why SwiftUI wants it on the main thread, but this property wrapper is in the Combine framework. Also it seems like SwiftUI internally could ask to receive the published updates on the main queue and @Published shouldn't enforce a specific thread. One thing we are thinking about doing is writing our own property wrapper, but that doesn't seem to be ideal for SwiftUI integration and it's one more property wrapper that users of our package would need to be educated about. Any thoughts on direction? Is there anyway to break @Published from the main thread?
Posted
by rolson.
Last updated
.
Post not yet marked as solved
2 Replies
321 Views
Following this Apple Article, I copied their code over for observePlayingState(). The only difference I am using @Observable instead of ObservableObject and @Published for var isPlaying. We get a bit more insight after removing the $ symbol, leading to a more telling error of: Cannot convert value of type 'Bool' to expected argument type 'Published.Publisher' Is there anyway to get this working with @Observable?
Posted
by anciltech.
Last updated
.
Post not yet marked as solved
0 Replies
577 Views
Hi all, I have some code that works on iOS 14 up to 16 and now suddenly doesn't work on iOS 17 so I guess it's a regression. Here's a snippet of a minimal reproducible example. SubView is supposed to update correctly when the button is pressed but that's not happening on iOS 17. onChange is not called either. struct ContentView: View { @ObservedObject var viewModel: ViewModel var body: some View { SubViewContainer(wrapper: $viewModel.wrapper) } } struct SubViewContainer: View { @Binding var wrapper: ValueWrapper var body: some View { SubView(value: $wrapper.value) } } struct SubView: View { @Binding var value: Bool var body: some View { Button(action: { value.toggle() }) { Text(value ? "true" : "false") } } } class ViewModel: ObservableObject { @Published var wrapper = ValueWrapper() } class ValueWrapper: ObservableObject { @Published var value = true } Any clue what is going on?
Posted
by mattiahv.
Last updated
.
Post not yet marked as solved
0 Replies
336 Views
I have a regular app and an app with LSUIElement=YES. Both have Swift app lifecycle main entry points. Both have an app group ".com.company.app". Both can read and write prefs and see each other's values. But I can't for the life of me get changes from one app to notify the other. At first I tried NotificationCenter, but then learned (thanks to this thread) that you have to use KVO or Combine. I tried both. Both get the initial value, but never see subsequent changes. Combine seems to just wrap KVO, looking at the stack trace. I'm subscribing to updates like this: let defaults = UserDefaults(suiteName: "<TEAMID>.com.company.app")! defaults .publisher(for: \.enabled) .handleEvents(receiveOutput: { enabled in print("Enabled is now: \(enabled)") }) .sink { _ in } .store(in: &subs) … extension UserDefaults { @objc var enabled: Bool { get { return bool(forKey: "Enabled") } set { set(newValue, forKey: "Enabled") } } } I'm writing to prefs like this: let defaults = UserDefaults(suiteName: "<TEAMID>.com.company.app")! defaults.set(enabled, forKey: "Enabled") Am I missing something?
Posted
by JetForMe.
Last updated
.
Post not yet marked as solved
1 Replies
378 Views
Using ImageCaptureCore, to send PTP devices to cameras via tether, I noticed that all of my Nikon cameras can take up to an entire minute for PTP events to start logging. My Canons and Sonys are ready instantly. Any idea why? I use the ICDeviceBrowser to browse for cameras, and then request to open the session. According to the docs, it says it's ready after it enumerates its objects? If that's the case, is there a way to bypass that? Even on an empty SD card it's slow.
Posted Last updated
.
Post not yet marked as solved
1 Replies
947 Views
That may not be the best way to explain it. Essentially, I'm requesting data from Reddit and storing it in an object called Request. This object has a timestamp and an array of objects called Post. Everything was working fine until I started to add some code to filter the post that were being fetched from reddit. extension [Request] { func insert(request: Request, in context: ModelContext) { if self.count == 0 { context.logMessage("There are no existing request") context.insert(request) context.logMessage("Request saved") }else { print(request) // No crash print(request.timestamp) // No crash print(request.posts) // Causes a crash } } } When it does crash, it points to this code inside the SwiftData model. This code seems to be something within SwiftData. I didn't write any of this. { @storageRestrictions(accesses: _$backingData, initializes: _posts) init(initialValue) { _$backingData.setValue(forKey: \.posts, to: initialValue) _posts = _SwiftDataNoType() } get { _$observationRegistrar.access(self, keyPath: \.posts) return self.getValue(forKey: \.posts) } set { _$observationRegistrar.withMutation(of: self, keyPath: \.posts) { self.setValue(forKey: \.posts, to: newValue) } } } It has the error at the return self.getValue() line: Thread 5: EXC_BREAKPOINT (code=1, subcode=0x2406965c4) This is the sequence that occurs: View is loaded Checks if it should load a new request If it should, it calls this function private func requestNewData() { redditService.fetchRedditAllData(completion: { result in DispatchQueue.main.async { switch result { case .success(let newRequest): modelContext.logMessage("Successfully retreived and decoded data from Reddit") // Log the success //modelContext.insert(newRequest) requests.insert(request: newRequest, in: modelContext) case .failure: modelContext.logMessage("Failed to retrieve and decode data from Reddit") } } }) } The code for the fetch function is here: func fetchRedditAllData(completion: @escaping (Result<Request, Error>) -> Void) { // Try to construct the URL and return if it fails guard let url = URL(string: RedditRequests.all) else { context.logMessage("Failed to contruct the url for r/all") return } // Try to retrieve data from the URL session.dataTask(with: url, completionHandler: { data, _, error in // If it fails, log the failure if let error = error { self.context.logMessage("Failed to retrieve data from the r/all URL.\n\(error.localizedDescription)") } else { // If it doesn't fail, try to decode the data do { let data = data ?? Data() // Get data let response = try self.decoder.decode(Request.self, from: data) // Decode JSON into Request model completion(.success(response)) // Return response (request) if successful self.context.logMessage("Decoded data") } catch { completion(.failure(error)) self.context.logMessage("Failed to decode data into a Request.\n\(error.localizedDescription)") } } }).resume() } If I don't try to access request.posts before saving it to the context, it works fine. It will fetch the data and store it to the phone and then display it on the phone. When I try to access request.posts to do some filtering, it crashes. Does anyone have any ideas?
Posted Last updated
.
Post not yet marked as solved
1 Replies
1.1k Views
This code crashes ("Unexpectedly found nil while unwrapping an Optional value") import Combine class Receiver { &#9;&#9;var value: Int! &#9;&#9;var cancellables = Set<AnyCancellable>([]) &#9;&#9;init(_ p: AnyPublisher<Int,Never>) { &#9;&#9;&#9;&#9;p.assign(to: \.value, on: self).store(in: &cancellables) &#9;&#9;} } let receiver = Receiver(Just(5).eraseToAnyPublisher()) It does not crash if I use p.sink { self.value = $0 }.store(in: &cancellables) instead of the assign, and it does not crash if I do not use an optional for the value-property. To me this looks like a bug in Swift's constructor code, but maybe I am overlooking something?
Posted
by kristofv.
Last updated
.
Post not yet marked as solved
1 Replies
613 Views
I've made a small reproducing example app to demonstrate this issue. Just create an iOS App project and replace the App and ContentView files with the following code. The app works as expected when running it in a simulator or on a device but the SwiftUI Preview will get stuck showing only the loading state, even though the print statement in the View's body has printed viewModel.state: showingContent(SwiftUITest.ViewModel.Content(text: "Hello world!", reloadButtonTitle: "Reload"))to the console. When stuck showing the loading state (an animated ProgressView), If I change .padding(.all, 12) to e.g. .padding(.all, 2) or vice versa, then that will make the Preview render the expected content. Also, if I tap the Reload-button, it will not show the ProgressView for 2 seconds as expected (and as the app will do when run in a simulator or on a device), instead it will just show a white screen, and the same workaround (changing the padding amount) can be used to make the it render the expected content. Can other people reproduce this behavior, is it a known bug, or am I doing something wrong? TestApp.swift import SwiftUI @main struct SwiftUITestApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: ViewModel( contentLoader: { try! await Task.sleep(nanoseconds: 2_000_000_000) return .init(text: "Hello world!", reloadButtonTitle: "Reload") } )) } } } ContentView.swift import SwiftUI struct ContentView: View { @StateObject var viewModel: ViewModel var body: some View { let _ = print("viewModel.state:", viewModel.state) Group { switch viewModel.state { case .notStarted, .loading: ProgressView() case .showingContent(let content): VStack { Text(content.text) .padding(.all, 12) Button(content.reloadButtonTitle) { viewModel.handle(event: .reloadButtonWasTapped) } } } } .onAppear { viewModel.handle(event: .viewDidAppear) } } } // MARK: - ViewModel @MainActor final class ViewModel: ObservableObject { @Published var state: State = .notStarted let contentLoader: () async -> Content init(contentLoader: @escaping () async -> Content) { self.contentLoader = contentLoader } func handle(event: Event) { switch state { case .notStarted: if event == .viewDidAppear { loadContent() } case .loading: break case .showingContent: if event == .reloadButtonWasTapped { loadContent() } } } func loadContent() { guard state != .loading else { return } state = .loading Task { print("starts loading", Date.now) let content = await contentLoader() print("finished loading", Date.now) state = .showingContent(content) } } enum State: Equatable { case notStarted case loading case showingContent(Content) } struct Content: Equatable { let text: String let reloadButtonTitle: String } enum Event: Equatable { case viewDidAppear case reloadButtonWasTapped } } // MARK: - Preview struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: ViewModel( contentLoader: { try! await Task.sleep(nanoseconds: 2_000_000_000) return .init(text: "Hello world!", reloadButtonTitle: "Reload") } )) } } Here's the simple behavior of the app (recorded from simulator): Each time the view appears, it loads it's content for two seconds while showing a ProgressView, then it shows the content, which includes a Reload button, and if you tap that button it will reload the content for 2 seconds again. I would expect the Preview to behave in the same way.
Posted
by Jens.
Last updated
.
Post not yet marked as solved
0 Replies
391 Views
I'm learning Combine. I'm trying to understand some behavior when there is concurrent queue applied to receiveOn as a scheduler. I extracted code to present the situation that I'm trying to understand. let queue = DispatchQueue.global() // Or OperationQueue() let subscription = (1...10).publisher .receive(on: queue) .sink { value in print("Received \(value)") } I'm receiving in debug console such output: Received 1 Received 2 Received 3 Received 7 Received 6 But I was expecting 10 lines in the output. Everytime I run it I'm receiving different results but not 10 lines - 10 values. What is happening here? Does anybody know? Where is 4, 5, 8, 9 and 10? Why completion is arriving faster before all values? Is this a combine bug? I was looking in 2 books related to Combine and in apple documentation. No warnings about using concurrent queues as a schedulers in Combine.
Posted
by Kapucha.
Last updated
.
Post not yet marked as solved
0 Replies
1.2k Views
We currently have our entire app written as SwiftUI Views with ViewModels (currently set as @StateObjects). SwiftUI has a new feature in iOS 17 called @Observable which simplifies the MVVM pattern and would greatly reduce the complexity of our codebase. However, our current ViewModels implement Combine pipelines on the @Published properties which allows us to do all sorts of things from validation of inputs to ensuring lists are filtered correctly. Without the @Published property wrapper in the new @Observable macro, we don't have access to those combine pipelines and so we were wondering how others have solved this? One idea we are floating around is using CurrentValueSubjects as the variable types, but that does pollute the code a little as we have to utilise .send() and .value in the Views which seems like an anti-pattern. Any thoughts or help would be greatly appreciated!
Posted
by HarryT.
Last updated
.
Post not yet marked as solved
0 Replies
544 Views
We are experiencing crash on Combine on iOS 16 Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x000000021dc6408c Termination Reason: SIGNAL 5 Trace/BPT trap: 5 Terminating Process: exc handler [11489] Triggered by Thread: 0 Thread 0 name: Thread 0 Crashed: 0 libsystem_platform.dylib 0x000000021dc6408c _os_unfair_lock_recursive_abort + 36 (lock.c:508) 1 libsystem_platform.dylib 0x000000021dc5e898 _os_unfair_lock_lock_slow + 280 (lock.c:567) 2 Combine 0x00000001d8298174 Publishers.ReceiveOn.Inner.cancel() + 28 (Locking.swift:35) 3 Combine 0x00000001d8297c98 protocol witness for Cancellable.cancel() in conformance Publishers.ReceiveOn<A, B>.Inner<A1> + 24 (<compiler-generated>:0) 4 Combine 0x00000001d828ffc0 Subscribers.Sink.cancel() + 628 (Sink.swift:161) 5 Combine 0x00000001d82ca8ec protocol witness for Cancellable.cancel() in conformance Subscribers.Sink<A, B> + 24 (<compiler-generated>:0) 6 Combine 0x00000001d82945fc AnyCancellable.cancel() + 212 (Subscription.swift:107) 7 Combine 0x00000001d828e250 AnyCancellable.__deallocating_deinit + 16 (Subscription.swift:92) 8 libswiftCore.dylib 0x00000001c9c24134 _swift_release_dealloc + 56 (HeapObject.cpp:706) 9 libswiftCore.dylib 0x00000001c9c15294 swift_arrayDestroy + 124 (Array.cpp:208) 10 libswiftCore.dylib 0x00000001c9a38140 _SetStorage.deinit + 328 (UnsafePointer.swift:954) 11 libswiftCore.dylib 0x00000001c9a38164 _SetStorage.__deallocating_deinit + 16 (SetStorage.swift:0) 12 libswiftCore.dylib 0x00000001c9c24134 _swift_release_dealloc + 56 (HeapObject.cpp:706) 13 AppNameCore 0x00000001033ae3ec MessagesThreadPresenterImpl.deinit + 396 (<compiler-generated>:0) 14 AppNameCore 0x00000001033ae43c MessagesThreadPresenterImpl.__deallocating_deinit + 12 (MessagesThreadPresenter.swift:0) 15 libswiftCore.dylib 0x00000001c9c24134 _swift_release_dealloc + 56 (HeapObject.cpp:706) 16 libswiftCore.dylib 0x00000001c9c25120 bool swift::HeapObjectSideTableEntry::decrementStrong<(swift::PerformDeinit)1>(unsigned int) + 292 (RefCount.h:1032) 17 AppNameCore 0x00000001033b7c44 0x102f64000 + 4537412 18 libswiftCore.dylib 0x00000001c9c24134 _swift_release_dealloc + 56 (HeapObject.cpp:706) 19 Combine 0x00000001d82c0b48 Publishers.FlatMap.Outer.deinit + 232 (<compiler-generated>:0) 20 Combine 0x00000001d82b8244 Publishers.FlatMap.Outer.__deallocating_deinit + 16 (FlatMap.swift:0) 21 libswiftCore.dylib 0x00000001c9c24134 _swift_release_dealloc + 56 (HeapObject.cpp:706) 22 Combine 0x00000001d829366c assignWithTake for SubscriptionStatus + 68 (<compiler-generated>:0) 23 Combine 0x00000001d82906ec outlined assign with take of SubscriptionStatus + 76 (<compiler-generated>:0) 24 Combine 0x00000001d82dd138 closure #1 in Publishers.ReceiveOn.Inner.receive(completion:) + 144 (ReceiveOn.swift:201) 25 Foundation 0x00000001c9fb312c $sIegh_IeyBh_TR + 36 (<compiler-generated>:0) 26 CoreFoundation 0x00000001cfab6514 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 28 (CFRunLoop.c:1805) 27 CoreFoundation 0x00000001cfb1ed6c __CFRunLoopDoBlocks + 368 (CFRunLoop.c:1847) 28 CoreFoundation 0x00000001cfaef1cc __CFRunLoopRun + 2452 (CFRunLoop.c:3201) 29 CoreFoundation 0x00000001cfaf3eb0 CFRunLoopRunSpecific + 612 (CFRunLoop.c:3418) 30 GraphicsServices 0x0000000209ce9368 GSEventRunModal + 164 (GSEvent.c:2196) 31 UIKitCore 0x00000001d1fe9668 -[UIApplication _run] + 888 (UIApplication.m:3758) 32 UIKitCore 0x00000001d1fe92cc UIApplicationMain + 340 (UIApplication.m:5348) 33 AppName 0x000000010009190c main + 180 (main.swift:11) 34 dyld 0x00000001ee3ec960 start + 2528 (dyldMain.cpp:1170)
Posted Last updated
.
Post not yet marked as solved
1 Replies
1.6k Views
Hello For apps built with xcode 13 but ran on ios 16 beta devices, im seeing a potential leak in Combine that i suspect may be causing a breaking bug(the view fails to load anything on subsequent visits after being dismissed, which im suspecting is due to the leaked observable object) . The issue is resolved when built with Xcode 14 Beta, no bug and no leak I am triggering a flow multiple times and seeing the instances of an @ObservableObject used as a @StateObject in a view keep going up in the memory debugger, without the corresponding view leaking to accompany it like ive seen before. so the observable object is being leaked I see the following in the memory graph debugger. It indicates a BoxVTable relation to the observable datastore, which i presume is an internal type i dont have access to, when the view has disappeared Any Ideas? ive checked all the typical closure leak possibilities ive fixed before and this seems to be different
Posted
by cn_nick.
Last updated
.
Post not yet marked as solved
1 Replies
822 Views
I'm working on an iOS project and I'm wondering if it's possible to mix Combine and the new async/await feature in Swift. I want to leverage the power of Combine's reactive programming paradigm along with the simplified asynchronous code flow provided by async/await. Has anyone tried mixing these two approaches in the same project? Are there any known issues or considerations to keep in mind? Are there any best practices or patterns to follow when combining Combine and async/await? I would appreciate any insights or experiences shared. Thank you in advance!
Posted
by Jael19.
Last updated
.
Post not yet marked as solved
0 Replies
620 Views
Can someone please help me understand PassthroughSubject and CurrentValueSubject? What I understand so far is that they are subjects where subscribers can listen to changes made to these subjects, but I'm really straggling to understand the following. I'm I correct by saying that PassthroughSubject or CurrentValueSubject could replace delegation and asynchronous function calls? Is it possible to delare a subject in Class A and subscribe to listen to those subject changes in Class B and in some other classes or are listeners meant to only be used direclty in SwiftUI structs? Thanks
Posted
by fsdolphin.
Last updated
.