La classe URLSession permet à une application Swift de faire appel à un service Web, aussi appelé API (Application Programming Interface).
Dans cet article, je vais vous montrer comment appeler un service Web qui permet de synchroniser des données locales avec une base de données distante.
Dans cette fiche :
Le service Web de mon exemple attend en entrée une représentation JSON des données à synchroniser pour une table d'items gérée par SwiftData.
Pour encoder ces données, il faut que la classe Item réponde au protocole Encodable.
@Model
final class Item: Encodable {
...
}
Il serait possible d'ajouter d'autres informations en entrée, par exemple un code d'usager et un mot de passe qui donnent accès au service Web. Il faudrait à ce moment créer une structure qui permet de stocker un tableau d'items, un code d'usager et un mot de passe tout en répondant au protocole Encodable.
struct DonneesFournies: Encodable {
var items: [Item]
var codeUsager: String
var motDePasse: String
}
Lorsqu'on appelle un service Web, il est important de connaître le format des données retournées par ce service.
Ces données seront récupérées dans une structure qui répond au protocole Decodable.
struct ReponseDuService: Decodable {
...
}
Notez que si certaines propriétés de la structures peuvent ne pas être retournées par le service Web, il faut les rendre optionnelles.
Sans cette précaution, le décodage ne réussira pas lorsque le service Web n'initialise pas toutes les propriétés.
struct ReponseDuService: Decodable {
var erreurs: [Erreur]? = []
...
}
Le service Web peut être appelé à différents endroits ou à différents moments dans le code, par exemple :
Voici un exemple complet d'un appel à un service Web pour synchroniser des données locales avec une base de données distante.
Dans cet exemple, l'application appelle un service Web sur le serveur de développement local qui débute par http://127.0.0.1. L'application doit donc être configurée pour pouvoir utiliser un URL non sécurisé. Une fois le service en ligne, il faudra changer l'URL du service.
Il faut savoir que l'appel d'un service Web est réalisé de façon asynchrone. Ceci ajoute un peu de complexité au code mais une fois qu'on a un bon algorithme, ça ne pose pas de problème.
Notez que depuis iOS 15, l'utilisation de URLSsession peut également être réalisée avec les mots-clés async et await.
Je vous fais ici la démonstration de la technique traditionnelle.
/**
Appelle le service Web qui synchronise les données dans le nuage.
*/
func appelerService(items: [Item]) {
// Tente d'encoder les items en JSON
guard let itemsJSON = try? JSONEncoder().encode(items) else {
print("Impossible d'encoder les items en JSON")
return
}
// URL du service Web
guard let url = URL(string: "http://127.0.0.1/monapi/synchro-items.php") else {
print("URL invalide")
return
}
// Initialisation de la requête. On y précise l'URL, le Content-Type, la méthode HTTP et les données en entrée
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = itemsJSON
// Appel asynchrone
// L'appel du service Web retournera des données (data) s'il réussi.
// Sinon, il retournera une erreur (error), jamais les deux.
let appel = URLSession.shared.dataTask(with: request) { data, response, error in
// Au besoin, response permet de retrouver le code d'état HTTP retourné par le service Web ainsi que le type mime
// if let httpResponse = response as? HTTPURLResponse {
// let etatHTTP = httpResponse.statusCode
// print("Code d'état HTTP : \(etatHTTP)")
// print(httpResponse.mimeType ?? "Type mime inconnu")
// }
// Si data existe, c'est que l'appel a réussi.
if let data = data {
// Décode la réponse
if let reponseDecodee = try? JSONDecoder().decode(ReponseDuService.self, from: data) {
// print(reponseDecodee)
// Retourne au fil d'exécution principal
DispatchQueue.main.async {
// Si désiré, on peut initialiser ici une variable d'état afin que le résultat de l'opération soit visible à l'écran.
self.reponseDuService = reponseDecodee
...
}
// Fin du traitement
return
}
else {
print("Impossible de décoder la réponse")
print("Chaîne JSON reçue: \(String(describing: String(data: data, encoding: .utf8)))")
... // mécanisme pour afficher un message à l'usager
return
}
}
print("L'appel de l'API a échoué: \(error?.localizedDescription ?? "Erreur inconnue")")
... // mécanisme pour afficher un message à l'usager
}
// La session est initialisée en mode suspendu. Il faut appeler resume() pour que l'appel ait lieu.
appel.resume()
}
Si l'application n'arrive pas à décoder la réponse, je vous partage une petite astuce pour vous aider à cibler ce qui ne fonctionne pas.
▼Publicité