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

Posts under Combine tag

16 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Auto-instrumentaion for URLSession async/wait
We have product for network monitoring and we are't able to add support auto-instrumenting the networking requests for URLSession async/wait methods as these methods are't exposed to dynamic environment or not exposed to ObjC and we con't use any of the run-time functionality and we con't override these methods as these methods are't public. looking for a way to add some kind of logic so that when customers use our product they don't have to add any code from there end to monitor this system.
1
0
114
1w
Strange crash when using .values from @Published publisher
Given the below code with Swift 6 language mode, Xcode 16.2 If running with iOS 18+: the app crashes due to _dispatch_assert_queue_fail If running with iOS 17 and below: there is a warning: warning: data race detected: @MainActor function at Swift6Playground/PublishedValuesView.swift:12 was not called on the main thread Could anyone please help explain what's wrong here? import SwiftUI import Combine @MainActor class PublishedValuesViewModel: ObservableObject { @Published var count = 0 @Published var content: String = "NA" private var cancellables: Set<AnyCancellable> = [] func start() async { let publisher = $count .map { String(describing: $0) } .removeDuplicates() for await value in publisher.values { content = value } } } struct PublishedValuesView: View { @ObservedObject var viewModel: PublishedValuesViewModel var body: some View { Text("Published Values: \(viewModel.content)") .task { await viewModel.start() } } }
2
0
214
3w
Diffable Data Source Warning: Non-Thread Confined Updates
Hello, I’ve encountered a warning while working with UITableViewDiffableDataSource. Here’s the exact message: Warning: applying updates in a non-thread confined manner is dangerous and can lead to deadlocks. Please always submit updates either always on the main queue or always off the main queue - view=&lt;UITableView: 0x7fd79192e200; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = &lt;NSArray: 0x600003f3c9f0&gt;; backgroundColor = &lt;UIDynamicProviderColor: 0x60000319bf80; provider = &lt;NSMallocBlock: 0x600003f0ce70&gt;&gt;; layer = &lt;CALayer: 0x6000036e8fa0&gt;; contentOffset: {0, -116}; contentSize: {375, 20}; adjustedContentInset: {116, 0, 49, 0}; dataSource: &lt;TtGC5UIKit29UITableViewDiffableDataSourceOC17ArticleManagement21DiscardItemsViewModel17SectionIdentifierSS: 0x600003228270&gt;&gt; OS: iOS Version: iOS 17+, Xcode Version: 16.0, Frameworks: UIKit, Diffable Data Source, View: UITableView used with a UITableViewDiffableDataSource. Steps to Reproduce: Using a diffable data source with a table view. Applying snapshot updates in the data source from a main thread. Warning occurs intermittently during snapshot application. Expected Behavior: The snapshot should apply without warnings, provided the updates are on a main thread. Actual Behavior: The warning suggests thread safety issues when applying updates on non-thread-confined queues. Questions: Is there a recommended best practice to handle apply calls in diffable data sources with thread safety in mind? Could this lead to potential deadlocks if not addressed? Note :- I confirm I am always reloading / reconfiguring data source on main thread. Please find the attached screenshots for the reference. Any guidance or clarification would be greatly appreciated!
0
0
323
Dec ’24
Issue with Property Wrapper Publisher Being Deallocated Prematurely When Not Stored as a Property
Hello everyone, I've built a @CurrentValue property wrapper that mimics the behavior of @Published, allowing a property to publish values on "did set". I've also created my own assign(to:) implementation that works with @CurrentValue properties, allowing values to be assigned from a publisher to a @CurrentValue property. However, I'm running into an issue. When I use this property wrapper with two classes and the source class (providing the publisher) is not stored as a property, the subscription is deallocated, and values are no longer forwarded. Here's the property wrapper code: @propertyWrapper public struct CurrentValue<Value> { /// A publisher for properties marked with the `@CurrentValue` attribute. public struct Publisher: Combine.Publisher { public typealias Output = Value public typealias Failure = Never /// A subscription that forwards the values from the CurrentValueSubject to the downstream subscriber private class CurrentValueSubscription<S>: Subscription where S: Subscriber, S.Input == Output, S.Failure == Failure { private var subscriber: S? private var currentValueSubject: CurrentValueSubject<S.Input, S.Failure>? private var cancellable: AnyCancellable? init(subscriber: S, publisher: CurrentValue<Value>.Publisher) { self.subscriber = subscriber self.currentValueSubject = publisher.subject } func request(_ demand: Subscribers.Demand) { var demand = demand cancellable = currentValueSubject?.sink { [weak self] value in // We'll continue to emit new values as long as there's demand if let subscriber = self?.subscriber, demand > 0 { demand -= 1 demand += subscriber.receive(value) } else { // If we have no demand, we'll cancel our subscription: self?.subscriber?.receive(completion: .finished) self?.cancel() } } } func cancel() { cancellable = nil subscriber = nil currentValueSubject = nil } } /// A subscription store that holds a reference to all the assign subscribers so we can cancel them when self is deallocated fileprivate final class AssignSubscriptionStore { fileprivate var cancellables: Set<AnyCancellable> = [] } fileprivate let subject: CurrentValueSubject<Value, Never> fileprivate let assignSubscriptionStore: AssignSubscriptionStore = .init() fileprivate var value: Value { get { subject.value } nonmutating set { subject.value = newValue } } init(_ initialValue: Output) { self.subject = .init(initialValue) } public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { let subscription = CurrentValueSubscription(subscriber: subscriber, publisher: self) subscriber.receive(subscription: subscription) } } public var wrappedValue: Value { get { publisher.value } nonmutating set { publisher.value = newValue } } public var projectedValue: Publisher { get { publisher } mutating set { publisher = newValue } } private var publisher: Publisher public init(wrappedValue: Value) { publisher = .init(wrappedValue) } } /// A subscriber that receives values from an upstream publisher and assigns them to a downstream CurrentValue property. private final class AssignSubscriber<Input>: Subscriber, Cancellable { typealias Failure = Never private var receivingSubject: CurrentValueSubject<Input, Never>? private weak var assignSubscriberStore: CurrentValue<Input>.Publisher.AssignSubscriptionStore? init(currentValue: CurrentValue<Input>.Publisher) { self.receivingSubject = currentValue.subject self.assignSubscriberStore = currentValue.assignSubscriptionStore } func receive(subscription: Subscription) { // Hold a reference to the subscription in the downstream publisher // so when it deallocates, the susbcription is automatically cancelled assignSubscriberStore?.cancellables.insert(AnyCancellable(subscription)) subscription.request(.unlimited) } func receive(_ input: Input) -> Subscribers.Demand { receivingSubject?.value = input return .none } func receive(completion: Subscribers.Completion<Never>) { // Nothing to do here } public func cancel() { receivingSubject = nil assignSubscriberStore = nil } } public extension Publisher where Self.Failure == Never { /// Assigns the output of the upstream publisher to a downstream CurrentValue property /// - Parameter currentValue: The CurrentValue property to assign the values to func assign(to currentValue: inout CurrentValue<Self.Output>.Publisher) { let subscriber = AssignSubscriber(currentValue: currentValue) self.subscribe(subscriber) } } Here’s an example demonstrating the issue, where two classes are used: Source, which owns the @CurrentValue property, and Forwarder1, which subscribes to updates from Source: final class Source { @CurrentValue public private(set) var value: Int = 1 func update(value: Int) { self.value = value } } final class Forwarder1 { @CurrentValue public private(set) var value: Int init(source: Source) { self.value = source.value source.$value.dropFirst().assign(to: &$value) // The source is not stored as a property, so the subscription deallocates } func update(value: Int) { self.value = value } } With this setup, if source isn’t retained as a property in Forwarder1, the subscription is deallocated prematurely, and value in Forwarder1 stops receiving updates from Source. However, this doesn’t happen with @Published properties in Combine. Even if source isn’t retained, @Published subscriptions seem to stay active, propagating values as expected. My Questions: What does Combine do internally with @Published properties that prevents the subscription from being deallocated prematurely, even if the publisher source isn’t retained as a property? Is there a recommended approach to address this in my custom property wrapper to achieve similar behavior, ensuring the subscription isn’t lost? Any insights into Combine’s internals or suggestions on how to resolve this would be greatly appreciated. Thank you!
0
4
213
Nov ’24
Async/Await and updating state
When using conformance to ObservableObject and then doing async work in a Task, you will get a warning courtesy of Combine if you then update an @Published or @State var from anywhere but the main thread. However, if you are using @Observable there is no such warning. Also, Thread.current is unavailable in asynchronous contexts, so says the warning. And I have read that in a sense you simply aren't concerned with what thread an async task is on. So for me, that begs a question. Is the lack of a warning, which when using Combine is rather important as ignoring it could lead to crashes, a pretty major bug that Apple seemingly should have addressed long ago? Or is it just not an issue to update state from another thread, because Xcode is doing that work for us behind the scenes too, just as it manages what thread the async task is running on when we don't specify? I see a lot of posts about this from around the initial release of Async/Await talking about using await MainActor.run {} at the point the state variable is updated, usually also complaining about the lack of a warning. But ow years later there is still no warning and I have to wonder if this is actually a non issue. On some ways similar to the fact that many of the early posts I have seen related to @Observable have examples of an @Observable ViewModel instantiated in the view as an @State variable, but in fact this is not needed as that is addressed behind the scenes for all properties of an @Observable type. At least, that is my understanding now, but I am learning Swift coming from a PowerShell background so I question my understanding a lot.
5
0
601
Dec ’24
iOS 18 .onChange(of: scenePhase) is constantly triggered
Hey all, I am facing a new issue on iOS 18 with ScenePhase and .onChange modifier. Here's roughly the code: .onChange(of: scenePhase, initial: true) { old, new in if new == .active { doStuff() } } on iOS 17 this was working as expected and triggered when eg. ap was brough from background or user navigated back to view on ios18 though this code gets triggered constatnly in my case when I eg. navigate to another screen on top of the one with that .onChange. after navigation happens, the onChange is being triggered continuosly and does not stop which causes doStuff() to be called multiple times
1
0
648
Sep ’24
App freezes when built with Xcode 16 on iOS 18, but not on iOS 17 or lower, or with Xcode 15 on iOS 18
I'm experiencing an issue where my app freezes when built with Xcode 16 on iOS 18. Additional Information: The issue does not occur when building the app with Xcode 16 on iOS 17 or lower. The issue does not occur when building the app with Xcode 15 on iOS 18. The CPU usage spikes to 100% when the app freezes. The app specifically freezes after the code runs into .sink(receiveValue:). Here is the relevant code snippet: @Published var selectedCardData: CardData? @Published var selectedRootTab: RootViewTab = .statement override func load() { state = .loading $selectedCardData.ignoreNil() .removeDuplicates() .map { [unowned self] cardData in $selectedRootTab.filter { $0 == .statement } .first() .map { _ in cardData } } .switchToLatest() .sink(receiveValue: { value in print(value) // value not nil print("Execution reaches this point and the app freezes (CPU 100%).") }) .store(in: &cancellables) } Are there any known changes in iOS 18 or Xcode 16 that might affect this code?
2
2
642
Sep ’24
Can I omit `ObservableObject` conformance?
Apple's documentation pretty much only says this about ObservableObject: "A type of object with a publisher that emits before the object has changed. By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.". And this sample seems to behave the same way, with or without conformance to the protocol by Contact: import UIKit import Combine class ViewController: UIViewController { let john = Contact(name: "John Appleseed", age: 24) private var cancellables: Set<AnyCancellable> = [] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. john.$age.sink { age in print("View controller's john's age is now \(age)") } .store(in: &cancellables) print(john.haveBirthday()) } } class Contact { @Published var name: String @Published var age: Int init(name: String, age: Int) { self.name = name self.age = age } func haveBirthday() -> Int { age += 1 return age } } Can I therefore omit conformance to ObservableObject every time I don't need the objectWillChange publisher?
2
0
419
Jul ’24
How can I subscribe to changes to an @AppStorage var...
From what I've read, @AppStorage vars should be @Published, however the following code generates a syntax error at extended's .sink modifier: Cannot call value of non-function type 'Binding<Subject>' class LanguageManager: ObservableObject { @Published var fred = "Fred" @AppStorage("extended") var extended: Bool = true private var subscriptions = Set<AnyCancellable>() init() { $fred .sink(receiveValue: {value in print("value: \(value)") }) .store(in: &subscriptions) $extended .sink(receiveValue: {value in print("value: \(value)") }) .store(in: &subscriptions) } Does anyone know of a way to listen for (subscribe to) changes in @AppStorage values? didSet works in for a specific subset of value changes, but this is not sufficient for my intended use.
2
0
1.1k
Jun ’24
How might I get didSet behaviour on an AppStorage var?
I've defined a value stored in UserDefaults. In a view struct I have code that can successfully update the stored value. I've also added an @AppStorage var in an instance of a class, that can read this value and run business logic that depends on the current stored value. But what I really want to do, is have code in my class that gets automatically called when the value stored in UserDefaults gets updated. Basically I want to do this: @AppStorage("languageChoice") var languageChoice: LanguageChoice = .all { didSet { print("hello") } } Unfortunately didSet closures in @AppStorage vars do not appear to get called :-( My clumsy attempts to use combine have all ended in tears from the compiler. Any/all suggestions are greatly appreciated. thanks, Mike
3
0
828
Jun ’24
Async publisher for AVPlayerItem.tracks doesn't produce values.
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.
2
0
753
Mar ’24
Process() object and async operation
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]) -&gt; 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: &amp;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?
2
0
782
Mar ’24
How to watch changes on fields of `@Published FamilyActivitySelection`?
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?
1
0
742
Jan ’24
Why aren't changes to @Published variables automatically published on the main thread?
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?
9
0
2.8k
Oct ’24
AsyncPublisher of KeyValueObservingPublisher doesn't work
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?
2
1
2.6k
Feb ’24