Fin de tâche.

iLandesiLandes Membre
novembre 2017 modifié dans Dev. iOS, watchOS, tvOS #1

Bonjour,


 


Je télécharge des images depuis un serveur. Voici un extrait de mon code



func loadImages() {
for urlImage in urlImageList() {
let serverUrl = k.imagesServerUrl.appendingPathComponent(urlImage)
let destinationUrl = k.imagesLocalUrl.appendingPathComponent(urlImage)
downloadFromServer(serverUrl: serverUrl, localDestination: destinationUrl)
}
}

// MARK: - Sync Helper Func
func downloadFromServer(serverUrl: URL, localDestination: URL) {
// Create destination URL
let destinationFileUrl = localDestination

let session = URLSession(configuration: .default)
let request = URLRequest(url:serverUrl)

let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
// Code .... }
task.resume()

}

Je l'ai un peu épuré pour en garder l'essentiel. Ma boucle lance ma fonction downloadFromServer pour toutes les images contenu dans ma liste d'url de mes images. Cela fonctionne. Mais voilà  j'aimerai savoir lorsque toutes mes images ont été téléchargées. En gros quand il n'y a plus de task en cours.


 


Si vous avez une idée...


 


D'avance merci

Réponses

  • LarmeLarme Membre
    novembre 2017 modifié #2

    Tu peux utiliser un dispatch group avec un create(), avec un enter() à  chaque itération de la for loop, leave() à  la find de chaque téléchargement, et enfin un notify() pour indiquer que faire lorsqu'elles ont toutes été téléchargées.


    Il va falloir implémenter des closures par contre dans downloadFromServer()


     


     


    En pseudo code (non-testé, ni au compilo, ni au run-time, tappé ici-même, et vu que je ne suis pas dév' Swift) par rapport à  ton code:


    J'ai rajouté un closure à  ta méthode afin de pouvoir quitter le dispatch_group_t.

    Potentiellement rajouter une closure également à  loadImages() afin de dire à  celui qui l'appelle de faire quelque chose...



    ​func loadImages() {
           let myDownloadGroup = dispatch_group_create()
            for urlImage in urlImageList() {
                myDownloadGroup.enter()
                let serverUrl = k.imagesServerUrl.appendingPathComponent(urlImage)
                let destinationUrl = k.imagesLocalUrl.appendingPathComponent(urlImage)
                downloadFromServer(serverUrl: serverUrl, localDestination: destinationUrl {
                  (successBoolean) in
                     myDownloadGroup.leave()
                })
            }
           dispatch_group_notify(myDownloadGroup, dispatch_get_main_queue()) {
            //All downloads are done
           }
        }
      
        // MARK: - Sync Helper Func
        func downloadFromServer(serverUrl: URL, localDestination: URL completionHandler: @escaping (Bool?) -> Void)) {
            // Create destination URL
            let destinationFileUrl = localDestination
          
            let session = URLSession(configuration: .default)
            let request = URLRequest(url:serverUrl)
          
            let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
                // Code ....
                completionHandler(trueOrFalseAccordingToSucessDownload)
            }
            task.resume()
          
        }

  • Merci larme, je regarde ça et j'envois le code testé. 


  • Code testé, Swift 4.


     


    Encore merci @Larme



    func syncImagesFromServer() {

    let myDownloadGroup = DispatchGroup()
    for urlImage in urlImageList() {
    myDownloadGroup.enter()
    let serverUrl = k.imagesServerUrl.appendingPathComponent(urlImage)
    let destinationUrl = k.imagesLocalUrl.appendingPathComponent(urlImage)
    downloadFromServer( serverUrl: serverUrl,
    localDestination: destinationUrl,
    completionHandler: { (successBoolean) in
    myDownloadGroup.leave()
    })
    }

    myDownloadGroup.notify(queue: DispatchQueue.main) {
    //All downloads are
    print("TRAITEMENT TERMINE")
    }

    }

    func downloadFromServer( serverUrl: URL,
    localDestination: URL,
    completionHandler: @escaping (Bool?) -> Void) {

    // Create destination URL
    let destinationFileUrl = localDestination

    let session = URLSession(configuration: .default)
    let request = URLRequest(url:serverUrl)

    let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
    // Code ....
    completionHandler(trueOrFalseAccordingToSucessDownload)
    }

    task.resume()
    }
  • Je relance ce sujet car j'ai des problèmes de downloads de mes images. Pas mal d'images qui ne se téléchargent pas à  cause de problèmes de TimeOut : NSURLErrorDomain Code -1001 "The request timed out"


     


     


    J'ai essayé avec Alamofire configuré comme cela



    // AppDelegate
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    // Alamofire Configuration
    let configuration = URLSessionConfiguration.default
    configuration.timeoutIntervalForRequest = 120 // seconds
    configuration.timeoutIntervalForResource = 120 //seconds
    AFManager = Alamofire.SessionManager(configuration: configuration)

    return true
    }


    // Model class
    AFManager.download(k.catalogServerUrl, to: destination).response { response in
    print(response)
    // ....
    }

    Au départ j'avais écris cela moi même



    func downloadFromServer(serverUrl: URL,
    localDestination: URL,
    completionHandler: @escaping (Bool?) -> Void) {

    // Create destination URL
    let destinationFileUrl = localDestination

    let sessionConfig = URLSessionConfiguration.default
    sessionConfig.timeoutIntervalForRequest = k.timeoutIntervalForRequest
    sessionConfig.timeoutIntervalForResource = k.timeoutIntervalForResource
    // sessionConfig.httpShouldUsePipelining = true
    let session = URLSession(configuration: sessionConfig)

    let request = URLRequest(url:serverUrl)

    let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
    if let tempLocalUrl = tempLocalUrl, error == nil {
    // Success
    if let statusCode = (response as? HTTPURLResponse)?.statusCode {
    print("Successfully downloaded \(destinationFileUrl.lastPathComponent). Status code: \(statusCode)")
    self.sendLog("Download : \(destinationFileUrl.lastPathComponent)", isError: false)
    }

    do {

    // Remove existing file

    if FileManager.default.fileExists(atPath: destinationFileUrl.path){
    do{
    try FileManager.default.removeItem(atPath: destinationFileUrl.path)
    } catch let error {
    self.sendLog("error occurred, here are the details:\n \(error)", isError: true)
    }
    print ("File deleted")
    }

    try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
    completionHandler(true)

    } catch (let writeError) {
    self.sendLog("Error creating a file \(destinationFileUrl.lastPathComponent) : \(writeError)", isError: true)
    completionHandler(false)
    }

    } else {
    print ("Error: \(destinationFileUrl.lastPathComponent). Error description: \(error?.localizedDescription as Any)")
    self.sendLog("Error: \(destinationFileUrl.lastPathComponent)", isError: true)
    completionHandler(false)
    }
    }
    task.resume()

    }

    Les deux codes produisent le même résultat quand j'ai un accès de mauvaise qualité : pas mal de Timed Out. Quand la liaison est bonne tout va bien...


     


    D'avance merci pour votre aide


     


    PS1:  Peut-être que ce message aurait mérité un nouveau Topic mais en le postant à  la suite, cela permet aussi de grouper la problématique...


     


    PS2: Tant qu'à  faire je préfèrerais trouver une solution à  partir de mon code plutôt qu'à  partir d'Alamofire. Je préfère toujours mon propre code plutôt que de dépendre d'une bibliothèque issue d'un tiers.


  • Au pire en cas de timeout tu peux relancer simplement la requête non ? avec peut être un temps d'attente et/ou une vérification de la connectivité (avec Reacheability ou en essayant de téléchargement une ressource pour laquelle tu est sur qu'elle sera présente sans limite de temps) avant de relancer la requête et au bout de 2 ou 3 tentatives appeler le completionhandler avec false. Et si tu as trop d'échec attendre que la connectivité soit rétablie (en vérifiant régulièrement) puis relancer les téléchargements (ou pas).


    Tu peux conserver les infos de téléchargement dans une structure (url, nombre d'échec, etc...) (ou une classe) pour te permettre de recréer les tâches de téléchargement.


    J'éclaterai pour cela la méthode downloadFromServer en deux 


    1/ downloadFromServer "actuel" (appel ce createDownloadTask et tasl.resume() si la connectivité est présente)


    2/ createDownloadTask qui va créer la tâche de développement avec un completionhandler "interne" qui va réagir en fonction du résultat (soit appel du vrai completionhandler, soit vérification de la connectivité, incrément du nombre d'échec, vérification du nombre d'échec total, etc...). Si le nombre d'échec est trop important, il faut stocker les infos de download demandés et les relancer lorsque la connectivité est rétablie. Il serait aussi possible de définit un temps d'attente de connectivité max au de la duquel il est certain que la connectivité ne sera jamais établie et appeler les completionhandler(false) de toute les downloads en attente...


Connectez-vous ou Inscrivez-vous pour répondre.