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

Posts under Combine tag

109 Posts

Post

Replies

Boosts

Views

Activity

AsyncPublisher does not buffer values. Is this a bug?
I'm trying out this code in a playground in Xcode 13.2 beta (13C5066c) import _Concurrency import Combine import PlaygroundSupport import Foundation extension Task where Success == Never, Failure == Never {   static func sleep(seconds: Double) async throws {     try await Self.sleep(nanoseconds: UInt64(1e9 * seconds))   } } Task {   let values = PassthroughSubject<Int, Never>()   Task {     var counter = 0     while true {       counter += 1       print("[SEND] \(counter)")       values.send(counter)       try! await Task.sleep(seconds: Double.random(in: 0.1...0.5))     }   }   for await value in values // vvvvvvvvvvvvv         .buffer(size: Int.max, prefetch: .keepFull, whenFull: .dropOldest) // ^^^^^^^^^^^^^         .values {     print("[RECV] \(value)")     try! await Task.sleep(seconds: 1)   } } PlaygroundPage.current.needsIndefiniteExecution = true This is modeled after real application code. For example, values could be a PassthroughSubject<Packet, NWError> (this is, in fact, what my app code looks like) I've noticed that when doing Publisher.values to convert a Publisher into an AsyncPublisher (so we can use for await), the values aren't buffered. In other words, if we are inside of the body of the for await loop and something is sent to the PassthroughSubject, that value is dropped unless we use .buffer beforehand. This is demonstrated in the playground code above. The outer task receives values, but takes 1 second to process them. The inner task sends values at a fast rate (100ms–500ms). This means that values received during that 1 second period are dropped.(without the .buffer call; with that call, the problem goes away) Is this intentional? I believe this should be more prevalent in documentation. The documentation says that AsyncStream has a buffer: An arbitrary source of elements can produce elements faster than they are consumed by a caller iterating over them. Because of this, AsyncStream defines a buffering behavior, allowing the stream to buffer a specific number of oldest or newest elements. By default, the buffer limit is Int.max, which means the value is unbounded. But AsyncPublisher conforms to AsyncSequence, and not AsyncStream. Maybe this is how this can be fixed?
0
0
1.3k
Nov ’21
Published.Publisher does not trasmit successive values when accessed via Mirror API
I'm using the Mirror API to access the underlying publisher of properties wrapped with @Published. For some reason, when setting a published property, values aren't received by the publisher I'm accessing after the initial value. In the following code, I would expect the "mirrored" publisher to receive events for eternity. import Combine class Dinosaur: ObservableObject { @Published var angry: Bool = false } let raptor = Dinosaur() //let subscription1 = raptor.$angry // .print("NORMAL") // .sink { value in } var published = Mirror(reflecting: raptor).children.first!.value as! Published<Bool> let publisher = published.projectedValue let subscription2 = publisher .print("MIRRORED") .sink { value in } Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in raptor.angry = !raptor.angry } But, it only has the following output. MIRRORED: receive subscription: (PublishedSubject) MIRRORED: request unlimited MIRRORED: receive value: (false) Interestingly, if the let subscription1 = ... line is uncommented, both sinks receive each successive value successfully. NORMAL: receive subscription: (PublishedSubject) NORMAL: request unlimited NORMAL: receive value: (false) MIRRORED: receive subscription: (PublishedSubject) MIRRORED: request unlimited MIRRORED: receive value: (false) MIRRORED: receive value: (true) NORMAL: receive value: (true) MIRRORED: receive value: (false) NORMAL: receive value: (false) ... etc It's almost like the compiler has to "see" the magic raptor.$angry to set up a proper subscription. What am I doing wrong?
0
0
585
Nov ’21
Using URLSession in Combine
I'm trying to figure out how to use URLSession with the Combine framework. I have a class that is to fetch data as follows. import UIKit import Combine class APIClient: NSObject { var cancellables = [AnyCancellable]() @Published var models = [MyModel]() func fetchData(urlStr: String) -> AnyPublisher<[MyModel], Never> { guard let url = URL(string: urlStr) else { let subject = CurrentValueSubject<[MyModel], Never>([]) return subject.eraseToAnyPublisher() } let subject = CurrentValueSubject<[MyModel], Never>(models) URLSession.shared.dataTaskPublisher(for: url) .map { $0.data } .decode(type: [MyModel].self, decoder: JSONDecoder()) .replaceError(with: []) .sink { posts in print("api client: \(posts.count)") self.models = posts } .store(in: &cancellables) return subject.eraseToAnyPublisher() } } I then have a view model class that is to deliver data for my view controller as follows. import Foundation import Combine class ViewModel: NSObject { @IBOutlet var apiClient: APIClient! var cancellables = Set<AnyCancellable>() @Published var dataModels = [MyModel]() func getGitData() -> AnyPublisher<[MyModel], Never> { let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events" let subject = CurrentValueSubject<[MyModel], Never>(dataModels) apiClient.fetchData(urlStr: urlStr) .sink { result in print("view model: \(result.count)") self.dataModels = result }.store(in: &cancellables) return subject.eraseToAnyPublisher() } } My view controller has an IBOutlet of ViewModel. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellables = [AnyCancellable]() @IBOutlet var viewModel: ViewModel! // MARK: - IBOutlet @IBOutlet weak var tableView: UITableView! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() viewModel.getGitData() .sink { posts in print("view controller: \(posts.count)") } .store(in: &cancellables) } } If I run it, it seems that ViewModel returns 0 without waiting for APIClient to return data. And the view controller doesn't wait, either. What am I doing wrong? Can I do it without using the completion handler? In case you need to know what MyModel is, it's a simple struct. struct MyModel: Decodable { let id: String let type: String } Muchos thanks
3
0
2.0k
Nov ’21
Observing Changes in Multiple @Published Variables at the Same Time?
I have the following lines of code to subscribe text changes over two text fields. import UIKit import Combine class ViewController: UIViewController { var cancellables = Set<AnyCancellable>() @Published var userText: String = "" @Published var passText: String = "" // MARK: - IBOutlet @IBOutlet var usernameTextField: UITextField! @IBOutlet var passwordTextField: UITextField! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField) .sink(receiveValue: { (result) in if let myField = result.object as? UITextField { if let text = myField.text { self.userText = text } } }) .store(in: &cancellables) NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField) .sink(receiveValue: { (result) in if let myField = result.object as? UITextField { if let text = myField.text { self.passText = text } } }) .store(in: &cancellables) $userText .sink(receiveValue: { text in print(text) }) .store(in: &cancellables) } } In the last several lines, I am printing the text change for userText. Does Combine allow me to observe two variables (userText, passText) at the same time so that I can plug them into a function? If yes, how? Muchos Thankos.
1
0
2.1k
Nov ’21
Keeping Track of Text Changes over Two Text Fields
I'm still a beginner in using Combine. I practice it on and off. Anyway, I have a view model to see changes in two text fields in my view controller as follows. // ViewModel // import Foundation import Combine class LoginViewModel { var cancellable = [AnyCancellable]() init(username: String, password: String) { myUsername = username myPassword = password } @Published var myUsername: String? @Published var myPassword: String? func validateUser() { print("\(myUsername)") print("\(myPassword)") } } And my view controller goes as follows. // ViewController // import UIKit import Combine class HomeViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBOutlet @IBOutlet var usernameTextField: UITextField! @IBOutlet var passwordTextField: UITextField! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField) .sink(receiveValue: { result in if let textField = result.object as? UITextField { if let text = textField.text { let loginViewModel = LoginViewModel(username: text, password: "") loginViewModel.validateUser() } } }) } } So I use NSNotification as a publisher to see text changes over one of the text fields. And I cannot see text changes over two of them at the same time. Is there a better approach in seeing text changes over two text fields at the same time using Combine? Muchos thankos.
3
0
2.5k
Oct ’21
JSON decoding challenges with Combine
I am using a Combine URLSession to pull data from the Food Data Central API and the Data I am receiving is not being decoded with the JSON decoder. I know the Data is being received because I use the String(data:encoding: .utf8) as a debug print and I can see the downloaded data correctly in the console. I get an error message after the .decode completion failure that says "The data couldn't be read because it isn't in the correct format." I am guessing I have to add something like the "encoder: utf8" statement in the .decode function. Or maybe transform the data in the .tryMap closure before returning. But I have searched the documentation and other sources and have not found anywhere that discusses this. I am a fairly new to Swift (my first real app), I am hoping someone more-experienced can point me in the right direction. My code is as follows: private func fdcSearch(searchFor searchText: String) {         let query = "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=+Apple%20+Fuji"         let searchURL = "https://api.nal.usda.gov/fdc/v1/foods/search?"         let searchQuery = "&amp;query="+searchString         print(searchURL+devData.apiKey+searchQuery) //        guard let url = URL(string: "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=AppleFuji") else {         guard let url = URL(string: searchURL+devData.apiKey+searchQuery) else {             print("Guard error on url assignment") // debug statement             return         }         print("In fdcSearch") // debug statement         fdcSearchSubscription = URLSession.shared.dataTaskPublisher(for: url)             .subscribe(on: DispatchQueue.global(qos: .default))             .tryMap { (output) -&gt; Data in                 guard let response = output.response as? HTTPURLResponse, response.statusCode &gt;= 200 &amp;&amp; response.statusCode &lt; 300 else {                     print("bad server response") // debug statement                     throw URLError(.badServerResponse)                 }                 print("got output") // debug statement                 if let dataString = String(data: output.data, encoding: .utf8) { // debug statement                         print("got dataString: \n\(dataString)") // debug statement                     } // debug statement                 return output.data             }             .receive(on: DispatchQueue.main)             .decode(type: [FDCFoodItem].self, decoder: JSONDecoder())             .sink { (completion) in                 switch completion {                 case .finished:                     print("Completion finished") // debug statement                     break                 case .failure(let error):                     print("Completion failed") // debug statement                     print(error.localizedDescription)                 }             } receiveValue: { [weak self] (returnedFoods) in                 self?.foods = returnedFoods                 print("returnedFoods: \(returnedFoods)") // debug statement                 print("self?.foods: \(String(describing: self?.foods))") // debug statement             }     } Any suggestions on how to handle this?
5
0
2.4k
Oct ’21
Observing UIButton Tap with Combine?
Let me say that I have an IBOutlet object like @IBOutlet weak var deleteButton: UIButton! RxCocoa can make this button tap observable like deleteButton.rx.tap It doesn't look like Combine lets us observe a button tap. Am I right? I find one approach found at the following URL. https://www.avanderlee.com/swift/custom-combine-publisher/ And Combine has no native approach? And you still have to use the IBAction?
1
0
3.0k
Oct ’21
Why Do We Need to Specify Schedule?
Hola, I have the following simple lines of code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var labelValue: String? // MARK: - IBOutlet @IBOutlet weak var textLabel: UILabel! // MARK: - IBAction @IBAction func actionTapped(_ sender: UIButton) { labelValue = "Jim is missing" } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $labelValue .receive(on: DispatchQueue.main) .assign(to: \.text, on: textLabel) } } I just wonder what is the point of specifying the main thread with .receive? If I comment out the receive line, the app will still run without a problem. Muchos thankos
1
0
850
Oct ’21
AsyncPublisher does not buffer values. Is this a bug?
I'm trying out this code in a playground in Xcode 13.2 beta (13C5066c) import _Concurrency import Combine import PlaygroundSupport import Foundation extension Task where Success == Never, Failure == Never {   static func sleep(seconds: Double) async throws {     try await Self.sleep(nanoseconds: UInt64(1e9 * seconds))   } } Task {   let values = PassthroughSubject<Int, Never>()   Task {     var counter = 0     while true {       counter += 1       print("[SEND] \(counter)")       values.send(counter)       try! await Task.sleep(seconds: Double.random(in: 0.1...0.5))     }   }   for await value in values // vvvvvvvvvvvvv         .buffer(size: Int.max, prefetch: .keepFull, whenFull: .dropOldest) // ^^^^^^^^^^^^^         .values {     print("[RECV] \(value)")     try! await Task.sleep(seconds: 1)   } } PlaygroundPage.current.needsIndefiniteExecution = true This is modeled after real application code. For example, values could be a PassthroughSubject<Packet, NWError> (this is, in fact, what my app code looks like) I've noticed that when doing Publisher.values to convert a Publisher into an AsyncPublisher (so we can use for await), the values aren't buffered. In other words, if we are inside of the body of the for await loop and something is sent to the PassthroughSubject, that value is dropped unless we use .buffer beforehand. This is demonstrated in the playground code above. The outer task receives values, but takes 1 second to process them. The inner task sends values at a fast rate (100ms–500ms). This means that values received during that 1 second period are dropped.(without the .buffer call; with that call, the problem goes away) Is this intentional? I believe this should be more prevalent in documentation. The documentation says that AsyncStream has a buffer: An arbitrary source of elements can produce elements faster than they are consumed by a caller iterating over them. Because of this, AsyncStream defines a buffering behavior, allowing the stream to buffer a specific number of oldest or newest elements. By default, the buffer limit is Int.max, which means the value is unbounded. But AsyncPublisher conforms to AsyncSequence, and not AsyncStream. Maybe this is how this can be fixed?
Replies
0
Boosts
0
Views
1.3k
Activity
Nov ’21
Published.Publisher does not trasmit successive values when accessed via Mirror API
I'm using the Mirror API to access the underlying publisher of properties wrapped with @Published. For some reason, when setting a published property, values aren't received by the publisher I'm accessing after the initial value. In the following code, I would expect the "mirrored" publisher to receive events for eternity. import Combine class Dinosaur: ObservableObject { @Published var angry: Bool = false } let raptor = Dinosaur() //let subscription1 = raptor.$angry // .print("NORMAL") // .sink { value in } var published = Mirror(reflecting: raptor).children.first!.value as! Published<Bool> let publisher = published.projectedValue let subscription2 = publisher .print("MIRRORED") .sink { value in } Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in raptor.angry = !raptor.angry } But, it only has the following output. MIRRORED: receive subscription: (PublishedSubject) MIRRORED: request unlimited MIRRORED: receive value: (false) Interestingly, if the let subscription1 = ... line is uncommented, both sinks receive each successive value successfully. NORMAL: receive subscription: (PublishedSubject) NORMAL: request unlimited NORMAL: receive value: (false) MIRRORED: receive subscription: (PublishedSubject) MIRRORED: request unlimited MIRRORED: receive value: (false) MIRRORED: receive value: (true) NORMAL: receive value: (true) MIRRORED: receive value: (false) NORMAL: receive value: (false) ... etc It's almost like the compiler has to "see" the magic raptor.$angry to set up a proper subscription. What am I doing wrong?
Replies
0
Boosts
0
Views
585
Activity
Nov ’21
Combining More Than Four @Published Variables in Combine?
If I want to subscribe to four @Published variables at the same time, I can do something like the following. Publishers.CombineLatest4($variable0, $variable1, $variable2, $variable3) I wonder if there is any solution to subscribing to more than four variables at the same time? Muchos thankos
Replies
5
Boosts
0
Views
2.1k
Activity
Nov ’21
Using URLSession in Combine
I'm trying to figure out how to use URLSession with the Combine framework. I have a class that is to fetch data as follows. import UIKit import Combine class APIClient: NSObject { var cancellables = [AnyCancellable]() @Published var models = [MyModel]() func fetchData(urlStr: String) -> AnyPublisher<[MyModel], Never> { guard let url = URL(string: urlStr) else { let subject = CurrentValueSubject<[MyModel], Never>([]) return subject.eraseToAnyPublisher() } let subject = CurrentValueSubject<[MyModel], Never>(models) URLSession.shared.dataTaskPublisher(for: url) .map { $0.data } .decode(type: [MyModel].self, decoder: JSONDecoder()) .replaceError(with: []) .sink { posts in print("api client: \(posts.count)") self.models = posts } .store(in: &cancellables) return subject.eraseToAnyPublisher() } } I then have a view model class that is to deliver data for my view controller as follows. import Foundation import Combine class ViewModel: NSObject { @IBOutlet var apiClient: APIClient! var cancellables = Set<AnyCancellable>() @Published var dataModels = [MyModel]() func getGitData() -> AnyPublisher<[MyModel], Never> { let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events" let subject = CurrentValueSubject<[MyModel], Never>(dataModels) apiClient.fetchData(urlStr: urlStr) .sink { result in print("view model: \(result.count)") self.dataModels = result }.store(in: &cancellables) return subject.eraseToAnyPublisher() } } My view controller has an IBOutlet of ViewModel. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellables = [AnyCancellable]() @IBOutlet var viewModel: ViewModel! // MARK: - IBOutlet @IBOutlet weak var tableView: UITableView! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() viewModel.getGitData() .sink { posts in print("view controller: \(posts.count)") } .store(in: &cancellables) } } If I run it, it seems that ViewModel returns 0 without waiting for APIClient to return data. And the view controller doesn't wait, either. What am I doing wrong? Can I do it without using the completion handler? In case you need to know what MyModel is, it's a simple struct. struct MyModel: Decodable { let id: String let type: String } Muchos thanks
Replies
3
Boosts
0
Views
2.0k
Activity
Nov ’21
Observing Changes in Multiple @Published Variables at the Same Time?
I have the following lines of code to subscribe text changes over two text fields. import UIKit import Combine class ViewController: UIViewController { var cancellables = Set<AnyCancellable>() @Published var userText: String = "" @Published var passText: String = "" // MARK: - IBOutlet @IBOutlet var usernameTextField: UITextField! @IBOutlet var passwordTextField: UITextField! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField) .sink(receiveValue: { (result) in if let myField = result.object as? UITextField { if let text = myField.text { self.userText = text } } }) .store(in: &cancellables) NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField) .sink(receiveValue: { (result) in if let myField = result.object as? UITextField { if let text = myField.text { self.passText = text } } }) .store(in: &cancellables) $userText .sink(receiveValue: { text in print(text) }) .store(in: &cancellables) } } In the last several lines, I am printing the text change for userText. Does Combine allow me to observe two variables (userText, passText) at the same time so that I can plug them into a function? If yes, how? Muchos Thankos.
Replies
1
Boosts
0
Views
2.1k
Activity
Nov ’21
Keeping Track of Text Changes over Two Text Fields
I'm still a beginner in using Combine. I practice it on and off. Anyway, I have a view model to see changes in two text fields in my view controller as follows. // ViewModel // import Foundation import Combine class LoginViewModel { var cancellable = [AnyCancellable]() init(username: String, password: String) { myUsername = username myPassword = password } @Published var myUsername: String? @Published var myPassword: String? func validateUser() { print("\(myUsername)") print("\(myPassword)") } } And my view controller goes as follows. // ViewController // import UIKit import Combine class HomeViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBOutlet @IBOutlet var usernameTextField: UITextField! @IBOutlet var passwordTextField: UITextField! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField) .sink(receiveValue: { result in if let textField = result.object as? UITextField { if let text = textField.text { let loginViewModel = LoginViewModel(username: text, password: "") loginViewModel.validateUser() } } }) } } So I use NSNotification as a publisher to see text changes over one of the text fields. And I cannot see text changes over two of them at the same time. Is there a better approach in seeing text changes over two text fields at the same time using Combine? Muchos thankos.
Replies
3
Boosts
0
Views
2.5k
Activity
Oct ’21
JSON decoding challenges with Combine
I am using a Combine URLSession to pull data from the Food Data Central API and the Data I am receiving is not being decoded with the JSON decoder. I know the Data is being received because I use the String(data:encoding: .utf8) as a debug print and I can see the downloaded data correctly in the console. I get an error message after the .decode completion failure that says "The data couldn't be read because it isn't in the correct format." I am guessing I have to add something like the "encoder: utf8" statement in the .decode function. Or maybe transform the data in the .tryMap closure before returning. But I have searched the documentation and other sources and have not found anywhere that discusses this. I am a fairly new to Swift (my first real app), I am hoping someone more-experienced can point me in the right direction. My code is as follows: private func fdcSearch(searchFor searchText: String) {         let query = "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=+Apple%20+Fuji"         let searchURL = "https://api.nal.usda.gov/fdc/v1/foods/search?"         let searchQuery = "&amp;query="+searchString         print(searchURL+devData.apiKey+searchQuery) //        guard let url = URL(string: "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=AppleFuji") else {         guard let url = URL(string: searchURL+devData.apiKey+searchQuery) else {             print("Guard error on url assignment") // debug statement             return         }         print("In fdcSearch") // debug statement         fdcSearchSubscription = URLSession.shared.dataTaskPublisher(for: url)             .subscribe(on: DispatchQueue.global(qos: .default))             .tryMap { (output) -&gt; Data in                 guard let response = output.response as? HTTPURLResponse, response.statusCode &gt;= 200 &amp;&amp; response.statusCode &lt; 300 else {                     print("bad server response") // debug statement                     throw URLError(.badServerResponse)                 }                 print("got output") // debug statement                 if let dataString = String(data: output.data, encoding: .utf8) { // debug statement                         print("got dataString: \n\(dataString)") // debug statement                     } // debug statement                 return output.data             }             .receive(on: DispatchQueue.main)             .decode(type: [FDCFoodItem].self, decoder: JSONDecoder())             .sink { (completion) in                 switch completion {                 case .finished:                     print("Completion finished") // debug statement                     break                 case .failure(let error):                     print("Completion failed") // debug statement                     print(error.localizedDescription)                 }             } receiveValue: { [weak self] (returnedFoods) in                 self?.foods = returnedFoods                 print("returnedFoods: \(returnedFoods)") // debug statement                 print("self?.foods: \(String(describing: self?.foods))") // debug statement             }     } Any suggestions on how to handle this?
Replies
5
Boosts
0
Views
2.4k
Activity
Oct ’21
Observing UIButton Tap with Combine?
Let me say that I have an IBOutlet object like @IBOutlet weak var deleteButton: UIButton! RxCocoa can make this button tap observable like deleteButton.rx.tap It doesn't look like Combine lets us observe a button tap. Am I right? I find one approach found at the following URL. https://www.avanderlee.com/swift/custom-combine-publisher/ And Combine has no native approach? And you still have to use the IBAction?
Replies
1
Boosts
0
Views
3.0k
Activity
Oct ’21
Why Do We Need to Specify Schedule?
Hola, I have the following simple lines of code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var labelValue: String? // MARK: - IBOutlet @IBOutlet weak var textLabel: UILabel! // MARK: - IBAction @IBAction func actionTapped(_ sender: UIButton) { labelValue = "Jim is missing" } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $labelValue .receive(on: DispatchQueue.main) .assign(to: \.text, on: textLabel) } } I just wonder what is the point of specifying the main thread with .receive? If I comment out the receive line, the app will still run without a problem. Muchos thankos
Replies
1
Boosts
0
Views
850
Activity
Oct ’21