1

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()
}
Christopher Graf
  • 1,929
  • 1
  • 17
  • 34

1 Answers1

0

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()
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks, one more question: Why can I not use `.map { $0 == "password1" ? nil : test }` on the result of the first `.map`? (Before `.eraseToAnyPublisher()`). It results in the error `'nil' is incompatible with return type 'String'` in the `guard` line...I know I could simply add the additional check in the first `.map()` but I want to understand why it won't work in general. – Christopher Graf Nov 16 '19 at 11:27
  • As you do not provide argument types explicitly compiler tries to reverse construct expected types to be aligned for Input and previous Output (unwinding generics). So in your output there is nil, but input is tried to be compared with string, so it must be String, but nil cannot be String - you got error. So solution should satisfy types checking - `.map { ($0 ?? "") == "password1" ? nil : $0 }` must work for you. – Asperi Nov 16 '19 at 11:37