RxSwift, как связать разные наблюдаемые

Я все еще новичок в реактивном программировании и RxSwift в целом. Я хочу связать две разные операции. В моем случае я просто хочу загрузить zip-файл с веб-сервера, а затем распаковать его локально. Я также хочу, заодно, показать прогресс скачанных файлов. Итак, я начал создавать первую наблюдаемую:

 class func rx_download(req:URLRequestConvertible, testId:String) -> Observable<Float> {

    let destination:Request.DownloadFileDestination = ...

    let obs:Observable<Float> = Observable.create { observer in
       let request =  Alamofire.download(req, destination: destination)
        request.progress { _, totalBytesWritten, totalBytesExpectedToWrite in
            if totalBytesExpectedToWrite > 0 {
                observer.onNext(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
            }
            else {
                observer.onNext(0)
            }
        }
        request.response {  _, response, _, error in
            if let responseURL = response {
                if responseURL.statusCode == 200 {
                    observer.onNext(1.0)
                    observer.onCompleted()
                } else  {
                    let error = NSError(domain: "error", code: responseURL.statusCode, userInfo: nil)
                    observer.onError(error)
                }
            } else {
                let error = NSError(domain: "error", code: 500, userInfo: nil)
                observer.onError(error)
            }

            }
            return AnonymousDisposable () {
                request.cancel()
            }
        }
    return obs.retry(3)

}

После этого создаю аналогичную функцию для распаковки

class func rx_unzip(testId:String) -> Observable<Float> {
    return Observable.create { observer in
        do {
            try Zip.unzipFile(NSURL.archivePath(testId), destination: NSURL.resourceDirectory(testId), overwrite: true, password: nil)
                {progress in
                    observer.onNext(Float(progress))
                }
        } catch let error {
            observer.onError(error)
        }
        observer.onCompleted()
        return NopDisposable.instance
    }
}

На данный момент у меня есть эта логика на уровне «Просмотр модели», поэтому я загружаю-> подписываюсь на завершение-> разархивирую

Я хочу объединить два Observable в один, чтобы сначала выполнить загрузку, а затем по завершении распаковать файл. Есть какой-либо способ сделать это?


person Punty    schedule 11.03.2016    source источник


Ответы (2)


Оператор Concat требует того же типа данных

Действительно, оператор concat позволяет вам принудительно применять последовательность наблюдаемых, однако проблема, с которой вы можете столкнуться при использовании concat, заключается в том, что оператор concat требует, чтобы Observable имели один и тот же общий тип.

let numbers     : Observable<Int>    = Observable.from([1,2,3])
let moreNumbers : Observable<Int>    = Observable.from([4,5,6])
let names       : Observable<String> = Observable.from(["Jose Rizal", "Leonor Rivera"])


// This works
numbers.concat(moreNumbers)

// Compile error
numbers.concat(names)

Оператор FlatMap позволяет объединить последовательность Observables

Вот пример.

class Tag {
    var tag: String = ""
    init (tag: String) {
        self.tag = tag
    }
}

let getRequestReadHTML : Observable<String> = Observable
                            .just("<HTML><BODY>Hello world</BODY></HTML>")

func getTagsFromHtml(htmlBody: String) -> Observable<Tag> {
    return Observable.create { obx in

        // do parsing on htmlBody as necessary

        obx.onNext(Tag(tag: "<HTML>"))
        obx.onNext(Tag(tag: "<BODY>"))
        obx.onNext(Tag(tag: "</BODY>"))
        obx.onNext(Tag(tag: "</HTML>"))

        obx.onCompleted()

        return Disposables.create()
    }
}

getRequestReadHTML
    .flatMap{ getTagsFromHtml(htmlBody: $0) }
    .subscribe (onNext: { e in
        print(e.tag)
    })

Обратите внимание, что getRequestReadHTML имеет тип Observable<String>, а функция getTagsFromHtml имеет тип Observable<Tag>.

Использование нескольких плоских карт может увеличить частоту излучения

Однако будьте осторожны, потому что оператор flatMap принимает массив (например, [1,2,3]) или последовательность (например, Observable) и испускает все элементы как выбросы. Вот почему известно преобразование 1...n.

Если вы определили наблюдаемый объект, такой как сетевой вызов, и уверены, что будет только одно излучение, вы не столкнетесь с какими-либо проблемами, поскольку его преобразование - это 1...1 (т.е. один наблюдаемый объект в один NSData). Здорово!

Однако, если ваш Observable имеет несколько выбросов, будьте очень осторожны, потому что связанные операторы flatMap будут означать, что выбросы будут экспоненциально (?) Увеличиваться.

Конкретный пример: когда первый наблюдаемый объект испускает 3 выброса, оператор flatMap преобразует 1...n, где n = 2, что означает, что теперь всего 6 выбросов. Другой оператор flatMap может снова преобразовать 1...n, где n = 2, что означает, что теперь всего 12 выбросов. Дважды проверьте, соответствует ли это вашему ожидаемому поведению.

person dsapalo    schedule 15.11.2016

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

Есть предостережение: вы получите значения прогресса в диапазоне от 0,0 до 1,0 от rx_download, а затем снова прогресс с rx_unzip начнется с 0,0. Это может сбить пользователя с толку, если вы хотите показать прогресс в одном окне прогресса.

Возможный подход - показать ярлык, описывающий, что происходит, вместе с просмотром хода выполнения. Вы можете map каждый Observable добавить в кортеж, содержащий значение прогресса и текст описания, а затем использовать concat. Это может выглядеть так:

let mappedDownload = rx_download.map {
    return ("Downloading", $0)
}

let mappedUnzip = rx_download.map {
    return ("Unzipping", $0)
}

mapped1.concat(mapped2)
 .subscribeNext({ (description, progress) in
    //set progress and show description
})

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

person Michał Ciuba    schedule 11.03.2016