Search code examples
swiftgenericsswiftuicombinecombinelatest

Mistake in WWDC 2019 "Combine in Practice" Code example for CombineLatest?


I tried to follow the code snippet of WWDC 2019's Combine in Practice talk (starting at minute 26:00) or see slides 179 ff. but it won't compile and looking at the API some parts don't make sense to me (e.g., calling CombineLatest.init(A, B) with a third argument of type closure. I tried to adapt the examples so they compile.

Question Part 1/2: Can someone help me out and let me know if I am misunderstanding the WWDC 2019 code snippets?

First code snippet (slide 179)

@Published var password: String = ""
@Published var passwordAgain: String = ""

var validatedPassword: CombineLatest<Published<String>, Published<String>, String?> {
    return CombineLatest($password, $passwordAgain) { password, passwordAgain in
        guard password == passwordAgain, password.count > 8 else { return nil }
        return password
    }
}

I can only get this snippet to at least return me the Publisher fromCombineLatest`

  • adding the Publishers enum to the namespace of CombineLatest
  • removing the trailing closure
  • adding .Publisher to Published<String>
@Published var password: String = ""
@Published var passwordAgain: String = ""

var validatedPassword: Publishers.CombineLatest<Published<String>.Publisher, Published<String>.Publisher> {
    return Publishers.CombineLatest($password, $passwordAgain)
}

Second code snippet (slide 185)

@Published var password: String = ""
@Published var passwordAgain: String = ""

var validatedPassword: Map<CombineLatest<Published<String>, Published<String>, String?>> {
    return CombineLatest($password, $passwordAgain) { password, passwordAgain in
        guard password == passwordAgain, password.count > 8 else { return nil }
        return password
    }
    .map { $0 == "password1" ? nil : $0 }
}

I can get this snippet to compile when:

  • doing all of the steps listed for the first snippet
  • Adding Publishers. in front of Map
  • moving the <> to the correct position
  • returning Publishers.Map explicitly and by using the correct parameter upstream:
@Published var password: String = ""
@Published var passwordAgain: String = ""

var validatedPassword: Publishers.Map<Publishers.CombineLatest<Published<String>.Publisher, Published<String>.Publisher>, String?> {
        return Publishers.Map(upstream: Publishers.CombineLatest($password, $passwordAgain)) { password, passwordAgain in
            guard password == passwordAgain, password.count > 8 else { return nil }
            return password
    }

or when including the .map {} from the slide:

  • by wrapping the var type in another Publishers.Map<..., String?>
@Published var password: String = ""
@Published var passwordAgain: String = ""
var validatedPassword: Publishers.Map<Publishers.Map<Publishers.CombineLatest<Published<String>.Publisher, Published<String>.Publisher>, String?>, String?> {
        return Publishers.Map(upstream: Publishers.CombineLatest($password, $passwordAgain)) { password, passwordAgain in
            guard password == passwordAgain, password.count > 8 else { return nil }
            return password
    }
    .map { $0 == "password1" ? nil : $0 }
}

Question Part 2/2: What would be the swifty way to do this? e.g, by using something like this (which does not compile):

@Published var password: String = ""
@Published var passwordAgain: String = ""

var validatedPassword: AnyPublisher<String?, Never> {
    return Just($password)
        .combineLatest($passwordAgain) { password, passwordAgain in
            guard password == passwordAgain, password.count > 8 else { return nil }
            return password
    }
    .map{ $0 == "password1" ? nil : $0 }
    .eraseToAnyPublisher()
}

Solution

  • On Q1:

    WWDC demos were using API existed at that time. Time passed - API changed. That's it.

    On Q2:

    Try instead like the following...

    var validatedPassword: AnyPublisher<String?, Never> {
        return Publishers.CombineLatest($password, $passwordAgain)
            .map { password, passwordRepeat in
                guard password == passwordRepeat, password.count > 8 else { return nil }
                return password
            }
            .map { ($0 ?? "") == "password1" ? nil : $0 }
            .eraseToAnyPublisher()
    }