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

Posts under Combine tag

123 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.2k
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
480
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
1.8k
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
1.9k
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.3k
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.2k
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
2.8k
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
692
Oct ’21
How to workaround a failed Combine API Call
I have two API call's for data and if openWeatherData fails it prevents planData.trainingdata from loading. Is there a way to workaround this so planData still loads?          List(planData.trainingdata) { index in           ForEach(openWeatherData.openWeatherData) { day in ... } } Here's the API call for openWeather class DownloadOpenWeatherData: ObservableObject {   @Published var openWeatherData: [OpenWeatherDecoder] = []   let locationManager = LocationManager()   var openWeatherCancellabes = Set<AnyCancellable>()   init() {     print("OpenWeather: loading init")     locationManager.startLoc()     getOpenWeatherData(weatherUrl: "https://api.openweathermap.org/data/2.5/onecall?lat=\(locationManager.getLat())&lon=\(locationManager.getLong())&exclude=hourly,minutely&appid")     locationManager.stopLoc()   }   func getOpenWeatherData(weatherUrl: String) {     guard let weatherUrl = URL(string: weatherUrl) else { return }     print("getOpenWeather url \(weatherUrl)")     URLSession.shared.dataTaskPublisher(for: weatherUrl)       .subscribe(on: DispatchQueue.global(qos: .background))       .receive(on: DispatchQueue.main)       .tryMap { (data, response) -> Data in         print(response)         guard           let response = response as? HTTPURLResponse,            response.statusCode >= 200 && response.statusCode < 300 else {           throw URLError(.badServerResponse)         }         print("data \(data)")         return data       }       .decode(type: OpenWeatherDecoder.self, decoder: JSONDecoder())       .sink { (completion) in         print(completion)       } receiveValue: { (returnedWeatherData) in         self.openWeatherData = [returnedWeatherData]         print("returnedWeatherData \(returnedWeatherData)")       }       .store(in: &openWeatherCancellabes)   } }
0
0
1k
Sep ’21
iOS 15 Navigation broken with SwiftUI, Combine, and Realm
I have an app that uses Realm as a data storage solution and Combine as a declarative framework. Ever since I upgraded to iOS 15, my app has become unusable because of what looks like a navigation bug. It works fine with iOS 14. The gist of what I believe the bug is, is that when I navigate to a child view and the parent view is updated, the navigation jumps back to the parent view instead of staying in the child view (https://www.youtube.com/watch?v=c-PAPy-RCqg) So in my example, ContentView loads data Realm and creates a list of DetailViews to display the data. When the DetailView is displayed, it writes some back to the Realm table. I have a sample app demonstrating the problem here: https://github.com/skywalkerdude/navsample. Since it is using Cocoapods, make sure you open NavSample.xcworkspace and not NavSample.xcodeproj.
1
0
778
Sep ’21
Core Data + CloudKit - I need to get FetchRequest's data into my Model
I have been working on this for a while and haven't found a solution. I have a populated public core data database (used readonly in the app) that I need to pull from on launch. This ONLY works with SwiftUI via @FetchRequest or FetchRequest -- NSFetchResultsController, attempting to do it with persistentCloudKitContainer.viewContext.perform {} doesn't fetch (all do work after relaunching the app). The only thing that works is FetchRequest, but I need to do things in my model as soon as I get that data from CloudKit (create local data in response). How can I set, in my model, allItems to the wrappedValue property of the FetchRequest or @FetchRequest ? Since it is from cloudkit just setting the variable won't work, I need to use combine, but I can't wrap my head around how. If anyone has any suggestions -- I'd much appreciate the assistance. class MyViewModel: ObservableObject {     typealias PublisherType = PassthroughSubject<MyViewModel, Never>     let objectWillChange = ObservableObjectPublisher()     let objectDidChange = ObservableObjectPublisher() @Published var allItems: [Item] = [] struct MyView: View {     @FetchRequest var fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])     var body: some View {            List(fetchRequest.wrappedValue) { (item:Item) in Text(item.name) } }
1
0
1.2k
Sep ’21
Changing a Status With @EnvironmentObject in Another View
I'm playing with @EnvironmentObject to see how it works in SwiftUI. I have the main view (ContentView) where it says the user has not logged in yet. By letting the user tap a link, I want to make it such that they can log in by tapping a button. class LoginMe: ObservableObject { @Published var loggedIn = false } struct ContentView: View { @StateObject var loginMe = LoginMe() var body: some View { if loginMe.loggedIn { Text("Yes, I'm logged in") } else { NavigationView { VStack { Text("No, not logged in yet") .padding(.vertical) NavigationLink(destination: LoginView()) { Text("Tap to log in") } } .navigationBarTitle("User") } .environmentObject(loginMe) } } } struct LoginView: View { @EnvironmentObject var loginMe: LoginMe var body: some View { /* Toggle(isOn: $loginMe.loggedIn) { Text("Log in") }.padding(.horizontal) */ Button("Login") { loginMe.loggedIn.toggle() } } } So far, when the user taps a button in the LoginView view, the screen goes back to ContentView and the navigation simply disappears. How can I change my code so that the status will change back and forth in in the LoginView view by tapping a button and then so that they can return to ContentView the navigation return button? I think the problem is that I need to use @State var in ContentView and @Binding var in LoginView. Things are kind of confusing. Muchos thankos.
1
0
580
Sep ’21
Why are Cancellables in Combine called Cancellables
I am learning combine and one of the thing that is holding me back is the confusion of the semantic meaning of Cancellables. Why are they called Cancellables. I have tried to search for this answer on google but I didn’t find sufficient answers to satisfy my curiosity. Also, what is the advantage of using combine bindings over computed properties in SwiftUI. Thanks
1
0
824
Sep ’21
How can I go about accessing the Periods portion of NOAA Api response?
Here's the model of the API that I'm using // MARK: - Welcome struct NOAAWeatherDecoder: Codable, Identifiable {   var id = UUID()   let type: String   let geometry: Geometry   let properties: Properties   enum CodingKeys: String, CodingKey {     case type, geometry, properties   } } // MARK: - Properties struct Properties: Codable {   let updated: Date   let units, forecastGenerator: String   let generatedAt, updateTime: Date   let validTimes: String   let elevation: Elevation   let periods: [Period] } // MARK: - Elevation struct Elevation: Codable {   let value: Double   let unitCode: String } // MARK: - Period struct Period: Codable {   let number: Int   let name: String   let startTime, endTime: Date   let isDaytime: Bool   let temperature: Int   let temperatureUnit: TemperatureUnit   let temperatureTrend: JSONNull?   let windSpeed, windDirection: String   let icon: String   let shortForecast, detailedForecast: String } I am able to get upto periods, but nothing after that. Here's the part of the code where I'm running into the issue   ForEach(noaaWeatherData.weathernoaadata) { day in Text(day.properties.periods[0]) After this I'm not entirely sure. } Thanks!
1
0
835
Sep ’21
Practical Use of Combine's Subject
I'm trying to understand how Combine works. The following is my sample code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { currentValueSubject.send(20) } // MARK: - Life cycle var currentValueSubject = CurrentValueSubject<Int, Never>(1) override func viewDidLoad() { super.viewDidLoad() let cancellable = currentValueSubject .sink { value in print("New value: \(value)") } currentValueSubject.send(5) currentValueSubject.send(10) //currentValueSubject.send(completion: .finished) currentValueSubject.send(15) //cancellable.cancel() } } If I run it with the iPhone simulator, I get New value: 1 New value: 5 New value: 10 New value: 15 If I tap the button, the app won't get a new value. I suppose that's because the subscription is cancelled at the end of viewDidLoad? If so, why does it get cancelled? I don't quite see a practical side of Combine's Subject. When is it useful? Thanks.
2
0
1.2k
Aug ’21
Value of type 'UIView?' has no member 'isEnabled'
I have the following lines of code in practicing Combine. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var segmentNumber: Int = 0 // MARK: - IBOutlet @IBOutlet weak var actionButton: UIButton! // MARK: - IBAction @IBAction func segmentChanged(_ sender: UISegmentedControl) { segmentNumber = sender.selectedSegmentIndex } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $segmentNumber.receive(on: DispatchQueue.main) .assign(to: \.isEnabled, on: actionButton) } } I get an error at .assign that says Value of type 'UIView?' has no member 'isEnabled' What am I doing wrong? Thank you.
3
0
2.5k
Aug ’21
Cannot compile combine.
Unable to combine a project with dependencies that rely on combine. Getting an error saying unable to compile combine This issue started with Xcode 13 beta 3 and still persists with beta 4 for me. The project builds and runs fine in debug configuration, this error only happens when trying to build in release configuration. This is happening with a dependency linked though SPM. Not sure how to fix this
1
0
747
Aug ’21
How to read a local value correctly both in Combine's sink scope and in the scope that Combine's sink was defined?
Now I want to notify value change from delegate event to another func() and returns the new value with callback(). To do that, I tried to write test program as below. However, problem happened. import SwiftUI struct ContentView: View {       let cst = CombineSinkTest()       var body: some View {     Button(action: {       cst.sinkTest(stCount: 0){ sinkResult in         print("finish")       }     }){       Text("Push")     }   } } import Foundation import Combine final public class CombineSinkTest: NSObject{       var stringPublisher = PassthroughSubject<String?, Never>()   var cancellables = [AnyCancellable]()         private var globalNum = 0   var sinkDefinedFlag = false       func sinkTest(stCount: Int, completion: @escaping (Bool) -> Void){     var localCount = stCount           childSink(count: localCount){ childResult in       localCount += 1       DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {         self.sinkTest(stCount: localCount){ stResult in           completion(true)         }       }     }   }       func childSink(count: Int, completion: @escaping (Bool) -> Void ){     globalNum += 1     let localNum = globalNum           print("globalNum: \(localNum)")           if(sinkDefinedFlag == false) {       self.stringPublisher         .sink(receiveCompletion: { print ("completion: \($0)") },          receiveValue: {           print ("received: \($0)")           print("local value: \(localNum)")           completion(true)          })         .store(in: &cancellables)       sinkDefinedFlag = true     } (a)    generateNotification()         }     (a)  private func generateNotification() {     stringPublisher.send("")   } } (a) is test-purpose. (a)'s func is planned to be delegate event. I want to let receiveValue: in .sink to use localCount number. However, localCount won't increase at all... globalNum: 1 localNum in caller of .sink: 1 received: Optional("") localNum in .sink: 1  globalNum: 2 localNum in caller of .sink: 2 received: Optional("") localNum in .sink: 1  globalNum: 3 localNum in caller of .sink: 3 received: Optional("") localNum in .sink: 1  globalNumber is increasing. And, localNum in func defining .sink. However, localCount in .sink closure won't increase. How should I change the code for passing updated localNum into .sink closure? Sorry for not good English... Best regards,
2
0
794
Aug ’21
I'm trying to understand Combine Future
I'm using playground to experiment with Combine. I found this example on a blog. I expect it to create the Future publisher, wait a couple seconds then send the promise and complete. import Foundation import Combine import SwiftUI let future = Future<Int, Never> { promise in     print("Creating")     DispatchQueue.global().asyncAfter(deadline: .now() + 2) {         print("sending promise")         promise(.success(1))     } } future     .sink(receiveCompletion: { print("receiveCompletion:\($0)") },             receiveValue: { print("receiveValue:\($0)") }) print("end") The output I expect: Creating end sending promise receiveValue: ... receiveCompletion: ... The output I get: Creating end sending promise I don't see an indication the promise was executed. What am I doing wrong?
2
0
1.3k
Jul ’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.2k
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
480
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
1.9k
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
1.8k
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
1.9k
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.3k
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.2k
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
2.8k
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
692
Activity
Oct ’21
How to workaround a failed Combine API Call
I have two API call's for data and if openWeatherData fails it prevents planData.trainingdata from loading. Is there a way to workaround this so planData still loads?          List(planData.trainingdata) { index in           ForEach(openWeatherData.openWeatherData) { day in ... } } Here's the API call for openWeather class DownloadOpenWeatherData: ObservableObject {   @Published var openWeatherData: [OpenWeatherDecoder] = []   let locationManager = LocationManager()   var openWeatherCancellabes = Set<AnyCancellable>()   init() {     print("OpenWeather: loading init")     locationManager.startLoc()     getOpenWeatherData(weatherUrl: "https://api.openweathermap.org/data/2.5/onecall?lat=\(locationManager.getLat())&lon=\(locationManager.getLong())&exclude=hourly,minutely&appid")     locationManager.stopLoc()   }   func getOpenWeatherData(weatherUrl: String) {     guard let weatherUrl = URL(string: weatherUrl) else { return }     print("getOpenWeather url \(weatherUrl)")     URLSession.shared.dataTaskPublisher(for: weatherUrl)       .subscribe(on: DispatchQueue.global(qos: .background))       .receive(on: DispatchQueue.main)       .tryMap { (data, response) -> Data in         print(response)         guard           let response = response as? HTTPURLResponse,            response.statusCode >= 200 && response.statusCode < 300 else {           throw URLError(.badServerResponse)         }         print("data \(data)")         return data       }       .decode(type: OpenWeatherDecoder.self, decoder: JSONDecoder())       .sink { (completion) in         print(completion)       } receiveValue: { (returnedWeatherData) in         self.openWeatherData = [returnedWeatherData]         print("returnedWeatherData \(returnedWeatherData)")       }       .store(in: &openWeatherCancellabes)   } }
Replies
0
Boosts
0
Views
1k
Activity
Sep ’21
iOS 15 Navigation broken with SwiftUI, Combine, and Realm
I have an app that uses Realm as a data storage solution and Combine as a declarative framework. Ever since I upgraded to iOS 15, my app has become unusable because of what looks like a navigation bug. It works fine with iOS 14. The gist of what I believe the bug is, is that when I navigate to a child view and the parent view is updated, the navigation jumps back to the parent view instead of staying in the child view (https://www.youtube.com/watch?v=c-PAPy-RCqg) So in my example, ContentView loads data Realm and creates a list of DetailViews to display the data. When the DetailView is displayed, it writes some back to the Realm table. I have a sample app demonstrating the problem here: https://github.com/skywalkerdude/navsample. Since it is using Cocoapods, make sure you open NavSample.xcworkspace and not NavSample.xcodeproj.
Replies
1
Boosts
0
Views
778
Activity
Sep ’21
Core Data + CloudKit - I need to get FetchRequest's data into my Model
I have been working on this for a while and haven't found a solution. I have a populated public core data database (used readonly in the app) that I need to pull from on launch. This ONLY works with SwiftUI via @FetchRequest or FetchRequest -- NSFetchResultsController, attempting to do it with persistentCloudKitContainer.viewContext.perform {} doesn't fetch (all do work after relaunching the app). The only thing that works is FetchRequest, but I need to do things in my model as soon as I get that data from CloudKit (create local data in response). How can I set, in my model, allItems to the wrappedValue property of the FetchRequest or @FetchRequest ? Since it is from cloudkit just setting the variable won't work, I need to use combine, but I can't wrap my head around how. If anyone has any suggestions -- I'd much appreciate the assistance. class MyViewModel: ObservableObject {     typealias PublisherType = PassthroughSubject<MyViewModel, Never>     let objectWillChange = ObservableObjectPublisher()     let objectDidChange = ObservableObjectPublisher() @Published var allItems: [Item] = [] struct MyView: View {     @FetchRequest var fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])     var body: some View {            List(fetchRequest.wrappedValue) { (item:Item) in Text(item.name) } }
Replies
1
Boosts
0
Views
1.2k
Activity
Sep ’21
Changing a Status With @EnvironmentObject in Another View
I'm playing with @EnvironmentObject to see how it works in SwiftUI. I have the main view (ContentView) where it says the user has not logged in yet. By letting the user tap a link, I want to make it such that they can log in by tapping a button. class LoginMe: ObservableObject { @Published var loggedIn = false } struct ContentView: View { @StateObject var loginMe = LoginMe() var body: some View { if loginMe.loggedIn { Text("Yes, I'm logged in") } else { NavigationView { VStack { Text("No, not logged in yet") .padding(.vertical) NavigationLink(destination: LoginView()) { Text("Tap to log in") } } .navigationBarTitle("User") } .environmentObject(loginMe) } } } struct LoginView: View { @EnvironmentObject var loginMe: LoginMe var body: some View { /* Toggle(isOn: $loginMe.loggedIn) { Text("Log in") }.padding(.horizontal) */ Button("Login") { loginMe.loggedIn.toggle() } } } So far, when the user taps a button in the LoginView view, the screen goes back to ContentView and the navigation simply disappears. How can I change my code so that the status will change back and forth in in the LoginView view by tapping a button and then so that they can return to ContentView the navigation return button? I think the problem is that I need to use @State var in ContentView and @Binding var in LoginView. Things are kind of confusing. Muchos thankos.
Replies
1
Boosts
0
Views
580
Activity
Sep ’21
Why are Cancellables in Combine called Cancellables
I am learning combine and one of the thing that is holding me back is the confusion of the semantic meaning of Cancellables. Why are they called Cancellables. I have tried to search for this answer on google but I didn’t find sufficient answers to satisfy my curiosity. Also, what is the advantage of using combine bindings over computed properties in SwiftUI. Thanks
Replies
1
Boosts
0
Views
824
Activity
Sep ’21
How can I go about accessing the Periods portion of NOAA Api response?
Here's the model of the API that I'm using // MARK: - Welcome struct NOAAWeatherDecoder: Codable, Identifiable {   var id = UUID()   let type: String   let geometry: Geometry   let properties: Properties   enum CodingKeys: String, CodingKey {     case type, geometry, properties   } } // MARK: - Properties struct Properties: Codable {   let updated: Date   let units, forecastGenerator: String   let generatedAt, updateTime: Date   let validTimes: String   let elevation: Elevation   let periods: [Period] } // MARK: - Elevation struct Elevation: Codable {   let value: Double   let unitCode: String } // MARK: - Period struct Period: Codable {   let number: Int   let name: String   let startTime, endTime: Date   let isDaytime: Bool   let temperature: Int   let temperatureUnit: TemperatureUnit   let temperatureTrend: JSONNull?   let windSpeed, windDirection: String   let icon: String   let shortForecast, detailedForecast: String } I am able to get upto periods, but nothing after that. Here's the part of the code where I'm running into the issue   ForEach(noaaWeatherData.weathernoaadata) { day in Text(day.properties.periods[0]) After this I'm not entirely sure. } Thanks!
Replies
1
Boosts
0
Views
835
Activity
Sep ’21
Practical Use of Combine's Subject
I'm trying to understand how Combine works. The following is my sample code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { currentValueSubject.send(20) } // MARK: - Life cycle var currentValueSubject = CurrentValueSubject<Int, Never>(1) override func viewDidLoad() { super.viewDidLoad() let cancellable = currentValueSubject .sink { value in print("New value: \(value)") } currentValueSubject.send(5) currentValueSubject.send(10) //currentValueSubject.send(completion: .finished) currentValueSubject.send(15) //cancellable.cancel() } } If I run it with the iPhone simulator, I get New value: 1 New value: 5 New value: 10 New value: 15 If I tap the button, the app won't get a new value. I suppose that's because the subscription is cancelled at the end of viewDidLoad? If so, why does it get cancelled? I don't quite see a practical side of Combine's Subject. When is it useful? Thanks.
Replies
2
Boosts
0
Views
1.2k
Activity
Aug ’21
Value of type 'UIView?' has no member 'isEnabled'
I have the following lines of code in practicing Combine. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var segmentNumber: Int = 0 // MARK: - IBOutlet @IBOutlet weak var actionButton: UIButton! // MARK: - IBAction @IBAction func segmentChanged(_ sender: UISegmentedControl) { segmentNumber = sender.selectedSegmentIndex } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $segmentNumber.receive(on: DispatchQueue.main) .assign(to: \.isEnabled, on: actionButton) } } I get an error at .assign that says Value of type 'UIView?' has no member 'isEnabled' What am I doing wrong? Thank you.
Replies
3
Boosts
0
Views
2.5k
Activity
Aug ’21
Cannot compile combine.
Unable to combine a project with dependencies that rely on combine. Getting an error saying unable to compile combine This issue started with Xcode 13 beta 3 and still persists with beta 4 for me. The project builds and runs fine in debug configuration, this error only happens when trying to build in release configuration. This is happening with a dependency linked though SPM. Not sure how to fix this
Replies
1
Boosts
0
Views
747
Activity
Aug ’21
How to read a local value correctly both in Combine's sink scope and in the scope that Combine's sink was defined?
Now I want to notify value change from delegate event to another func() and returns the new value with callback(). To do that, I tried to write test program as below. However, problem happened. import SwiftUI struct ContentView: View {       let cst = CombineSinkTest()       var body: some View {     Button(action: {       cst.sinkTest(stCount: 0){ sinkResult in         print("finish")       }     }){       Text("Push")     }   } } import Foundation import Combine final public class CombineSinkTest: NSObject{       var stringPublisher = PassthroughSubject<String?, Never>()   var cancellables = [AnyCancellable]()         private var globalNum = 0   var sinkDefinedFlag = false       func sinkTest(stCount: Int, completion: @escaping (Bool) -> Void){     var localCount = stCount           childSink(count: localCount){ childResult in       localCount += 1       DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {         self.sinkTest(stCount: localCount){ stResult in           completion(true)         }       }     }   }       func childSink(count: Int, completion: @escaping (Bool) -> Void ){     globalNum += 1     let localNum = globalNum           print("globalNum: \(localNum)")           if(sinkDefinedFlag == false) {       self.stringPublisher         .sink(receiveCompletion: { print ("completion: \($0)") },          receiveValue: {           print ("received: \($0)")           print("local value: \(localNum)")           completion(true)          })         .store(in: &cancellables)       sinkDefinedFlag = true     } (a)    generateNotification()         }     (a)  private func generateNotification() {     stringPublisher.send("")   } } (a) is test-purpose. (a)'s func is planned to be delegate event. I want to let receiveValue: in .sink to use localCount number. However, localCount won't increase at all... globalNum: 1 localNum in caller of .sink: 1 received: Optional("") localNum in .sink: 1  globalNum: 2 localNum in caller of .sink: 2 received: Optional("") localNum in .sink: 1  globalNum: 3 localNum in caller of .sink: 3 received: Optional("") localNum in .sink: 1  globalNumber is increasing. And, localNum in func defining .sink. However, localCount in .sink closure won't increase. How should I change the code for passing updated localNum into .sink closure? Sorry for not good English... Best regards,
Replies
2
Boosts
0
Views
794
Activity
Aug ’21
I'm trying to understand Combine Future
I'm using playground to experiment with Combine. I found this example on a blog. I expect it to create the Future publisher, wait a couple seconds then send the promise and complete. import Foundation import Combine import SwiftUI let future = Future<Int, Never> { promise in     print("Creating")     DispatchQueue.global().asyncAfter(deadline: .now() + 2) {         print("sending promise")         promise(.success(1))     } } future     .sink(receiveCompletion: { print("receiveCompletion:\($0)") },             receiveValue: { print("receiveValue:\($0)") }) print("end") The output I expect: Creating end sending promise receiveValue: ... receiveCompletion: ... The output I get: Creating end sending promise I don't see an indication the promise was executed. What am I doing wrong?
Replies
2
Boosts
0
Views
1.3k
Activity
Jul ’21