I'm struggling to understand why no values are received to sink in the underlying code.
func somePublisher() -> AnyPublisher<Bool, Never> {
let subject = PassthroughSubject<Bool, Never>()
subject.send(true)
subject.send(completion: .finished)
return subject.eraseToAnyPublisher()
}
somePublisher()
.first()
.sink { _ in
print("Completed")
} receiveValue: {
print("Received \($0)")
}
.store(in: &sinks)
Output:
Completed
It looks like values are not received by the publishers down the stream if it was finished before it was connected. Is that right?
How could I fix that if my publisher can finish synchronously?
PassthroughSubject receives a value and passes it along, it doesn't store the value so if you subscribe to it after the value passed through it, you won't receive it.
You can use CurrentValueSubject, which will store the latest value and resend it whenever somebody subscribes to it.
All of this is moot if you send completion: .finished though. A completed publisher won't send any values to a subscriber because it's completed.
This is your fixed code:
func somePublisher() -> AnyPublisher<Bool, Never> {
let subject = CurrentValueSubject<Bool, Never>(true)
return subject.eraseToAnyPublisher()
}
var bag: Set<AnyCancellable> = []
somePublisher()
.first()
.sink { _ in
print("Completed")
} receiveValue: {
print("Received \($0)")
}
.store(in: &bag)
I was struggling with this same problem and I made use of RunLoop.current to overcome it...
func somePublisher() -> AnyPublisher<Bool, Never> {
let subject = PassthroughSubject<Bool, Never>()
RunLoop.current.perform {
// Delay send/completion until next run loop to give subscribers a chance to subscribe
subject.send(true)
subject.send(completion: .finished)
}
return subject.eraseToAnyPublisher()
}
somePublisher()
.first()
.sink { _ in
print("Completed")
} receiveValue: {
print("Received \($0)")
}
.store(in: &sinks)
Related
// MARK: - Combine
/// First Image
let firstImage = UnsplashAPI.randomeImage()
.flatMap { RandomImageResponse in
ImageDownloader.download(url: RandomImageResponse.urls.regular)
}
/// Second Image
let secondImage = UnsplashAPI.randomeImage()
.flatMap { RandomImageResponse in
ImageDownloader.download(url: RandomImageResponse.urls.regular)
}
firstImage.zip(secondImage)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { [unowned self ] completion in
switch completion {
case .finished:
break
case .failure():
self.gameState = .stop
}
},
receiveValue: { [unowned self] first, second in
self.gameImages = [first, second, second, second].shuffled()
self.gameScoreLabel.text = "Score: \(self.gameScore)"
// TODO: Handling game score
self.stopLoaders()
self.setImages()
})
.store(in: &subscrptions)
That's my code:
while using .store(in: &subscrptions)
Here is also --> var subscrptions: Set<AnyCancellable> = []
This is only happening with Xcode 14. Any guesses?
The compiler is probably complaining about the block that begins:
receiveValue: { [unowned self] first, second in
It can't infer the types of first and second. You might be able to resolve the error by indicating their types explicitly. I don't know what type ImageDownloader.download returns, But here's an example using simple types:
import UIKit
import Combine
var greeting = "Hello, playground"
let numbers = [1, 2, 3, 5].publisher
let strings = ["a", "b", "c", "d"].publisher
var cancellables = Set<AnyCancellable>()
numbers.zip(strings)
.sink { completion in
switch completion {
case .failure(let err):
debugPrint(err)
case .finished:
print("Finshed")
}
} receiveValue: { (first:Int, second:String) in
print(first, second)
}
.store(in: &cancellables)
Note how the types are made explicit in the block using (first:Int, second:String) in
I am trying to merge two publishers but one of the completions are never run.
The following is how I create my two publishers and try to use .sink to observe when they complete. The featureFlagPublisher will finish as expected and print "featureFlagPublisher done", but the migratePublisher and the merged publishers will never complete.publisher.send(completion: .finished) is run but nothing happens.
private var cancellables = Set<AnyCancellable>()
func start() {
let featureFlagPublisher = self.startFeatureFlagging()
let migratePublisher = self.migrate()
migratePublisher.sink { _ in
print("migratePublisher done")
} receiveValue: { _ in }.store(in: &cancellables)
featureFlagPublisher.sink { _ in
print("featureFlagPublisher done")
} receiveValue: { _ in }.store(in: &cancellables)
migratePublisher.merge(with: featureFlagPublisher)
.sink { completion in
print("All Done")
} receiveValue: { _ in }
.store(in: &cancellables)
}
private func startFeatureFlagging() -> AnyPublisher<Bool, Never> {
let future = Future<Bool, Never> { promise in
FeatureFlaggingService.shared.start {
promise(.success(true))
}
}
return future.eraseToAnyPublisher()
}
private func migrate() -> AnyPublisher<Bool, Never> {
let future = Future<Bool, Never> { promise in
FavoriteMigrationsAPI.shared.get { result in
switch result {
case .success(let favoriteIDs):
promise(.success(true))
...
}
}
return future.eraseToAnyPublisher()
}
It turned out that the issue was that the controller was deinitialized early and therefore the promise could not respond to the observer. So it had nothing to do with Combine or the code I posted, but with the way I initialized the controller.
I have this code in lrvViewModel.swift
func getVerificationID (phoneNumber: String) -> Future<String?, Error> {
return Future<String?, Error> { promise in
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let e = error {
promise(.failure(e))
return
}
print("verification worked")
self.defaults.set(verificationID, forKey: "authVerificationID")
return promise(.success(verificationID))
}
}
}
and then i call and subscribe to the Publisher in another file like this
let _ = lrvViewModel.getVerificationID(phoneNumber: (lrvViewController?.textField.text)!)
.sink(receiveCompletion: {
print("Error worked")
// does a bunch of stuff
}, receiveValue: {
print("completion worked")
// does a bunch of stuff
})
I don't get any buildtime errors, but whenever I run the app the GetVerificationID function runs fine (prints "Verification worked"), but the code within .sink doesn't run (I don't get any print statements). What's going on?
Edit:
My solution was to give up on combine and go back to RXSwift where the code is simply:
var validateObs = PublishSubject<Any>()
func getVerificationID (phoneNumber: String) {
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let e = error {
print("v error")
self.validateObs.onError(e)
return
}
self.defaults.set(verificationID, forKey: "authVerificationID")
self.validateObs.onCompleted()
}
}
and
lrvViewModel.getVerificationID(phoneNumber: (lrvViewController?.textField.text)!)
let _ = lrvViewModel.validateObs.subscribe(onError: {
let e = $0
print(e.localizedDescription)
// do stuff
}, onCompleted: {
// do stuff
})
Was hoping to not rely on a dependency but RxSwift implementation was much easier.
If someone knows the solution to the Combine Future problem please post! I would still like to know wtf is happening. It's very possible (and likely) I'm just using combine wrong.
Edit 2:
Was using combine wrong. I can duplicate the code I had with RXSwift like this:
let verifyPub = PassthroughSubject<Any, Error>()
func getVerificationID (phoneNumber: String) {
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let e = error {
self.verifyPub.send(completion: .failure(e))
return
}
print("verification worked")
self.defaults.set(verificationID, forKey: "authVerificationID")
self.verifyPub.send(completion: .finished)
}
}
and
let subs = Set<AnyCancellable>()
let pub = lrvViewModel.verifyPub
.sink(receiveCompletion: { completion in
if case let .failure(error) = completion {
print("Error worked")
// do stuff
} else {
print("completion worked")
// do stuff
}
}, receiveValue: { _ in
print("this will never happen")
}).store(in: &subs)
I didnt' understand that in combine there are only two results to a sink, a completion or a value, and that completion is split up into multiple cases. Whereas in RxSwift you have OnNext, OnComplete, and OnError.
Shoutout to the book on Combine from raywanderlich.com. Good stuff.
What's going on is that your .sink is not followed by a .store command, so the pipeline goes out of existence before any value has a chance to come down it.
Your assignment of the pipeline to the empty _ effectively masks the problem. The compiler tried to warn you, and you shut it down.
I'm struggling with combine two requests. I need id from the first one, then start the second one with this first id from the received list. I can't find a nice solution to do this with Swift Combine.
my first request looks like that CarSerview.shared.getCategories() -> AnyPublisher<[Category], CarError> :
private func getCategories() {
CarSerview.shared.getCategories()
.receive(on: DispatchQueue.main)
.map { car in
return car.id
}
.replaceError(with: [])
.assign(to: \.carsIds, on: self)
.store(in: &cancellable)
}
and the second one looks like that CarSerview.shared.getCar() -> AnyPublisher<Car, CarError>:
private func getCar(_ category: CategoryObject) {
SPService.shared.getExcursions(category)
.receive(on: DispatchQueue.main)
.map { car in
return car.cars.compactMap { $0.name }
}
.sink(receiveCompletion: { error in
print(error)
}, receiveValue: { [weak self] result in
self?.cars = result
})
}
how can in chain this two request in one?
Cannot test but the idea is to use .map + .switchToLatest, so can be like as follows
private func getCars() {
CarSerview.shared.getCategories()
.map { car in
return SPService.shared.getExcursions(car.id)
}
.switchToLatest()
.receive(on: DispatchQueue.main)
.map { car in
return car.cars.compactMap { $0.name }
}
.sink(receiveCompletion: { error in
print(error)
}, receiveValue: { [weak self] result in
self?.cars = result
})
}
iOS13's Combine streams of publishers don't appear to be flowing after operator using schedulers.
Here's my code:
import Foundation
import Combine
struct MyPublisher: Publisher {
typealias Output = Int
typealias Failure = Error
func receive<S>(subscriber: S) where S : Subscriber,
Failure == S.Failure,
Output == S.Input {
subscriber.receive(1)
print("called 1")
subscriber.receive(2)
print("called 2")
subscriber.receive(completion: .finished)
print("called finish")
}
}
MyPublisher()
// .receive(on: RunLoop.main) // If this line removed, it will be fine.
// .throttle(for: .milliseconds(1000), scheduler: RunLoop.main, latest: false)) // If this line removed, it will be fine.
// .debounce(for: .milliseconds(1000), scheduler: RunLoop.main)) // If this line removed, it will be fine.
// .delay(for: .milliseconds(1000), scheduler: DispatchQueue.main)) // If this line removed, it will be fine.
.print()
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("finished")
case .failure(let error):
print("error:\(error)")
}
}, receiveValue: { num in
print("\(num)")
})
I expected output to be
1
2
finished
but the actual output is nothing.
If I don't use receive or throttle or debounce or delay. The output will be fine.
Is it a bug or something wrong with my code?
I tried with Playground (Xcode 11 beta3).
Subscription:
I'm unsure of why it works in the case of a single thread but you should make sure to call received(subscription:) on the subscriber. If you do not need to handle the subscribers demands you can use Subscribers.empty:
struct MyPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(subscription: Subscriptions.empty)
_ = subscriber.receive(1)
Swift.print("called 1")
_ = subscriber.receive(2)
Swift.print("called 2")
_ = subscriber.receive(completion: .finished)
Swift.print("called finish")
}
}
AnyCancellable:
You should notice a warning:
Result of call to 'sink(receiveCompletion:receiveValue:)' is unused
That should appear since sink returns an AnyCancellable:
func sink(receiveCompletion: #escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: #escaping ((Self.Output) -> Void)) -> AnyCancellable
Anything that returns an AnyCancellable will get canceled as soon as the AnyCancellable is deallocated.
My speculation is that if you are putting this on another thread, then when the end of the calling method is reached the cancellable will deallocate before the subscription is received. But when received on the current thread it seems to be executing just in time for the subscription and output to show. Most likely the cancellable is being deallocated when the current thread exits.
Use Cancellable
For example :
class ImageLoader: ObservableObject {
#Published var image: UIImage?
private var cancellable: AnyCancellable?
func fetchImages() {
guard let urlString = urlString,
let url = URL(string: urlString) else { return }
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.image = $0 }
}
}
Use the underscore
You can pass the underscore to pass the warning. I've used the example from Naishta's answer.
For example
class ImageLoader: ObservableObject {
#Published var image: UIImage?
func fetchImages() {
guard let urlString = urlString,
let url = URL(string: urlString) else { return }
_ = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.image = $0 }
}
}