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
Combine
RSS for tagCustomize handling of asynchronous events by combining event-processing operators using Combine.
Posts under Combine tag
16 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
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?
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?
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.
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
Hi,
Is there any way to return cached URLSession response and then reload and return?
I want show cached response while load last version of API call, and if reload works fine, show the latest version of response and in case os failure, show cached response (if exists)
Thanks!
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.
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?
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?
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
}
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?
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?
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?
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?
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?
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?