私はまだリアクティブプログラミングの初心者であり、RxSwift全般です。 2つの異なる操作を連鎖させたい。私の場合、Webサーバーから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
}
}
今のところ、「モデルレイヤーの表示」にこのロジックがあるので、ダウンロード->完了時にサブスクライブ->解凍します
私が欲しいのは、最初にダウンロードを実行し、完了したらファイルを解凍するために、2つのObservableを1つに結合することです。これを行う方法はありますか?
Concat
演算子には同じデータ型が必要です実際、concat
演算子を使用すると、オブザーバブルのシーケンスを適用できますが、concat
の使用で発生する可能性のある問題は、concat
演算子でObservable
sは同じ汎用タイプを持っています。
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)
Observable
sのシーケンスをチェーンできます。これが例です。
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...1
(つまり、1つのオブザーバブルから1つのNSData)であるため、問題は発生しません。すごい!
ただし、Observableに複数の排出量がある場合は、チェーンされたflatMap
演算子は排出量が指数関数的に(?)増加することを意味するため、十分に注意してください。
具体的な例は、最初の観測値が3つの排出量を放出する場合、flatMap演算子は1...n
を変換します。ここでn = 2は、合計6つの排出量があることを意味します。別のflatMapオペレーターは、再び1...n
を変換できます。ここで、n = 2です。これは、合計12のエミッションがあることを意味します。これが予想される動作であるかどうかを再確認してください。
concat
演算子を使用して、これら2つのObservableをチェーンできます。結果のObservableは、最初の値からnext
値を送信し、完了すると、2番目の値から送信します。
注意点があります。rx_download
から0.0から1.0の範囲の進捗値を取得し、rx_unzip
からの進捗は0.0から始まります。単一の進行状況ビューに進行状況を表示する場合、これはユーザーを混乱させる可能性があります。
考えられるアプローチは、進行状況ビューとともに何が起こっているかを説明するラベルを表示することです。進行状況の値と説明テキストを含むタプルに対して各Observableをmap
してから、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
})
もちろん、考えられる解決策はたくさんありますが、これはコーディングの問題というよりも設計上の問題です。