0

The issue can be seen in the following playground. There are four published values that will be updated asynchronously (an image and three strings). When all four of them have been initialized or subsequently changed then the UI will need to be updated. When I try to capture this data flow using CombineLatest4 the compiler immediately objects to the fourth argument with the message Extra argument in call. (Note: the following code doesn't actually do anything since it only has a publisher, but it is sufficient to produce the error message in Playground).

import Combine import UIKit struct CustomerUpdates { @Published var photo: UIImage! @Published var firstName: String! @Published var lastName: String! @Published var id: String! typealias customerTuple = ( photo: UIImage, firstName: String, lastName: String, id: String ) var validatedCustomer: AnyPublisher< customerTuple, Never > { return Publishers.CombineLatest4( $photo, $firstName, $lastName, $id ) { photo, firstName, lastName, id in if photo == nil || firstName == nil || lastName == nil || id == nil { return nil } return ( photo!, firstName!, lastName!, id! ) } .compactMap .return( on: RunLoop.main ) } } 

My question is, why does the compiler flag the fourth argument (the "id")? Apple's documentation for the CombineLatest4 generic struct says:

A publisher that receives and combines the latest elements from four publishers.

2 Answers 2

2

The problem is that CombineLatest doesn't take a closure, it simply emits the latest value emitted from all of its upstreams whenever any of the upstreams emit a new value. Your closure should be supplied to compactMap instead, which is the operator that takes a closure returning an Optional and only emits a value downstream if the return value was not nil.

struct CustomerUpdates { @Published var photo: UIImage! @Published var firstName: String! @Published var lastName: String! @Published var id: String! typealias CustomerTuple = (photo: UIImage, firstName: String, lastName: String, id: String) var validatedCustomer: AnyPublisher<CustomerTuple, Never> { return Publishers.CombineLatest4($photo, $firstName, $lastName, $id) .compactMap { photo, firstName, lastName, id in guard let photo = photo, let firstName = firstName, let lastName = lastName, let id = id else { return nil } return ( photo, firstName, lastName, id) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } } 

A couple of other issues unrelated to your question: there's no such operator return(on:), you need receive(on:) and you want to pass DispatchQueue.main, not RunLoop.main to be able to publish updates straight to your UI.

Type names should be UpperCamelCase in Swift, so use CustomerTuple instead of customerTuple.

Also, your properties should be Optional (var photo: UIImage?), not implicitly unwrapped optional (var photo: UIImage!).

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! The "Combine In Practice" video at WWDC 2019 shows the instantiation of CombineLatest with a trailing closure. I suppose things must have changed since then. My mistake for using " return(on:)", but again that video shows the use of "RunLoop.main". I hope others who are watching that video see your response. Super helpful!
Either RunLoop.main or DispatchQueue.main will generally work, because both conform to the Combine.Scheduler protocol.
2

The CombineLatest type (and its larger variants including CombineLatest4) don't take a transformation closure. But Publisher has combineLatest operators that do. So you can say this if you want:

 return $photo.combineLatest($firstName, $lastName, $id) { guard let photo = $0, let firstName = $1, let lastName = $2, let id = $3 else { return nil } return ($0, $1, $2, $3) } .compactMap { $0 } .receive(on: RunLoop.main) 

1 Comment

Interesting! I had missed that the Type and the operator were so different. That said, the example in the WWDC video "Combine In Practice" shows an example (skip to minute 26:06) where they create an instance of the CombineLatest type that has a trailing closure. So heads up, all you Combine students, that WWDC code no longer works!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.