我有一个必须下载多个大文件的应用程序。我希望它依次而不是同时下载每个文件。当它同时运行时,该应用程序将过载并崩溃。
所以。我试图将downloadTaskWithURL包装在NSBlockOperation内,然后在队列上设置maxConcurrentOperationCount
= 1。我在下面编写了此代码,但由于两个文件同时下载而无法正常工作。
import UIKit
class ViewController: UIViewController, NSURLSessionDelegate, NSURLSessionDownloadDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
processURLs()
}
func download(url: NSURL){
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
let downloadTask = session.downloadTaskWithURL(url)
downloadTask.resume()
}
func processURLs(){
//setup queue and set max conncurrent to 1
var queue = NSOperationQueue()
queue.name = "Download queue"
queue.maxConcurrentOperationCount = 1
let url = NSURL(string: "http://azspeastus.blob.core.windows.net/azurespeed/100MB.bin?sv=2014-02-14&sr=b&sig=%2FZNzdvvzwYO%2BQUbrLBQTalz%2F8zByvrUWD%2BDfLmkpZuQ%3D&se=2015-09-01T01%3A48%3A51Z&sp=r")
let url2 = NSURL(string: "http://azspwestus.blob.core.windows.net/azurespeed/100MB.bin?sv=2014-02-14&sr=b&sig=ufnzd4x9h1FKmLsODfnbiszXd4EyMDUJgWhj48QfQ9A%3D&se=2015-09-01T01%3A48%3A51Z&sp=r")
let urls = [url, url2]
for url in urls {
let operation = NSBlockOperation { () -> Void in
println("starting download")
self.download(url!)
}
queue.addOperation(operation)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
//code
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
//
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
var progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
println(progress)
}
}
如何正确编写此代码以实现我一次只下载一个文件的目标。
您的代码无法正常URLSessionDownloadTask
运行,因为它异步运行。因此,BlockOperation
下载操作在下载完成之前完成,因此在依次触发操作的同时,下载任务将异步且并行地继续。
为了解决这个问题,您可以将请求包装在异步Operation
子类中。有关更多信息,请参见《 并发编程指南》
中的“为并发执行配置操作
” 。
但是,在我说明如何根据您的情况(基于委托URLSession
)来执行此操作之前,让我首先向您展示使用完成处理程序表示法时更简单的解决方案。稍后,我们将在此基础上解决您更复杂的问题。因此,在Swift
3及更高版本中:
class DownloadOperation : AsynchronousOperation {
var task: URLSessionTask!
init(session: URLSession, url: URL) {
super.init()
task = session.downloadTask(with: url) { temporaryURL, response, error in
defer { self.finish() }
guard
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
// handle invalid return codes however you'd like
return
}
guard let temporaryURL = temporaryURL, error == nil else {
print(error ?? "Unknown error")
return
}
do {
let manager = FileManager.default
let destinationURL = try manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(url.lastPathComponent)
try? manager.removeItem(at: destinationURL) // remove the old one, if any
try manager.moveItem(at: temporaryURL, to: destinationURL) // move new one there
} catch let moveError {
print("\(moveError)")
}
}
}
override func cancel() {
task.cancel()
super.cancel()
}
override func main() {
task.resume()
}
}
哪里
/// Asynchronous operation base class
///
/// This is abstract to class emits all of the necessary KVO notifications of `isFinished`
/// and `isExecuting` for a concurrent `Operation` subclass. You can subclass this and
/// implement asynchronous operations. All you must do is:
///
/// - override `main()` with the tasks that initiate the asynchronous task;
///
/// - call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `finish()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `finish()` is called.
class AsynchronousOperation: Operation {
/// State for this operation.
@objc private enum OperationState: Int {
case ready
case executing
case finished
}
/// Concurrent queue for synchronizing access to `state`.
private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent)
/// Private backing stored property for `state`.
private var rawState: OperationState = .ready
/// The state of the operation
@objc private dynamic var state: OperationState {
get { return stateQueue.sync { rawState } }
set { stateQueue.sync(flags: .barrier) { rawState = newValue } }
}
// MARK: - Various `Operation` properties
open override var isReady: Bool { return state == .ready && super.isReady }
public final override var isExecuting: Bool { return state == .executing }
public final override var isFinished: Bool { return state == .finished }
// KVO for dependent properties
open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if ["isReady", "isFinished", "isExecuting"].contains(key) {
return [#keyPath(state)]
}
return super.keyPathsForValuesAffectingValue(forKey: key)
}
// Start
public final override func start() {
if isCancelled {
finish()
return
}
state = .executing
main()
}
/// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
open override func main() {
fatalError("Subclasses must implement `main`.")
}
/// Call this function to finish an operation that is currently executing
public final func finish() {
if !isFinished { state = .finished }
}
}
然后,您可以执行以下操作:
for url in urls {
queue.addOperation(DownloadOperation(session: session, url: url))
}
因此,这是将异步URLSession
/ NSURLSession
请求包装在异步Operation
/
NSOperation
子类中的一种非常简单的方法。更一般而言,这是一种有用的模式,AsynchronousOperation
用于将一些异步任务包装在Operation
/
NSOperation
对象中。
不幸的是,在您的问题中,您想使用基于委托的URLSession
/,NSURLSession
以便可以监视下载进度。这更复杂。
这是因为NSURLSession
在会话对象的委托处调用了“任务完成”
委托方法。这是的令人毛骨悚然的设计功能NSURLSession
(但是Apple这样做是为了简化后台会议,在这里不相关,但是我们受制于该设计限制)。
但是,我们必须在任务完成时异步完成操作。因此,我们需要某种方式让会话确定何时didCompleteWithError
调用哪个操作才能完成。现在您可以使每个操作都有其自己的NSURLSession
对象,但是事实证明这效率很低。
因此,为了解决这个问题,我维护了一个字典,该字典以任务的键为关键字taskIdentifier
,用于标识适当的操作。这样,下载完成后,您可以“完成”正确的异步操作。从而:
/// Manager of asynchronous download `Operation` objects
class DownloadManager: NSObject {
/// Dictionary of operations, keyed by the `taskIdentifier` of the `URLSessionTask`
fileprivate var operations = [Int: DownloadOperation]()
/// Serial OperationQueue for downloads
private let queue: OperationQueue = {
let _queue = OperationQueue()
_queue.name = "download"
_queue.maxConcurrentOperationCount = 1 // I'd usually use values like 3 or 4 for performance reasons, but OP asked about downloading one at a time
return _queue
}()
/// Delegate-based `URLSession` for DownloadManager
lazy var session: URLSession = {
let configuration = URLSessionConfiguration.default
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
/// Add download
///
/// - parameter URL: The URL of the file to be downloaded
///
/// - returns: The DownloadOperation of the operation that was queued
@discardableResult
func queueDownload(_ url: URL) -> DownloadOperation {
let operation = DownloadOperation(session: session, url: url)
operations[operation.task.taskIdentifier] = operation
queue.addOperation(operation)
return operation
}
/// Cancel all queued operations
func cancelAll() {
queue.cancelAllOperations()
}
}
// MARK: URLSessionDownloadDelegate methods
extension DownloadManager: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
operations[downloadTask.taskIdentifier]?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
operations[downloadTask.taskIdentifier]?.urlSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
}
}
// MARK: URLSessionTaskDelegate methods
extension DownloadManager: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let key = task.taskIdentifier
operations[key]?.urlSession(session, task: task, didCompleteWithError: error)
operations.removeValue(forKey: key)
}
}
/// Asynchronous Operation subclass for downloading
class DownloadOperation : AsynchronousOperation {
let task: URLSessionTask
init(session: URLSession, url: URL) {
task = session.downloadTask(with: url)
super.init()
}
override func cancel() {
task.cancel()
super.cancel()
}
override func main() {
task.resume()
}
}
// MARK: NSURLSessionDownloadDelegate methods
extension DownloadOperation: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard
let httpResponse = downloadTask.response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
// handle invalid return codes however you'd like
return
}
do {
let manager = FileManager.default
let destinationURL = try manager
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(downloadTask.originalRequest!.url!.lastPathComponent)
try? manager.removeItem(at: destinationURL)
try manager.moveItem(at: location, to: destinationURL)
} catch {
print(error)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
print("\(downloadTask.originalRequest!.url!.absoluteString) \(progress)")
}
}
// MARK: URLSessionTaskDelegate methods
extension DownloadOperation: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
defer { finish() }
if let error = error {
print(error)
return
}
// do whatever you want upon success
}
}
然后像这样使用它:
let downloadManager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
let urlStrings = [
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"
]
let urls = urlStrings.compactMap { URL(string: $0) }
let completion = BlockOperation {
print("all done")
}
for url in urls {
let operation = downloadManager.queueDownload(url)
completion.addDependency(operation)
}
OperationQueue.main.addOperation(completion)
}
问题内容: 我想创建一个与此服务类似的服务(从Here引用),以在Android中异步下载多个文件。 用户可以选择不同片段中的下载项目。我的策略是,随着用户选择项目并按下下载按钮,这些项目将被传递到其中,负责下载文件。然后将下载任务添加到中。 这里有一些问题: 我知道是由某些已定义的操作触发的。但是我想要创建一个后台服务,监视,如果有新消息可用,那么将调用一些线程来操作任务。 如果我对这个定制商品
我正在尝试下载https://occ.ca/our-publications 我的最终目标是解析PDF文件中的文本并定位某些关键字。 到目前为止,我已经能够抓取所有页面上PDF文件的链接。我已将这些链接保存到列表中。现在,我想浏览一下列表并用Python下载所有pdf文件。下载完文件后,我想对它们进行解析。 这是我迄今为止使用的代码: 这是我运行代码时遇到的错误。 回溯(最近的最后一次调用):ur
问题内容: 我正在尝试使用线程下载多个与模式匹配的文件。该模式可以匹配1或5或10个差异大小的文件。 为了简单起见,可以说下载文件的实际代码在downloadFile()方法中,而fileNames是与模式匹配的文件名列表。我该如何使用线程。每个线程将仅下载一个文件。建议在for循环内创建一个新线程。 问题答案: 您确实想使用ExecutorService而不是单个线程,它更干净,性能可能更高,并
问题内容: 我正在尝试使用selenium从网站下载pdf文件,但我能够打开文件,但无法使用代码自动下载。 码: 请提出建议。先感谢您 问题答案: 以上问题现已解决
问题内容: 两部分的问题。我正在尝试从互联网档案中下载多个已存档的Cory Doctorow播客。我的iTunes提要中未包含的旧版本。我已经编写了脚本,但是下载的文件格式不正确。 问题1-如何更改以下载zip mp3文件?问题2-将变量传递到URL的更好方法是什么? 该脚本是从这里改编的 问题答案: 这是我处理URL构建和下载的方式。我确保将文件命名为url的基本名称(后跟斜杠后的最后一位),并
问题内容: 我希望能够使用远程链接从Rails调用文件下载。我的链接正常工作,看起来像这样: 响应中包含正确的文件,但是当响应进入时,下载对话框不会出现。是否可以调用下载对话框?我需要这个才能在IE和Firefox中工作 谢谢, -C 问题答案: ParticleTree的一篇文章提到了一个简单有效的解决方案, 它根本不涉及AJAX ,但是 确实导致文件下载 而用户无需离开当前页面: 这并不能直接