2016-11-17 2 views
1

J'ai implémenté (presque) avec succès URLSessionDelegate, URLSessionTaskDelegate et URLSessionDataDelegate afin que je puisse télécharger mes objets en arrière-plan. Mais je ne suis pas sûr de savoir comment implémenter des gestionnaires d'achèvement, afin que je puisse supprimer l'objet que j'ai envoyé, lorsque le serveur retourne statuscode=200Implémentation des gestionnaires de complétion pour backgroundSession.uploadTask

Je commence actuellement le uploadTask comme celui-ci

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.myObject\(myObject.id)") 
let backgroundSession = URLSession(configuration: configuration, 
            delegate: CustomDelegate.sharedInstance, 
            delegateQueue: nil) 
let url: NSURL = NSURL(string: "https://www.myurl.com")! 
let urlRequest = NSMutableURLRequest(url: url as URL) 

urlRequest.httpMethod = "POST" 
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") 

let uploadTask = backgroundSession.uploadTask(with: urlRequest as URLRequest, fromFile: path) 

uploadTask.resume() 

J'ai essayé d'ajouter une fermeture à l'initialisation de uploadTask mais xcode a affiché une erreur que ce n'était pas possible.

J'ai mon CustomDelegate de classe personnalisée:

class CustomDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate { 

static var sharedInstance = CustomDelegate() 

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 
    print("\(session.configuration.identifier!) received data: \(data)") 
    do { 
     let parsedData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any] 
     let status = parsedData["status"] as! NSDictionary 
     let statusCode = status["httpCode"] as! Int 

     switch statusCode { 
     case 200: 
      // Do something 
     case 400: 
      // Do something 
     case 401: 
      // Do something 
     case 403: 
      // Do something 
     default: 
      // Do something 
     } 
    } 
    catch { 
     print("Error parsing response") 
    } 
} 
} 

Il met également en œuvre les autres fonctions pour les délégués.

Ce que je veux est de savoir en quelque sorte que le téléchargement est fait pour que je puisse mettre à jour l'interface utilisateur et la base de données que je pense est difficile (peut-être impossible?) CustomDelegate.

+0

Mettre en œuvre [ 'didCompleteWithError'] (https://developer.apple.com/reference/foundation/urlsessiontaskdelegate/1411610-urlsession). Et si vous voulez que les mises à jour progressent au fur et à mesure, vous pouvez utiliser ['didSendBodyData'] (https://developer.apple.com/reference/foundation/urlsessiontaskdelegate/1408299-urlsession). – Rob

+0

Non reliée, mais plutôt que d'instancier 'NSURL' et' NSMutableURLRequest' et de convertir 'URL' et' URLRequest', vous devez simplement créer 'URL' et' URLRequest' en premier lieu. Le seul truc est que puisque vous voulez muter le 'URLRequest', utilisez' var', pas 'let'. – Rob

+0

@Rob Je l'ai fait mais mon problème est comment "sortir de" la classe "CustomDelegate". Si je suis par exemple dans 'mainViewController' et que j'appelle une fonction qui fait la configuration de' uploadTask' alors je veux mettre à jour l'interface utilisateur par exemple – Frederik

Répondre

2

Si vous êtes uniquement intéressé à détecter l'achèvement de la demande, l'approche la plus simple est d'utiliser une fermeture:

class CustomDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate { 

    static var sharedInstance = CustomDelegate() 
    var uploadDidFinish: ((URLSessionTask, Error?) -> Void)? 

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 
     DispatchQueue.main.async { 
      uploadDidFinish?(task, error) 
     } 
    } 

} 

Ensuite, votre contrôleur de vue fixerait cette fermeture avant de lancer la demande, par exemple

CustomDelegate.sharedInstance.uploadDidFinish = { [weak self] task, error in 
    // update the UI for the completion here 
} 

// start the request here 

Si vous souhaitez mettre à jour votre interface utilisateur pour de multiples situations (par exemple, non seulement comme ajouts fini, mais les progrès que les téléchargements sont envoyés), vous pourriez théoriquement définir plusieurs fermetures (un pour l'achèvement, un pour progrès), mais vous adopteriez souvent votre propre modèle de protocole délégué. (Personnellement, je renomme CustomDelegate à quelque chose comme UploadManager pour éviter toute confusion au sujet de qui est un délégué à quoi, mais c'est à vous.)

Par exemple, vous pourriez faire:

protocol UploadDelegate: class { 
    func didComplete(session: URLSession, task: URLSessionTask, error: Error?) 
    func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) 
} 

