TechBook #3 : Comment consommer un web service dans une application iOS à travers Alamofire
Un petit rappel avant de rentrer dans le vif du sujet de cet article. Nous avons vu dans le précédent article comment créer une API simple, en l’occurence l’inscription (SignUp) et la connexion (SignUp) avec Node.js.
L’objectif du présent article est de consommer cette API de services dans notre application iOS en utilisant Swift 4 à utilisant la bibliothèque Alamofire.
Installation des CocoaPods
CocoaPods est un gestionnaire de dépendances pour les projets Swift et Objective-C Cocoa. Il compte plus de 57 000 bibliothèques et est utilisé dans plus de 3 millions d’applications. CocoaPods peut vous aider à dimensionner vos projets avec élégance.
Pour plus d’information, vous pouvez visiter le site officiel https://cocoapods.org/.
Alors pour utiliser ces bibliothèques vous devez l’installer sur votre Mac via le terminal avec cette commande :
sudo gem install cocoapods
Création du fichier podfile
Le fichier Pod est un fichier spécifique pour les CocoaPods qui va permettre de répertorier toutes les librairies dans votre projet.
Pour le créer rendez-vous à la racine de votre projet dans le terminal et tapez la commande :
Pod init
Et voilà le fichier créé. Maintenant, ouvrez-le à travers un éditeur de texte et ajoutez les pod que vous voulez utiliser. La structure du pod être sur deux formes :
- pod ‘libraryName’, ‘~ library version’
- pod ‘libraryName’,
Dans notre cas, on va installer 3 bibliothèques :
- Alamofire : Pour la consommation des web services
- SDWebImage : Téléchargement rapide d’images à travers les URL
- DropDown : Création de menus (on va l’utiliser après dans l’application)
Le fichier sera comme ceci
Sauvegardez les modifications puis refermez le fichier. Revenez dans le terminal et n’oubliez pas d’être sous la racine de votre projet et installez ces bibliothèques via la commande :
Pod install
Après l’installation, un nouveau fichier sera créé automatiquement nommé «name_project. Xcworkspace » comme l’indique le screenshot suivant :
NB : Après l’installation des pod dans votre projet, il faut ouvrir le fichier «name_project. Xcworkspace » et non pas le fichier «name_project.xcodeproj » pour utiliser les bibliothèques.
Dans notre démo, nous allons consommer le web service d’authentification afin de vous monter comment fonctionne Alamofire.
Exemple de d’exécution du webService
Pour en savoir davantage sur l’API d’authentification vous pouvez revoir l’article précédent de notre série. L’endpoint d’authentification requiert le login et le mot de passe et renvoie en réponse le JSON que vous voyez ci-après.
Notez dans la réponse un token JWT utilisé pour interagir avec l’API.
Supposons qu’on a les deux vues suivantes : Sing In & SingUp. L’utilisateur doit entrer les champs requis pour s’authentifier.
Pour commencer, on doit vérifier la validité de l’adresse email. Nous faisons cela à travers cette fonction qu’on peut déclarer à la fin du SingInViewController :
Fonction de vérification de l’adresse email :
// test validation email
extension String {
func isValidEmail() -> Bool {
let regex = try? NSRegularExpression(pattern: "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$", options: .caseInsensitive)
return regex?.firstMatch(in: self, options: [], range: NSMakeRange(0, self.count)) != nil
}
}
Le modèle User
Le modèle User de notre application Swift contient les infos renvoyées par l’API à la suite d’une authentification réussie comme nous l’avons vu plus haut. Il s’agit en particulier dans la propriété user
de la réponse. Nous transformons cette partie de la réponse en un objet Swift conforme au modèle Swift suivant déclaré dans le fichier «User.swfit» :
import Foundation
class User: NSObject {
var _id : String?
var email : String?
var firstName : String?
var lastName: String?
var age : String?
var gender : String?
var pictureProfile : String?
var createdAt : String?
override init() {}
// Parse Request
init(_ dic : [String : Any])
{
if let _id = dic["_id"] as! String? {
self._id = _id
}
if let _email = dic["email"] as! String? {
self.email = _email
}
if let _firstName = dic["firstName"] as! String? {
self.firstName = _firstName
}
if let _lastName = dic["lastName"] as! String? {
self.lastName = _lastName
}
if let _age = dic["age"] as! String? {
self.age = _age
}
if let _gender = dic["gender"] as! String? {
self.gender = _gender
}
if let _pictureProfile = dic["pictureProfile"] as! String? {
self.pictureProfile = _pictureProfile
}
if let _createdAt = dic["createdAt"] as! String? {
// parse date
let formatterParse = DateFormatter()
formatterParse.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let parsedDate = formatterParse.date(from: _createdAt)
//get date and month
let formatterDate = DateFormatter()
formatterDate.dateStyle = .long
formatterDate.timeStyle = .none
formatterDate.dateFormat = "yyyy-MM-dd"
let newFormatDate = formatterDate.string(from: parsedDate!)
self.createdAt = newFormatDate
}
}
Il est également possible de décoder le token JWT pour en tirer les informations sur l’utilisateur authentifié auprès de l’API.
Remarques
La fonction init(_ dic : [String : Any]) reçoit un dictionnaire de type [String :Any] et parse les données reçues.
Nous utilisons le format « yyyy-MM-dd » pour la date de création, le champ createdAt
Décoder le token JWT :
Vous pouvez consulter le site du JSON Web Token https://jwt.io/ pour décoder votre token (le site contient différentes méthodes de décodage pour les différents langages de programmation).
En Swift 4, vous pouvez utiliser la fonction suivante :
// decode jwt received from ws
func decode(_ token: String) -> [String: AnyObject]? {
let string = token.components(separatedBy: ".")
let toDecode = string[1] as String
var stringtoDecode: String = toDecode.replacingOccurrences(of: "-", with: "+") // 62nd char of encoding
stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/") // 63rd char of encoding
switch (stringtoDecode.utf16.count % 4) {
case 2: stringtoDecode = "\(stringtoDecode)=="
case 3: stringtoDecode = "\(stringtoDecode)="
default: // nothing to do stringtoDecode can stay the same
print("")
}
let dataToDecode = Data(base64Encoded: stringtoDecode, options: [])
let base64DecodedString = NSString(data: dataToDecode!, encoding: String.Encoding.utf8.rawValue)
var values: [String: AnyObject]?
if let string = base64DecodedString {
if let data = string.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true) {
values = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : AnyObject]
}
}
return values
}
Appel du web service d’authentification : login
Il faut importer Alamofire dans SignInViewController via import Alamofire
Lors d’un login réussi :
- Décoder le token
- Parser la réponse dans le model User
- Sauvegarder l’object User ‘parsé’ dans UserDefaults.standard
- Sauvegarder une variable bool userConnected = true dans UserDefaults.standard pour gérer la session après (voir ci-dessus)
- Naviger vers le tabBar
Le code source de l’action du clic sur le buttons login :
@IBAction func btnLoginAction(_ sender: Any) {
let email_tapped = emailTxtField.text
// test if email is valid
if email_tapped!.isValidEmail() {
if(self.passwordTxtField.text == ""){
self.validationFormLabel.text = "password required"
}else{
// execute web service
self.validationFormLabel.text = ""
let postParameters = [
"email": emailTxtField.text!,
"password": passwordTxtField.text!,
] as [String : Any]
Alamofire.request("http://localhost:2500/users/signIn", method: .post, parameters: postParameters as Parameters,encoding: JSONEncoding.default).responseJSON {
response in
switch response.result {
case .success:
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling POST")
print(response.result.error!)
return
}
// make sure we got some JSON since that's what we expect
guard let json = response.result.value as? [String: Any] else {
print("didn't get object as JSON from URL")
if let error = response.result.error {
print("Error: \(error)")
}
return
}
//print("response from server of signInViaEmail : ",json)
let responseServer = json["status"] as? NSNumber
if responseServer == 1{
// user successfuly looged in
if let data = json["data"] as? [String:Any]{
if let token = data["token"] as? String {
// decode the JWT
let userData = self.decode(token)
// save statut of login user in NSUserDefaults
let userConnected = true
let defaults = UserDefaults.standard
defaults.set(userConnected, forKey: "userStatut")
// save object user in NSUserDefaults
defaults.value(forKey: "objectUser")
defaults.set(userData, forKey: "objectUser")
defaults.synchronize()
// navigate to HomePage
self.performSegue(withIdentifier: "ShowHomeViaSignIn", sender: self)
}
}
}else if (responseServer == 0) {
// Authentification failed
let alert = UIAlertController(title: "Opps", message: "Authentification failed", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.destructive, handler: nil))
self.present(alert, animated: true, completion: nil)
}
break
case .failure(let error):
print("error from server : ",error)
break
}
}
}
}else{
self.validationFormLabel.text = "email invalid"
}
}
Remarques
Alamofire request prend comme paramètres :
- URL du web service
- méthode : (POST/GET)
- paramètres : nil || [String :Any]
Gestion de la session en iOS :
Lorsqu’un utilisateur déjà connecté ouvre l’application une autre fois il doit être redirigé vers le Home et non vers la page login pour s’authentifier.
Pour gérer cela, on doit vérifier les données dans NSDefaults : la valeur de la variable booléenne userConnected
qu’on a déjà affectée lors de l’identification dans le web service précédent, car lorsque l’utilisateur se déconnecte, on doit changer cette valeur à false
. Cette vérification doit s’effectuer dans le fichier AppDelegate.swift
dans la fonction didFinishLaunchingWithOptions
.
var window: UIWindow?
let defaults = UserDefaults.standard
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Controle of Session
let userStaut = self.defaults.bool(forKey: "userStatut")
// Init Root View
var initialViewController : UIViewController?
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// Check User Connected
if(userStaut == false) {
// User Not Connected
var root : UIViewController?
root = storyboard.instantiateViewController(withIdentifier: "SignInViewController")
initialViewController = UINavigationController(rootViewController: root!)
//initialViewController = storyboard.instantiateViewController(withIdentifier: "SignInViewController")
}else {
// User Connected
initialViewController = storyboard.instantiateViewController(withIdentifier: "AppTabBar")
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
Perspective :
Vous pouvez télécharger le code source de l’API de service et l’application mobile via ce lien : https://github.com/moben-technology/TechBook
Dans le prochain article nous verrons comment utiliser des multipartFormData
. Nous ferons également une démo de la modification du profil avec téléchargement d’image à partir de votre galerie ou bien via la camera.