使用dispatch_async在后台加载图像

时间:2016-06-17 15:47:43

标签: ios swift sprite-kit

我正在尝试更新进度条,因为我的图片已加载。我在这里阅读了几个答案,并尝试以不同的方式格式化我的代码。我正在尝试阅读图像并更新进度条。然后,将加载所有图像调用代码来处理它们。我得到的最好结果是一些大部分时间都能运行的代码。但是,如果我正在处理大量图像的情况,我会遇到奇怪的错误。我认为它会继续并在所有图像完全加载之前运行继续代码。当我删除dispatch_async时,代码工作正常,但进度条不会更新。

func imageLocXML(il:String) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
        let url:NSURL? = NSURL(string: il)
        let data:NSData? = NSData(contentsOfURL: url!)
        let image = UIImage(data: data!)
        pieceImages.append(image!)
        self.numImagesLoaded += 1
        self.updateProgressBar()
        if self.numImagesLoaded == self.numImagesToLoad {
            self.continueLoad()
        }
    }
}

2 个答案:

答案 0 :(得分:4)

有很多问题:

  1. 此代码不是线程安全的,因为您在numImagesLoaded上有竞争条件。从理论上讲,这可能导致continueLoad不止一次被调用。您可以通过将{(1}}同步到这个(和其他模型对象)的更新回到主队列来实现线程安全。

  2. 与DashAndRest一样,您必须将UI更新分派给主队列。

  3. 当您使此异步时,您在发起大量请求时引入了网络超时风险。您可以通过重构代码来使用操作队列而不是调度队列来解决此问题,并指定numImagesLoaded

  4. 图像正在添加到数组中:

    • 由于这些任务是异步运行的,因此无法保证按任何特定顺序完成,因此阵列不会按顺序排列。您应该将图像保存在字典中,在这种情况下,订单不再重要。

    • 就像maxConcurrentOperationCount一样,numImagesLoaded不是线程安全的。

  5. 您正在使用大量强制解包,因此如果任何请求失败,则会崩溃。

  6. 但要解决这个问题,我们必须退一步看看调用此方法的例程。让我们想象你有类似的东西:

    pieceImages

    我建议您用以下内容替换它:

    var pieceImages = [UIImage()]
    
    func loadAllImages() {
        for imageUrl in imageURLs {
            imageLocXML(imageUrl)
        }
    }
    
    func imageLocXML(il:String) {
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
            let url:NSURL? = NSURL(string: il)
            let data:NSData? = NSData(contentsOfURL: url!)
            let image = UIImage(data: data!)
            self.pieceImages.append(image!)
            self.numImagesLoaded += 1
            self.updateProgressBar()
            if self.numImagesLoaded == self.numImagesToLoad {
                self.continueLoad()
            }
        }
    }
    

    话虽如此,我认为这里存在更深层次的问题:

    • 您是否应该像这样提前加载图像?我们通常建议延迟加载图像,只在需要时加载它们。

    • 如果您要将图像加载到这样的结构中,您应该优雅地处理内存压力,在低内存警告时清除它。然后,您需要优雅地处理在检索图像时要执行的操作,并且由于内存压力而将其清除(导致您回到即时延迟加载模式)。

      < / LI>
    • 我们通常建议不要使用同步网络请求(var pieceImages = [String: UIImage]() func loadAllImages() { let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 4 let completionOperation = NSBlockOperation { self.continueLoad() } for imageURL in imageURLs { let operation = NSBlockOperation() { if let url = NSURL(string: imageURL), let data = NSData(contentsOfURL: url), let image = UIImage(data: data) { NSOperationQueue.mainQueue().addOperationWithBlock { self.numImagesLoaded += 1 self.pieceImages[imageURL] = image self.updateProgressBar() } } } queue.addOperation(operation) completionOperation.addDependency(operation) } NSOperationQueue.mainQueue().addOperation(completionOperation) } )。我们通常使用NSData(contentsOfURL:_)这是可取消的,提供更丰富的错误处理等等。不可否认,这进一步使上述代码复杂化(可能导致我走上异步NSURLSession子类的道路),但你应该至少了解NSOperation的限制。

答案 1 :(得分:0)

尝试DISPATCH_QUEUE_PRIORITY_DEFAULT将此作为后台队列:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    let url:NSURL? = NSURL(string: il)
    let data:NSData? = NSData(contentsOfURL: url!)
    let image = UIImage(data: data!)
    self.pieceImages.append(image!)
    self.numImagesLoaded += 1
    dispatch_async(dispatch_get_main_queue(), {
        //UI must be updated on main thread queue
        self.updateProgressBar()
    })
    if self.numImagesLoaded == self.numImagesToLoad {
        self.continueLoad()
    }
}

必须在MAIN线程队列上更新UI!

您还必须使用self来访问pieceImages