2017-07-14 125 views
1

我有9个图像的数组,我想将它们全部保存到用户的相机卷。你可以用UIImageWriteToSavedPhotosAlbum来做到这一点。我写了一个循环来保存每个图像。这个问题是由于某种原因,它会only save the first five。现在,顺序很重要,所以如果图像无法保存,我想重试并等待,直到成功,而不是有一些不可预测的比赛。等待完成处理程序完成,然后继续

所以,我实现一个完成处理程序,并认为我可以用旗语像这样:

func save(){ 
    for i in (0...(self.imagesArray.count-1)).reversed(){ 
     print("saving image at index ", i) 
     semaphore.wait() 

     let image = imagesArray[i] 
     self.saveImage(image) 

    } 
} 

func saveImage(_ image: UIImage){ 
    UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil) 
} 

func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) { 
    //due to some write limit, only 5 images get written at once. 
    if let error = error { 
     print("trying again") 
     self.saveImage(image) 
    } else { 
     print("successfully saved") 
     semaphore.signal() 
    } 
} 

我的代码的问题是它获得的第拯救后受阻并semaphore.signal不会被调用。我想我的完成处理程序应该在主线程上调用,但已被semaphore.wait()阻止。任何帮助赞赏。由于

+0

你应该尝试把你的代码放在Dispatach.global队列中,它肯定会有帮助 –

+0

@MikeAlter它确实有帮助。它现在有效,但我不知道为什么,你能帮忙解释一下吗?谢谢 – user339946

回答

0

年前,我面临同样的问题

你应该尽量把你的代码中Dispatach.global队列它一定会帮助

原因:我真的不知道原因也一样,我觉得这是什么信号它可能是,可能需要在后台执行线程来同步等待和信号

0

正如迈克改变提到的,使用Dispatch.global().async帮助解决了这个问题。现在,该代码如下所示:

func save(){ 
    for i in (0...(self.imagesArray.count-1)).reversed(){ 
     DispatchQueue.global().async { [unowned self] in 
      self.semaphore.wait() 

      let image = self.imagesArray[i] 
      self.saveImage(image) 
     } 
    } 
} 

我怀疑的问题是,完成处理程序被在主线程,这是已经由最初semaphore.wait()称为锁定执行。所以当完成发生时,semaphore.signal()永远不会被调用。

这是通过将任务运行为异步队列解决的。

1

正如其他人指出的,你想避免等待主线程,冒着死锁的风险。因此,尽管您可以将其推送到全局队列,但另一种方法是采用许多机制之一来执行一系列异步任务。选项包括异步Operation子类或承诺(例如PromiseKit)。

例如,包裹图像中的异步Operation节能任务,并将它们添加到OperationQueue您可以定义您的影像保存操作,像这样:

class ImageSaveOperation: AsynchronousOperation { 

    let image: UIImage 
    let imageCompletionBlock: ((NSError?) -> Void)? 

    init(image: UIImage, imageCompletionBlock: ((NSError?) -> Void)? = nil) { 
     self.image = image 
     self.imageCompletionBlock = imageCompletionBlock 

     super.init() 
    } 

    override func main() { 
     UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil) 
    } 

    func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) { 
     imageCompletionBlock?(error) 
     complete() 
    } 

} 

然后,假设你有一个数组,images ,即这是一个[UIImage],你可以再做:

let queue = OperationQueue() 
queue.name = Bundle.main.bundleIdentifier! + ".imagesave" 
queue.maxConcurrentOperationCount = 1 

let operations = images.map { 
    return ImageSaveOperation(image: $0) { error in 
     if let error = error { 
      print(error.localizedDescription) 
      queue.cancelAllOperations() 
     } 
    } 
} 

let completion = BlockOperation { 
    print("all done") 
} 
operations.forEach { completion.addDependency($0) } 

queue.addOperations(operations, waitUntilFinished: false) 
OperationQueue.main.addOperation(completion) 

可以很明显的自定义它在错误的重试添加逻辑,但是很可能现在还没有必要,因为“去根o忙“问题是由于太多的并发保存请求而导致的,我们已经取消了这些。这只留下不可能通过重试解决的错误,所以我可能不会添加重试逻辑。 (错误更可能是权限失败,空间不足等)但是如果您真的想要,可以添加重试逻辑。更有可能的是,如果你有错误,你可能只想取消队列上的所有剩余操作,就像我上面所说的那样。

请注意,上面的小类AsynchronousOperation,这只是Operation子类,isAsynchronous返回true。例如:

/// Asynchronous Operation base class 
/// 
/// This class performs all of the necessary KVN of `isFinished` and 
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer 
/// a concurrent NSOperation subclass, you instead subclass this class which: 
/// 
/// - must override `main()` with the tasks that initiate the asynchronous task; 
/// 
/// - must call `completeOperation()` function when the asynchronous task is done; 
/// 
/// - optionally, periodically check `self.cancelled` status, performing any clean-up 
/// necessary and then ensuring that `completeOperation()` is called; or 
/// override `cancel` method, calling `super.cancel()` and then cleaning-up 
/// and ensuring `completeOperation()` is called. 

public class AsynchronousOperation : Operation { 

    private let syncQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".opsync") 

    override public var isAsynchronous: Bool { return true } 

    private var _executing: Bool = false 
    override private(set) public var isExecuting: Bool { 
     get { 
      return syncQueue.sync { _executing } 
     } 
     set { 
      willChangeValue(forKey: "isExecuting") 
      syncQueue.sync { _executing = newValue } 
      didChangeValue(forKey: "isExecuting") 
     } 
    } 

    private var _finished: Bool = false 
    override private(set) public var isFinished: Bool { 
     get { 
      return syncQueue.sync { _finished } 
     } 
     set { 
      willChangeValue(forKey: "isFinished") 
      syncQueue.sync { _finished = newValue } 
      didChangeValue(forKey: "isFinished") 
     } 
    } 

    /// Complete the operation 
    /// 
    /// This will result in the appropriate KVN of isFinished and isExecuting 

    public func complete() { 
     if isExecuting { isExecuting = false } 

     if !isFinished { isFinished = true } 
    } 

    override public func start() { 
     if isCancelled { 
      isFinished = true 
      return 
     } 

     isExecuting = true 

     main() 
    } 
} 

现在,我很欣赏的操作队列(或承诺)是要看起来像矫枉过正您的具体情况,但它是你可以使用任何你有一系列的异步任务的有用的模式。有关操作队列的更多信息,请随时参阅Concurrency Programming Guide: Operation Queues