2017-07-14 6 views
1

私は9枚の画像の配列を持っており、それらをすべてユーザーのカメラロールに保存したいと思います。あなたはUIImageWriteToSavedPhotosAlbumでそれを行うことができます。私は各画像を保存するためのループを書いた。これの問題は何らかの理由で、only save the first fiveになるということです。さて、注文は重要なので、イメージが保存に失敗した場合、私は再試行し、予測できないレースをするのではなく、成功するまで待っています。完了ハンドラが完了するのを待ってから続行します

だから、私は完了ハンドラを実装し、私はそうのようにセマフォを使用できると思っていた:私のコードの問題は、それが保存最初の後にブロックされると呼ばれることは決してありませんsemaphore.signal

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.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

他の人が指摘しているように、デッドロックを招く危険性があるため、メインスレッドで待機することは避けたいと考えています。したがって、グローバルキューにプッシュすることができますが、もう1つの方法は、一連の非同期タスクを実行するための多くのメカニズムの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 busy "の問題は、あまりにも多くの並行保存要求の結果であり、私たちはこれを排除しました。再試行では解決されないエラーが残っているだけなので、リトライロジックは追加しないことにします。 (エラーは、パーミッションの失敗、スペース不足などの可能性が高いですが)本当に必要ならば、再試行ロジックを追加することができます。おそらく、エラーが発生した場合は、上記のように、キュー上の残りの操作をすべてキャンセルするだけです。

上記のサブクラス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を参照してください。

関連する問題