ios - Swift (iOS),等待所有图片下载完成再返回

标签 ios swift alamofire

我正在编写一个 Swift iOS 应用程序(我的第一个,所以请耐心等待),我在其中使用 Swifter HTTP 服务器来处理各种请求。一个这样的请求是带有 JSON 数组的 HTTP POST,指定要从 Web 下载的图像(并做一些其他事情,与手头的问题无关)。

我使用 Alamofire 下载图像(这很好用),但我正在寻找好的(最好是简单的)方法来等待所有图像完成下载,然后再返回对上面 POST 请求的响应(因为响应有包含指示结果的 JSON,包括任何失败的下载)。

完成此任务的好方法是什么(最好不阻塞主线程)?

这里有一些片段来说明:

public func webServer(publicDir: String?) -> HttpServer {
  let server = HttpServer()

  server.POST["/images/update"] = { r in
        let images = ...(from JSON array in body)
        let updateResult = ImageUtil.updateImages(images)
        let resultJson: String = Mapper().toJSONString(updateResult, prettyPrint: true)!

        if updateResult.success {
            return .OK(.Text(resultJson))
        }
        return HttpResponse.RAW(500, "Error", nil, { $0.write([UInt8](updateResult.errorMessage.utf8)) })
    }
}

static func updateImages(images: [ImageInfo]) -> UpdateResult {
  let updateResult = UpdateResult()
  for image in images {
    Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
        .validate()
        .response{_, _, _, error in
            if let error = error {
                Log.error?.message("Error downloading file \(image.imageUrl) to \(image.fileName): \(error)")
            } else {
                updateResult.filesDownloaded++
                Log.info?.message("Downloaded file \(image.imageUrl) to \(image.fileName)")
            }}
    }

    return updateResult // It obviously returns before any images finish downloading.  I need to wait until all images have downloaded before I can return an accurate result.
}

更新 1/23/2016,使用 dispatcher per bbum

这是尝试使用调度程序机制,但对 updateImages 的调用仍然会立即返回(即使使用 dispatch_sync)。

在将我的 HTTP 响应返回给调用方之前,如何等待所有下载完成?

public func webServer(publicDir: String?) -> HttpServer {
    let server = HttpServer()

    server.POST["/images/update"] = { r in
        let imageDownloader = ImageDownloader()
        imageDownloader.updateimageFiles(adFilesOnServer)
        let resultJson: String = Mapper().toJSONString(imageDownloader.updateResult, prettyPrint: true)!

        if imageDownloader.updateResult.success {
            return .OK(.Text(resultJson))
        }
        return HttpResponse.RAW(500, "Error", nil, { $0.write([UInt8](imageDownloader.updateResult.errorMessage.utf8)) })
    }
}

class ImageDownloader {

    var updateResult = AdUpdateResult()
    private var imageFilesOnServer = [ImageFile]()

    private let fileManager = NSFileManager.defaultManager()
    private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.imageDirectory, isDirectory: true)

    private let semaphore = dispatch_semaphore_create(4)
    private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL)

    func updateimageFiles(imageFilesOnServer: [ImageFile]) {
        self.imageFilesOnServer = imageFilesOnServer

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

        for serverFile in imageFilesOnServer {
            downloadImageFileFromServer(serverFile)
        }

        dispatch_sync(downloadQueue) {
            dispatch_sync(dispatch_get_main_queue()) {
                print("done") // It gets here before images have downloaded.
            }
        }
    }

    private func downloadImageFileFromServer(serverFile: ImageFile) {

        let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)

        Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
        .validate()
        .response { _, _, _, error in
            if let error = error {
                Log.error?.message("Error downloading file \(serverFile.imageUrl) to \(serverFile.fileName): \(error)")
            } else {
                self.updateResult.filesDownloaded++
                Log.info?.message("Downloaded file \(serverFile.imageUrl) to \(serverFile.fileName)")
            }
            dispatch_semaphore_signal(self.semaphore)
        }
    }
}

最佳答案

首先,您真的不想在没有某种限制的情况下触发每张图片的请求。信号量很适合这类事情。

其次,您需要基本上计算未完成操作的数量,然后在它们全部完成时触发完成处理程序。或者,如果可以随时开始新操作,您可能希望对操作进行分组。

所以,伪代码:

sema = dispatch_semaphore_create(4) // 4 being # of concurrent operations allowed
serialQ = dispatch_queue_create(.., SERIAL)

dispatch_async(serialQ) {
    dispatch_semaphore_wait(sema, FOREVER) // will block if there are 4 in flight already
    for image in images {
        downloader.downloadAsync(image, ...) { // completion
              dispatch_semaphore_signal(sema) // signal that we are done with one
              ... handle downloaded image or error ...
              ... add downloaded images to downloadedImages ...
        }
    }
}

dispatch_async(serialQ) {
     // since serialQ is serial, this will be executed after the downloads are done
    dispatch_async(main_queue()) {
         yo_main_queue_here_be_yer_images(... downloadedImages ...)
    }
}

关于ios - Swift (iOS),等待所有图片下载完成再返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34966491/

相关文章:

ios - dismissViewControllerAnimated :completion: not executed

ios - Tesseract OCR w/iOS & Swift 返回错误或乱码

ios - 私有(private)类(class)成员可在类(class)外访问

ios - 如何将按钮移动到随机位置? (Swift) 当 iAd 正在展示时

ios - 如何从 appDelegate 访问每个 ViewController?

ios - UISplitviewcontroller - 主视图和详细 View 中的大小类问题

ios - Swifty Json 解析

swift - 当分数高于当前高分时,获得高分?

swift - Alamofire 相当于 AFHTTPSessionManager

ios - 如何使用 Alamofire 关闭证书验证?