Как синхронизировать или заставить Observable ждать на Swift?

Итак, у меня есть эта функция входа в систему, где я хочу вернуть логическое значение моему контроллеру .xib, где он должен внести некоторые изменения в зависимости от успеха входа в систему.

 func login(cpf: String) -> Bool {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    var success = false

    dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .subscribe(onNext: { (userToken) in
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)
            userData.synchronize()
            success = true
    }, onError: { (error) in
        print("Login Error: \(error)")
    }).disposed(by: disposeBag)

    return success
}

Метод postSimpleResponse() возвращает объект Observable типа Any?.

Дело в том, что мой метод login() возвращается до того, как логическая переменная success становится истинной в подписке.

Как я могу решить это?


person Emmanuel    schedule 15.01.2019    source источник
comment
Весь смысл RX в том, что вы моделируете асинхронные задачи с помощью Observables. Если вы выходите из наблюдаемого мира, вы теряете большую часть цели его использования. Правильным решением здесь было бы создать var loginState: Observable<LoginState> и подписаться на него, чтобы обновлять свой пользовательский интерфейс при каждом изменении.   -  person Alexander    schedule 16.01.2019
comment
Примечание. Это НЕ дубликат этого вопроса. Если вы используете RX, обработчики завершения не являются правильным решением для асинхронного моделирования данных.   -  person Alexander    schedule 16.01.2019


Ответы (2)


Вы можете сопоставить наблюдаемое с наблюдаемым Bool (хотя Bool здесь не обязательно, вы можете просто использовать Void) и вернуть Observable из метода login.

Как это:

func login(cpf: String) -> Observable<Bool> {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    return dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .do(onNext: {
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)
        })
        .map { _ in
            return true
    }
}

И затем наблюдайте за ним:

login(cpf: "data").subscribe(onNext: { _ in
    // Success
}, onError: { _ in
    // Failure
}).disposed(by: disposeBag)
person Mukesh    schedule 16.01.2019

Как было сказано ранее:

Весь смысл Rx в том, что вы моделируете асинхронные задачи с помощью Observables.

Но если вам действительно нужно сделать наблюдаемую синхронность (дождитесь onNext или, возможно, onCompleted), вы можете использовать DispatchGroup (монитор).

Ваш код должен выглядеть так:

func login(cpf: String) -> Bool {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    var result = false

    let group = DispatchGroup()
    group.enter()

    let subscription = dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .subscribe(onNext: {
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)

            result = true
            group.leave()
        }, onError: { _ in 
            group.leave()
        })

    group.wait() // wait till leave() method called
    subscription.dispose()

    return result
}

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

person Constantine Nikolsky    schedule 22.04.2021