2017-07-14 4 views
1

J'ai un tableau de 9 images et je voudrais les sauvegarder toutes dans la pellicule de l'utilisateur. Vous pouvez le faire avec UIImageWriteToSavedPhotosAlbum. J'ai écrit une boucle pour enregistrer chaque image. Le problème avec ceci est que pour une raison quelconque, il va only save the first five. Maintenant, l'ordre est important, donc si une image ne parvient pas à sauvegarder, je veux réessayer et attendre qu'elle réussisse, plutôt que d'avoir une course imprévisible.En attente de la fin du gestionnaire de complétion, avant de continuer

Donc, je mets en œuvre un gestionnaire d'achèvement, et je pensais que je pouvais utiliser sémaphores comme ceci:

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() 
    } 
} 

Le problème avec mon code est il est bloqué après la première sauvegarde et semaphore.signal ne sera jamais appelé. Je pense que mon gestionnaire d'achèvement est supposé être appelé sur le thread principal, mais il est déjà bloqué par le semaphore.wait(). Toute aide appréciée. Merci

+0

Vous devriez essayer de mettre votre code dans la file d'attente Dispatach.global cela aidera sûrement –

+0

@MikeAlter Cela a aidé. Cela fonctionne maintenant mais je ne sais pas pourquoi, pouvez-vous m'aider à expliquer? Merci – user339946

Répondre

0

année il y a je faisais face Même problème

Vous devriez essayer de mettre votre code dans la file d'attente Dispatach.global il va sûrement aider

Raison: Je ne sais vraiment pas raison aussi, je pense que ce que sémaphores Il est, Peut-être qu'il est nécessaire d'exécuter en arrière-plan Thread pour synchroniser l'attente et le signal

0

Comme Mike modif mentionné, en utilisant un Dispatch.global().async aidé à résoudre le problème. Le code ressemble maintenant à ceci:

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) 
     } 
    } 
} 

Je soupçonne que le problème était que le gestionnaire d'achèvement est exécuté dans le thread principal, qui était déjà verrouillé par le semaphore.wait() initialement appelé. Donc, lorsque l'achèvement se produit, semaphore.signal() ne reçoit jamais appelé.

Ceci est résolu en exécutant les tâches une file d'attente asynchrone.

1

Comme d'autres l'ont souligné, vous voulez éviter d'attendre sur le thread principal, en risquant le blocage. Ainsi, alors que vous pouvez le repousser dans une file d'attente globale, l'autre approche consiste à utiliser l'un des nombreux mécanismes permettant d'effectuer une série de tâches asynchrones. Les options incluent une sous-classe asynchrone Operation ou des promesses (par exemple, PromiseKit).

Par exemple, pour envelopper la tâche d'enregistrement des images dans une asynchrone Operation et les ajouter à un OperationQueue vous pouvez définir votre image opération de sauvegarde comme ceci:

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() 
    } 

} 

Ensuite, en supposant que vous aviez un tableau, images , ce qui était un [UIImage], vous pouvez alors faire:

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) 

vous pouvez évidemment personnaliser cette option pour ajouter une logique de nouvelle tentative en cas d'erreur, mais qui est probablement pas nécessaire maintenant parce que la racine du « à o occupé "problème était le résultat de trop de demandes d'enregistrement simultanées, que nous avons éliminé. Cela ne laisse que des erreurs qui sont peu susceptibles d'être résolues en réessayant, donc je n'ajouterais probablement pas de logique de réessai. (Les erreurs sont plus susceptibles d'être des échecs d'autorisations, de l'espace, etc.) Mais vous pouvez ajouter une nouvelle tentative si vous le voulez vraiment. Plus probablement, si vous avez une erreur, vous pourriez vouloir annuler toutes les opérations restantes dans la file d'attente, comme je l'ai ci-dessus.

Note, les sous-classes ci-dessus AsynchronousOperation, qui est juste une sous-classe Operation pour laquelle isAsynchronous retours true.Par exemple:

/// 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() 
    } 
} 

Maintenant, je comprends les files d'attente d'opération (ou promesses) va sembler exagéré pour votre situation, mais il est un modèle utile que vous pouvez utiliser chaque fois que vous avez une série de tâches asynchrones. Pour plus d'informations sur les files d'attente d'opération, n'hésitez pas à vous reporter au Concurrency Programming Guide: Operation Queues.