SwiftUI / Combine - подписка на последнюю стоимость одного издателя - PullRequest
0 голосов
/ 03 ноября 2019

Я играю немного с Combine и SwiftUI для моего маленького любимого проекта, учусь на ходу.

Вот LoginModel в его текущем состоянии:

public class LoginModel: ObservableObject {
    @Published var domain: String = ""
    @Published var email: String = ""
    @Published var password: String = ""
    @Published var isValid: Bool = false
    public var didChange = PassthroughSubject<Void, Never>()

    var credentialsValidPublisher: AnyPublisher<Bool, Never> {
        Publishers.CombineLatest($email, $password)
            .receive(on: RunLoop.main)
            .map { (email, password) in
                let emailValid = String.emailValid(emailString: email) // String extension function
                let passwordValid = password.count > 5
                return emailValid && passwordValid
        }
        .breakpointOnError()
        .eraseToAnyPublisher()
    }

    init() {
        // This works just fine
        _ = credentialsValidPublisher.sink { isValid in
            self.isValid = isValid
        }

        // However this does not work at all
        _ = domain
            .publisher
            .receive(on: RunLoop.main)
            .sink { value in
                print(value)
        }
    }
}

Теперь, насколько я понимаю, @Published var foo: String уже имеет Publisher. И это должно быть в состоянии использовать это непосредственно, чтобы подписаться на его изменения.

Изменение переменной credentialsValidPublisher на это тоже работает:

var credentialsValidPublisher: AnyPublisher<Bool, Never> {
    Publishers.CombineLatest3($domain, $email, $password)
        .receive(on: RunLoop.main)
        .map { (domain, email, password) in
            let domainValid = URL.isValidURL(urlString: domain)
            let emailValid = String.emailValid(emailString: email)
            let passwordValid = password.count > 5
            return domainValid && emailValid && passwordValid
    }
    .breakpointOnError()
    .eraseToAnyPublisher()
}

Но это не то, что я хочу. В моем случае мне нужен специальный Publisher, чтобы сопоставить действительную строку URL-адреса с сетевым запросом, а затем пропинговать сервер, чтобы узнать, отвечает ли предоставленный сервер.

Также эта модельподключен к представлению SwiftUI с помощью набора текстовых полей SwiftUI. Буду очень признателен за любую помощь, чтобы указать мне правильное направление.

1 Ответ

0 голосов
/ 03 ноября 2019

Так что я выясняю, как это сделать. В LoginModel под var credentialsValidPublisher: AnyPublisher<Bool, Never> я добавил:

var domainValidPublisher: AnyPublisher<Bool, Never> {
    $domain
        .debounce(for: 0.5, scheduler: DispatchQueue.main)
        .removeDuplicates()
        .map { domain in
            URL.isValidURL(urlString: domain)
        }
        .eraseToAnyPublisher()
}

Тогда я могу просто подписаться на него в init. Я также добавил AnyCancellable свойства, которые мы называем .cancel() на deinit. Вот как выглядит обновленная модель:

public class LoginModel: ObservableObject {
    @Published var domain: String = ""
    @Published var email: String = ""
    @Published var password: String = ""
    @Published var isValid: Bool = false
    public var didChange = PassthroughSubject<Void, Never>()
    private var credentialsValidPublisherCancellable: AnyCancellable!
    private var domainValidCancellable: AnyCancellable!

    var credentialsValidPublisher: AnyPublisher<Bool, Never> {
        Publishers.CombineLatest3($domain, $email, $password)
            .receive(on: RunLoop.main)
            .map { (domain, email, password) in
                let domainValid = URL.isValidURL(urlString: domain)
                let emailValid = String.emailValid(emailString: email)
                let passwordValid = password.count > 5
                return domainValid && emailValid && passwordValid
        }
        .breakpointOnError()
        .eraseToAnyPublisher()
    }
    var domainValidPublisher: AnyPublisher<Bool, Never> {
        $domain
            .debounce(for: 0.5, scheduler: DispatchQueue.main)
            .removeDuplicates()
            .map { domain in
                URL.isValidURL(urlString: domain)
            }
            .eraseToAnyPublisher()
    }

    init() {
        credentialsValidPublisherCancellable = credentialsValidPublisher.sink { isValid in
            self.isValid = isValid
        }

        domainValidCancellable = domainValidPublisher.sink { isValid in
            print("isValid: \(isValid)")
        }
    }

    deinit {
        credentialsValidPublisherCancellable.cancel()
        domainValidCancellable.cancel()
    }
}
...