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 :

  1.  pod ‘libraryName’, ‘~ library version’
  2.  pod ‘libraryName’, 

Dans notre cas, on va installer 3 bibliothèques :

  1. Alamofire : Pour la consommation des web services
  2. SDWebImage : Téléchargement rapide d’images à travers les URL
  3. 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 :

  1. Décoder le token 
  2. Parser la réponse dans le model User
  3. Sauvegarder l’object User ‘parsé’ dans UserDefaults.standard 
  4. Sauvegarder une variable bool userConnected = true dans UserDefaults.standard pour gérer la session après (voir ci-dessus)
  5. 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.

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *