目标
我正在尝试使用Range标头实现下载文件的服务。这允许我一次下载一个文件块。
实施 - Observable.generate()
为每个请求创建一个observable并保存我尝试使用的文件:
let downloadObservable = Observable.generate(initialState: 0, condition: { $0 < fileSize }, iterate: { $0 + self.defaultChunkSize })
这似乎很棒!除了较大的文件,它似乎有一个错误。我的请求被取消了。经过调试后,我发现我的工作流程并不像我预期的那样有效。以下是工作流程的其余部分,它附加在上面的行中。
.map( { (startChunk) -> (Int64, Int64) in
// I determine the end chunk so I can download any size file in chunks of size X
let endChunk = (startChunk + self.defaultChunkSize > fileSize ? fileSize : startChunk + self.defaultChunkSize )
return (startChunk, endChunk)
}).flatMap( { [unowned self] (startChunk: Int64, endChunk: Int64) -> Observable<FileChunk> in
// I make the request via alamofire - UNEXPECTED FLOW HERE SEE NOTE #1
return self.makeChunkRequest(url: downloadUrl, startChunk: startChunk, endChunk: endChunk)
}).flatMap( { [unowned self] (fileChunk: FileChunk) -> Observable<FileSaveChunkResult> in
// Upon receiving chunk response save to file
return self.saveChunkToFile(fileChunk: fileChunk, location: localDestinationUrl)
}).flatMap( { (saveResult: FileSaveChunkResult) -> Observable<Progress> in
// Update progress if successful
switch (saveResult) {
case .success(let bytesSaved):
progress.completedUnitCount += bytesSaved
case .failure:
break
}
return Observable.just(progress)
})
注意#1
当我运行并调试它时,我的第一个flatMap
循环直到所有的块请求。我期望这更加顺序,我们将生成一个observable,然后通过flatMap
完成所有转换,然后循环回到开头。
这不是我应该如何实现它吗?
我是否需要在merge()
上使用Observable.generate()
做一些魔术?
答案 0 :(得分:2)
我想我找到了这个问题的解决方案。关键是通过网络请求map
,然后concat
。这样做而不是使用flatMap
。在开始下一个请求之前,concat
运算符将等待请求发送onCompleted。代码如下:
let downloadObservable = Observable.generate(initialState: 0, condition: { $0 < fileSize }, iterate: { $0 + self.defaultChunkSize })
.map( { (startChunk) -> (Int64, Int64) in
let endChunk = (startChunk + self.defaultChunkSize > fileSize ? fileSize : startChunk + self.defaultChunkSize )
return (startChunk, endChunk)
}).map( { [unowned self] (startChunk: Int64, endChunk: Int64) -> Observable<FileChunk> in
return self.makeChunkRequest(url: downloadUrl, startChunk: startChunk, endChunk: endChunk)
}).concat()
.flatMap( { [unowned self] (fileChunk: FileChunk) -> Observable<FileSaveChunkResult> in
return self.saveChunkToFile(fileChunk: fileChunk, location: localDestinationUrl)
}).flatMap( { (saveResult: FileSaveChunkResult) -> Observable<Progress> in
if case .success(let bytesSaved) = saveResult {
progress.completedUnitCount += bytesSaved
}
return Observable.just(progress)
})
我想出了如何将其分成4批次。我把它分散了一些,并在代码中做出评论以帮助:
let generator = Observable.generate(initialState: 0, condition: { $0 < fileSize }, iterate: { $0 + defaultChunkSize })
let chunks = generator.map( { (startChunk) -> (Int64, Int64) in
let endChunk = (startChunk + defaultChunkSize > fileSize ? fileSize : startChunk + defaultChunkSize )
return (startChunk, endChunk)
})
let requests = chunks.buffer(timeSpan: 0.0, count: 4, scheduler: MainScheduler.instance)// makes batches of four item arrays.
.map { (batch) -> Observable<FileChunk> in
let requests = Observable.from(batch) // spreads the four items back out.
return requests.flatMap( { (startChunk: Int64, endChunk: Int64) -> Observable<FileChunk> in
return makeChunkRequest(url: downloadUrl, startChunk: startChunk, endChunk: endChunk)
}) // start the four requests as normal.
}.concat() // wait until the four requests are finished before allowing the next four to begin.
let downloadObservable = requests
.flatMap( { (fileChunk: FileChunk) -> Observable<FileSaveChunkResult> in
return saveChunkToFile(fileChunk: fileChunk, location: localDestinationUrl)
}).flatMap( { (saveResult: FileSaveChunkResult) -> Observable<Progress> in
if case .success(let bytesSaved) = saveResult {
progress.completedUnitCount += bytesSaved
}
return Observable.just(progress)
})