Ensuite, dans votre gestionnaire de requêtes réseau (votre CustomDelegate de mise en œuvre), définir une propriété delegate:

weak var delegate: UploadDelegate? 

Dans les méthodes déléguées URLSession appropriées, vous appelez votre délégué sur mesure meth ods de transmettre les informations au contrôleur de vue:

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 
    // do whatever you want here 

    DispatchQueue.main.async { 
     delegate?.didComplete(session: session, task: task, didCompleteWithError: error) 
    } 
} 

func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { 
    // do whatever you want here 

    DispatchQueue.main.async { 
     delegate?.didSendBodyData(session: session, task: task, bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) 
    } 
} 

Ensuite, vous souhaitez déclarer votre contrôleur de vue pour se conformer à votre nouveau protocole et mettre en œuvre ces méthodes:

class ViewController: UIViewController, UploadDelegate { 
    ... 
    func startRequests() { 
     CustomDelegate.sharedInstance.delegate = self 

     // initiate request(s) 
    } 

    func didComplete(session: URLSession, task: URLSessionTask, error: Error?) { 
     // update UI here 
    } 

    func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { 
     // update UI here 
    } 
} 

Maintenant, vous pouvez mettre à jour ce protocole UploadDelegate pour capturer des informations de modèle et passer cela en tant que paramètre à vos méthodes, aussi, mais j'espère que cela illustre l'idée de base.


Quelques observations mineures:

  1. Lors de la création de votre session, vous devriez probablement exciser les NSURL et NSMutableURLRequest types de votre code, par exemple:

    let url = URL(string: "https://www.myurl.com")! 
    var urlRequest = URLRequest(url: url) 
    
    urlRequest.httpMethod = "POST" 
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") 
    
    let uploadTask = backgroundSession.uploadTask(with: urlRequest, fromFile: path) 
    
    uploadTask.resume() 
    
  2. Vous recherchez statusCode dans didReceiveData. Vous devriez vraiment le faire en didReceiveResponse. En outre, vous obtenez généralement le code d'état à partir du . Vous analysez la réponse dans didReceiveData. Généralement, vous devriez le faire dans didCompleteWithError (juste au cas où il faut plusieurs appels à didReceiveData pour recevoir la réponse entière).

  3. Je ne sais pas ce que myObject.id est, mais l'identifiant que vous avez choisi, "com.example.myObject\(myObject.id)", est un peu suspect:

    • Êtes-vous créez une nouvelle instance URLSession pour chaque objet? Vous en voulez probablement un pour toutes les demandes. Lorsque votre application est suspendue/abandonnée pendant le téléchargement en arrière-plan, lorsque l'application est redémarrée, disposez-vous d'un moyen fiable de réactiver les mêmes objets de session?
       

    En général, vous voudriez une seule session de téléchargement pour tous vos téléchargements, et le nom doit être cohérent. Je ne dis pas que ne peut pas le faire comme vous l'avez fait, mais il semble que cela va être problématique de recréer ces sessions sans faire de travail supplémentaire. C'est à vous. Tout cela pour dire que je m'assurerais de tester votre processus de téléchargement en arrière-plan si l'application est terminée et redémarrée en arrière-plan une fois les téléchargements terminés. Cela semble être incomplet/fragile, mais j'espère que je vais juste sauter à des conclusions incorrectes et que vous avez tout cela fonctionne et que vous n'avez simplement pas partagé quelques détails (p. Ex. handleEventsForBackgroundURLSession de votre délégué) pour des raisons de concision. est très apprécié).

+0

merci beaucoup, bonne réponse! Je l'ai fait fonctionner sauf pour obtenir le code d'état. Je pense que j'applique incorrectement 'didReceiveResponse' car il n'est jamais appelé quoi qu'il arrive. Vous avez des idées sur pourquoi? À votre santé. – Frederik

+0

Je vérifie la signature de la méthode et je m'assure que c'est bien: https://developer.apple.com/reference/foundation/urlsessiondatadelegate/1410027-urlsession. Sinon, je suggère de poster une nouvelle question avec un [exemple reproductible du problème] (http://stackoverflow.com/help/mcve). – Rob

+0

@Rob J'essaye d'implémenter un délégué pour une tâche de téléchargement avec une session d'arrière-plan mais mon didFinishDownloadingTo n'est pas déclenché. Si vous avez une chance, je vous serais reconnaissant si vous pouviez jeter un oeil à: http://stackoverflow.com/questions/41488989/using-urlsession-and-background-fetch-together-with-remote-notifications-using-f ? noredirect = 1 # comment70190510_41488989 – user2363025