首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >按url's - iOS Swift顺序下载图片

按url's - iOS Swift顺序下载图片
EN

Stack Overflow用户
提问于 2019-09-14 18:24:21
回答 1查看 1.9K关注 0票数 0

我在一个数组中有10个urls,当其中4个下载时,我需要显示它们。我使用信号量和组来实现。但看来我陷入僵局了。不知道该怎么做。请告诉我怎么做

在操场上模拟:

代码语言:javascript
复制
PlaygroundPage.current.needsIndefiniteExecution = true

let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
    
    group.enter()
    semaphore.wait()
    queue.async(group: group) {
        print("Downloading image \(i)")
        // Simulate a network wait
        Thread.sleep(forTimeInterval: 3)
        nums.append(i)
        print("Hola image \(i)")
        if nums.count == 4 {
            print("4 downloaded")
            semaphore.signal()
            group.leave()
        }
    }
    if nums.count == 4 {
        break
    }
}

group.notify(queue: DispatchQueue.main) {
    print(nums)
}

我把这个放在操作/操作控制台里

代码语言:javascript
复制
> Downloading image 1
> Downloading image 2
> Downloading image 3
> Downloading image 4

信号量(41269,0x70000ade5000) malloc:*对象0x1077d4750错误:未分配被释放的指针 信号量(41269,0x70000ade5000) malloc:*在malloc_error_break中设置一个断点以进行调试

我希望按顺序打印1,2,3,4

我知道我试图访问异步中的共享资源,但不确定如何解决这个问题。请指点

此外,如果我想一次下载4,4,2个任务,以便在我的输出中显示1,2,3,4,5,6,7,8,9,10,那么我如何与信号量一起使用它?

EN

回答 1

Stack Overflow用户

发布于 2019-09-16 00:41:24

您的标题是“按url的顺序下载图像”,但是您的代码片段并没有试图这样做。它似乎试图使用信号量一次将下载限制为四幅图像,但这并不能保证它们是正常的。

值得称赞的是,这段代码片段没有按顺序顺序下载它们,因为这会造成巨大的性能损失。同样好的是,此代码段将这种程度的并发限制在合理的水平上,从而避免耗尽工作线程或导致后一些请求超时。因此,使用信号量允许并发图像下载,但一次将其限制为4个的想法是一种很好的方法;如果您想要的话,我们只需要在最后对结果进行排序。

但在讨论之前,让我们先解决提供的代码片段中的一系列问题:

  1. 每次迭代都调用group.enter()semaphore.wait() (这是正确的),但是只有当i4 (不正确)时,才调用group.leave()semaphore.signal()。您希望在每次迭代中都使用leavesignal。 显然,break调用也是不需要的。 因此,要修复这个“一次执行四次”的过程,可以简化以下代码: 让组= DispatchGroup()让队列= DispatchQueue.global(qos:.userInteractive)让信号量=DispatchSemaphore(值: 4) var num: Int = [] for i在1.10{ group.enter() semaphore.wait() queue.async() { // NB:group参数不需要打印(“下载图像(i)") //模拟网络等待Thread.sleep(forTimeInterval: 3) nums.append(i)打印(“Hola图像(i)") semaphore.signal() group.leave() }}group.notify(队列:.main) {print(Num)} 这将一次下载四个图像,并在它们全部完成后调用您的group.notify闭包。
  2. 虽然上面的代码修复了信号量和组逻辑,但在上面的代码片段中还隐藏着另一个问题。它正在从多个后台线程更新nums数组,但Array并不是线程安全的.因此,您应该将这些更新同步到该数组。实现这一目标的一种简单方法是将更新发送回主线程。(任何串行队列都可以,但是主线程可以正常工作。) 另外,由于不应该在主队列上调用wait,所以我建议您显式地将整个for循环分配给后台线程: DispatchQueue.global(qos:.utility).async { let group = DispatchGroup() let queue = DispatchQueue.global(qos:.userInteractive) let信号量=DispatchSemaphore(值: 4) var num: Int = [] for i in 1.10{ group.enter() semaphore.wait() queue.async() {print(“下载图像(i)")//模拟网络等待Thread.sleep(forTimeInterval: 3) DispatchQueue.main.async { nums.append(i) print("Hola image (i)") } semaphore.signal() group.leave() }group.notify(队列:.main){打印(Num)}} 这就是现在正确的“一次做四次,当它完成时让我知道”。

好了,现在我们正在正确下载所有的图像,让我们找出如何排序的结果。坦率地说,我认为,如果我们设想有一些图像下载方法,比如下载特定图像的方法,那么跟踪所发生的事情就更容易了:

代码语言:javascript
复制
func download(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) { ... }

然后,例程(a)下载图像,每次不超过4个;(b)按顺序返回结果,如下所示:

代码语言:javascript
复制
func downloadAllImages(_ urls: [URL], completion: @escaping ([UIImage]) -> Void) {
    DispatchQueue.global(qos: .utility).async {
        let group = DispatchGroup()
        let semaphore = DispatchSemaphore(value: 4)
        var imageDictionary: [URL: UIImage] = [:]

        // download the images

        for url in urls {
            group.enter()
            semaphore.wait()

            self.download(url) { result in
                defer {
                    semaphore.signal()
                    group.leave()
                }

                switch result {
                case .failure(let error):
                    print(error)

                case .success(let image):
                    DispatchQueue.main.async {
                        imageDictionary[url] = image
                    }
                }
            }
        }

        // now sort the results

        group.notify(queue: .main) {
            completion(urls.compactMap { imageDictionary[$0] })
        }
    }
}

你会这样称呼它:

代码语言:javascript
复制
downloadAllImages(urls) { images in
    self.images = images
    self.updateUI()        // do whatever you want to trigger the update of the UI
}

FWIW,“下载单个图像”例程可能如下所示:

代码语言:javascript
复制
enum DownloadError: Error {
    case notImage
    case invalidStatusCode(URLResponse)
}

func download(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
            completion(.failure(error!))
            return
        }

        guard 200..<300 ~= response.statusCode else {
            completion(.failure(DownloadError.invalidStatusCode(response)))
            return
        }

        guard let image = UIImage(data: data) else {
            completion(.failure(DownloadError.notImage))
            return
        }

        completion(.success(image))
    }
}

这是使用Swift 5 Result枚举。如果您使用的是Swift的早期版本,您可以自己定义一种简单的表示方式:

代码语言:javascript
复制
enum Result<Success, Failure> {
    case success(Success)
    case failure(Failure)
}

最后,值得注意的还有其他几种选择:

  1. 将网络请求封装在异步Operation子类中,并将它们添加到maxConcurrentOperationCount设置为4的操作队列中。如果您对这种方法感兴趣,我可以提供一些引用。
  2. 使用像翠鸟这样的图像下载库。
  3. 不要手动下载所有图像,而是使用UIImageView扩展(如翠鸟提供的),完全放弃“下载所有图像”过程,转而使用一种模式,您只需指示图像视图以及时的方式(或预取)异步检索图像。
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57938164

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档