2 jours à FrenchKit 2022

Le 29 et 30 Septembre 2022 se tenait le FrenchKit, le plus grand salon Français entièrement consacré aux technologies de la firme à la pomme.

Après une édition 2021 avortée à cause de la pandémie désormais bien connue, ce ne sont pas moins de 250 développeurs et experts iOS / macOS du monde entier qui se sont réunis pour assister à différentes conférences, partager leurs connaissances, mais aussi… se détendre autour d’un rafraîchissement bien mérité, au soir du premier jour. Et tout cela dans une bonne humeur caractéristique de ce genre d'événement.

C’est donc tout naturellement que la practice Mobile Ippon s’est déplacée en force afin d’assister aux Conférences, Workshops et Masterclass présentés durant cet événement. Voici les principales présentations qui ont retenu leur attention.

Keynote vidéo by la team FrenchKit

L’aventure prit place au Pan Piper, l’une des antres du développement mobile et lieu historique d’accueil due FrenchKit. Nous commençâmes par une Keynote pleine d’humour et d’originalité, à la frontière entre Mozinor et la Comedia Dell’Arte. Au programme, une parodie de la dernière WWDC : une mise en scène entre Tim Cook et l’équipe FrenchKit.  Simple. Efficace. À la fois dans les clins d'œil (lecture de lettres, stéréotypes parisiens) et dans le montage (pro - mais pas trop 😂). La Keynote donna le ton de ces deux jours. Un événement d’expertise tech sans prise de tête.

How not to develop an "Umm" detector using create ML by Yono Mittlefehldt

Yono a opté pour un format Failcon rafraîchissant. Son postulat de départ était simple : Et s’il était possible de détecter les fameux temps d’arrêt dans le discours d’une personne ? Ces moments se traduisent bien souvent par l’utilisation d’onomatopées telles que “umm”, “uh”, “hem”, ou bien encore “heu” en français. N’ayant une expérience avec le Machine Learning que dans le cadre de la reconnaissance et la classification d’images, Yono décide d’expérimenter cette fois-ci avec de l’audio.

Sa première idée est d’utiliser Apple Speech qui contient des outils pour transcrire de la voix vers du texte (pour ensuite pouvoir l’analyser). Néanmoins, Speech n’est pas adapté car le contenu retourné est déjà filtré pour empêcher tout “bruit” qui pourrait polluer la retranscription. Il existe bien un “hack” qui consiste à enregistrer un contact sous le nom “Um” : en effet, Apple est capable de détecter lorsque le nom d’un contact est prononcé et celui-ci sera forcément pris en compte dans la retranscription. Il est d’ailleurs facile d’essayer la manipulation sur son téléphone, Siri détectera bien votre “Um” si vous l’avez dans votre répertoire.  

Face à ce premier échec, Yono décide alors d'entraîner son propre modèle ML qui fera spécifiquement ce qu’il désire. Créer un modèle nécessite plusieurs étapes et compétences : des maths, un dataset, de quoi entraîner le modèle et de quoi le tester. Yono choisit d’utiliser Create ML qui permet de simplifier fortement les deux dernières étapes mais également de s’affranchir des connaissances mathématiques requises. Il ne reste plus que le dataset à obtenir.


Heureusement, Yono possède quelques amis podcasteurs qui lui permettent de profiter d’un dataset assez conséquent. Il se met alors en tête de développer un modèle Machine Learning qui pourrait non seulement analyser les “failles” d’un discours, mais surtout prédire l'occurrence du prochain “Umm”.

Create ML est malheureusement limité sur la longueur des fichiers sons entrants. Yono doit alors couper tout son audio en segments d’une seconde. Ensuite vient le plus fastidieux : il faut classifier chaque fichier pour le placer dans une catégorie :

  • clean : l’échantillon n’a clairement pas de “um” identifié
  • “Um” : l’onomatopée est détectée dans le son
  • “uh” : un autre son détecté que Yono cherchait à reconnaître
  • silence : l’échantillon ne contient pas de son
  • unknown : si le fichier contient un mot avec le son “um” dedans, ou si ce n’est pas assez clair pour bien identifier ce qui est dit dans l’échantillon

Oui, Yono a classifié à la main tous ses fichiers. Il a d’ailleurs profité de la Touchbar de son MacBook durant ses longs vols en avion pour identifier les passages silencieux grâce au spectre audio affiché sur celle-ci.
Après une phase d'entraînement et de test, Yono tient enfin son modèle avec une précision de 89% !
Hélas, c’est là qu’il nous révèle que son modèle n’est pas si performant que ça, et cela pour 3 raisons :

  • Il ne fonctionne pas pour tout le monde. Il n’est précis que pour les voix utilisées dans les échantillons. La tonalité différente de chaque personne influe énormément sur le modèle entraîné.
  • Un set de données non équilibrées : trop de données “clean” pour pas assez de “umm” identifiés.
  • Une classification en amont très fastidieuse.

Pour tacler ces problèmes, Yono va tout essayer : ajouter plus de données, modifier artificiellement certains échantillons pour augmenter ou diminuer la tonalité. Il va également chercher une banque de données open source contenant beaucoup plus de voix différentes afin de généraliser son modèle. Pour accélérer la classification (initialement manuelle), il va jusqu’à utiliser son modèle pour “pré-classifier” ses nouvelles données. Toutes ces itérations sont nécessaires pour permettre d’affiner son modèle. L’important pouvant être résumé en deux mots : MORE DATA !

À l’arrivée, son modèle fonctionne ! Enfin en quelque sorte. Non en vrai pas trop. Il préfère nous le dire directement : “C’est plus compliqué que ça”. Parce qu’après toutes ces itérations, tout cet entraînement, le modèle reste ultra limité. Il nous est dit alors que le modèle “peut fonctionner” pour une partie des personnes parlant l’anglais américain, mais qu’il serait bien perdu pour de l’anglais Britannique ou l’anglais Indien. Son modèle fonctionne également pour lui, dans son environnement et pas ailleurs. Et la conclusion est toujours la même : il faut toujours plus de données pour améliorer le modèle.

Twitter URL

Hacking iOS Apps - And trying to prevent it by Elliot Shrock

Nos terminaux sont devenus des points d’entrées sur les systèmes distants. Ils peuvent eux-mêmes stocker des informations sensibles. Pendant sa démonstration Elliot Shrock attaque une application mobile de plusieurs manières et nous montre comment parer ses attaques.
Il fait preuve d’un talent d’orateur impressionnant, plein d’humour et sachant bien gérer les “effets démo”, il a d'ailleurs construit son speech de façon originale en commençant par la démonstration et finissant par se présenter.

La démonstration

Elliot commence par charger l’application cible sur un iPhone jailbreaké et utilise cycript pour injecter et évaluer des commandes Objc directement dans le processus de l’application. Il est capable d’appeler dismiss sur des viewController et de réaliser des transitions directement depuis son mac connecté via ssh à son téléphone.

En manipulant l’ipa et la renommant en .zip on peut y voir les fichiers sources et leur contenu. On peut alors voir dans les Storyboards les noms des différents écrans et les transitions possibles (segue).

Il réussit à récupérer les informations masquées par un écran nécessitant la saisie d’un code PIN en demandant à son contrôleur parent de retirer cet écran ou de réaliser une transition qui normalement nécessite la validation du code PIN.

Il utilise ensuite Hopper pour désassembler l’application. On peut y voir le fameux code pin en clair dans les symboles. Mais aussi tous les symboles de l’application (variables, classes, méthodes) qu’il va être ensuite facile de manipuler grâce à cycript.


Les solutions proposées :

  • Ne pas mettre d’informations sensibles dans le .plist ou tout autre ressource de l’application. Ces fichiers ne sont pas chiffrés ou compilés.
  • Ne pas utiliser Objective C. En effet Objc utilise un système de “dynamic dispatch” (contrairement à Swift qui utilise du “static dispatch”) et pour lequel qu’il est aisé d’injecter du code. Il est alors beaucoup plus sûr d’utiliser des classes pures Swift et éviter les liens vers l’Objective C. A proscrire:  @objc, @IBOutlet, @IBAction, etc.
  • Pour lutter contre l’injection, on peut aussi empêcher la connexion d’un debugger en ajoutant un fichier main.swift qui sert de point d’entrée à l’application. Dans ce fichier on appelle une fonction écrite en C désactivant gdb.
  • Détecter si l’application s’exécute sur un téléphone jailbreaké et interdire son exécution.
  • La possibilité de chiffrer les données, elles ne sont alors plus lisibles “simplement”.

Elliot met bien en avant que ces parades n’empêchent pas totalement une application d’être piratée mais que le but est de décourager le plus de monde possible.

Notre conseil de lecture : https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06j-testing-resiliency-against-reverse-engineering

Twitter URL


I 💕 Swift Concurrency by Tunde Adegoroye

La concurrence et le calcul distribué sont des problématiques mobiles communes. En effet, une application mobile suit souvent le principe d’un Main Thread (ou UI Thread), responsable du rendu graphique et des interactions utilisateurs, et d’un ou plusieurs Worker Thread(s). L’idée est de soulager le premier de toutes les tâches qui ne concernent pas la partie visible de l’application.



Pourtant, il est souvent compliqué de garder une approche simple peu importe les cas d’usages. Parmi eux :

  • Comment gérer l’I/O non blocking ?
  • Comment faire du calcul distribué ?
  • Comment retourner les résultats d’un Worker Thread sur l’UI Thread ?
  • Le tout, en garantissant une thread safety, pas de race condition et une implémentation élégante.
    Plusieurs essais ont déjà été faits comme RxSwift ou Combine pour traiter tout ou partie des use cases. Swift Concurrency est le dernier en date. Cette implémentation propose de reprendre ce qui se fait déjà dans d’autres écosystèmes : la notation async / await.
func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

Ici, on annote la fonction avec le mot clé async pour indiquer que du code asynchrone est exécuté.

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)

Pour utiliser la fonction, il faut la précéder du mot clé await. Cela met en pause l’exécution du code tant que la fonction n’est pas terminée, permettant à d’autres parties asynchrones de s’exécuter en parallèle.
La suite de la présentation s’est concentrée sur les problématiques de thread safety. Swift Concurrency propose les actors comme solution. Un actor est thread safe par défaut. Chaque donnée est partagée de manière sécurisée. Comment l’implémenter ? Il suffit de remplacer le mot clé class par actor :


actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}

Tunde a ensuite introduit d’autres concepts, comme async let ainsi que les task & task groups. Ces derniers reprennent une fonctionnalité phare des coroutines Kotlin : la structured concurrency. Si ce principe vous est inconnu, nous vous recommandons vivement de lire cet article.
Pour finir, nous sommes invités à nous référer aux implémentations faites par Apple de chaque concept. Elles sont sur GitHub. Merci Tunde !

Twitter URL


Cutting-Edge SwiftUI apps by Betty Godier

Outre les différents talks, nous avons pu assister à des classrooms sur différents thèmes. Après s'être présenté, Betty nous a fait découvrir pendant près d'une heure et demie les dernières nouveautés de SwiftUI.

MacOS et Xcode mis à jour, nous commençons par les charts, diagrammes en français. Les charts sont très simples à mettre en place. Il faut importer la librairie Chart et utiliser la vue Chart. Ce dernier contient lui-même autant de vues BarMark qu'il y aura de barres dans le diagramme. BarMark possède 2 arguments représentant les 2 axes x et y. Ils sont définis avec une valeur et ce que cette valeur représente.

struct WinesTab: View {
	var body: some View {
		Chart {
			BarMark(
				x: .value("Name", "Margaux"),
				y: .value("Sales", 900)
			)
			BarMark(
				x: .value("Name", "Médoc"),
				y: .value("Sales", 460)
			)
		}
	}
}


Chart permet même d'afficher plusieurs diagrammes à ligne s'il y a plusieurs sets de données.


struct SalesDetailsChart: View {
  var body: some View {
    Chart {
      ForEach(parisData) { element in
        BarMark(
          x: .value("Day", element.weekday, unit: .day)
          y: .value("Sales", element.sales)
        )
      }
    }
  }
}


Nous enchaînons avec le second thème : les fenêtres. En effet, grâce à SwiftUI nous pouvons maintenant créer des applications multiplateformes — iOS, iPadOS, macOS et WatchOS en même temps.
Reprenons notre fabuleuse application de chart précédemment créée. Dans notre point d'entrée, en ajoutant une scène Window ainsi qu'un raccourci avec le modifier .keyboardShortcut(), on peut ouvrir une seconde fenêtre à partir de la première fenêtre :


@main
struct FrenchKit2022App: App {
  var body: some Scene {
	WindowGroup {
	  ContentView()
	}
	Window("Paris c'est aussi le FrenchKit!", id: "Sales") {
	  ParisDetailsChart()
	}
	.keyboardShortcut("9")
	.defaultPosition(.topLeading)
	.defaultSize(width: 420, height: 250)
  }
}

Il est aussi possible de créer une fenêtre spécifiquement pour la MenuBar des Macs (c'est la barre en haut à droite) grâce à la scène MenuBarExtra:


@main
struct FrenchKit2022App: App {
	var body: some Scene {
		WindowGroup {
			ContentView()
		}
		MenuBarExtra(
			"Conference",
			systemImage: "lasso.and.sparkles"
		) {
			SalesDetailsChart()
			Text("Result")
		}
		.menuBarExtraStyle(.window)
	}
}

Enfin, dernière notion et pas des moindres : les layouts, mise en page en français. La vue Grid nous permet de placer nos vues enfant sous forme de grille et comme nous le souhaitons. Nous pouvons y placer différentes vues GridRow contenant les vues à afficher pour une ligne. Si une vue enfant de GridRow doit prendre plus d'espace qu'une autre, nous pouvons utiliser le modifier gridCellCollumn.


struct WeatherDetailView: View {
	var body: some View {
		Grid {
			GridRow {
				NameHeadline()
					.gridCellColumns(2)
			}
			GridRow {
				NameHeadline()
					.gridCellColumns(2)
			}
		}
	}
}

Voici un cours écrit rédigé par Betty sur ces notions. Merci Betty!

Twitter URL


Cloud Functions to the Rescue by Zamzam Farzamipooya


Qui n’a jamais rêvé, en tant que développeur mobile / front, de disposer d’API exécutant automatiquement le code BackEnd de son choix, sans avoir à se préoccuper du serveur et de l’infogérance associée ?

Depuis plusieurs années, l’essor du Serverless apporte une réponse concrète et financièrement intéressante à ce souhait d’autonomie.

Parmi les services proposés, on retrouve les Cloud Functions for Firebase côté Google Cloud Platform, offrant des API clé en main, avec des garanties de sécurité, haute disponibilité et scalabilité. L’intérêt ? Se concentrer uniquement sur la partie métier du BackEnd.

La preuve par l’exemple

Comme l’a présenté Zamzam, le déploiement d’une Cloud Function est simple, et ne nécessite que 3 étapes :

  1. Installation de la CLI Firebase
  2. Développement de la partie Backend (index.ts ou index.js)
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions');

// Take the text parameter passed to this HTTPS endpoint and return an uppercase result
exports.toUpperCase = functions.https.onRequest(async (req, res) => {
  // Grab the text parameter.
  const original = req.query.text;
  // Transform
  let transformed = original.toUpperCase();
  // Send back the successfully transformation
  res.json({result: transformed});
});

  1. Test & Déploiement

Voilà, notre API est maintenant déployée et prête à être utilisée par notre application iOS.

Côté Front, l’intégration à cette nouvelle API ne requiert pas plus de complexité. L’autre avantage des Cloud Functions est en effet de pouvoir être interrogées non seulement par requête REST, mais aussi via différents SDK disponibles pour plusieurs plateformes :  Android, iOS & Web (JS).



Voici le code implémenté côté Swift pour appeler notre API toUpperCase, à l’aide du SDK fourni par Firebase :

var functions = Functions.functions()
functions.httpsCallable("toUpperCase").call(["text": inputField.text]) { result, error in
  if let error = error as NSError? {
    if error.domain == FunctionsErrorDomain {
      let code = FunctionsErrorCode(rawValue: error.code)
      let message = error.localizedDescription
      let details = error.userInfo[FunctionsErrorDetailsKey]
    }
    // ...
  }
  if let data = result?.data as? [String: Any], let text = data["text"] as? String {
    self.resultField.text = text
  }
}

Les Cloud Functions, en résumé

Les Cloud Functions apportent une solution concrète et pérenne aux développeurs mobile.
Pour information, nous utilisons déjà cette solution chez Ippon Technologies, dans le cadre de projets standalone, ou chez des clients intéressés par ces infrastructures Serverless.
A savoir, d’autres Cloud Providers comme Amazon Web Services et Azure proposent aussi cette approche Serverless, avec une approche plus ou moins similaire.

Enfin, comme précisé précédemment, l’intérêt du Serverless réside aussi dans la facturation associée, nommée Pay as you go. Celle-ci dépend du succès de votre application, un business model intéressant pour tout type de société.

—-----

Pour finir, Zamzam a souhaité exprimer son soutien aux femmes iraniennes, qui luttent actuellement pour leur liberté face à la répression du régime en place ; soutien applaudi par l’ensemble de la salle, et que nous ne pouvions que relayer à travers cet article. #WomenLifeFreedom

—------

Twitter URL


The journey of widgets begins with one step by Audrey Sobgou Zebaze

Parmi les nouveautés sur SwiftUI annoncées lors de la WWDC22, il y a des améliorations apportées au package WidgetKit. Audrey Sobgou Zebaze nous fait une petite démonstration de ce qu’il est possible de faire actuellement.

Pour créer un nouveau Widget, il faut créer un nouveau projet sur XCode et choisir l’option WidgetKit Extension.


WidgetKit

Il existe 2 types de Widget : StaticConfiguration et IntentConfiguration. Le premier permet de créer des widgets statiques permettant d’afficher du contenu tandis que le second crée un widget paramétrable (choix d’un code postal pour la météo par exemple).

@main
struct MyWidget: Widget {
    let kind: String = "MyWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            // Some View here
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        
        IntentConfiguration(
            kind: kind,
            intent: ConfigurationIntent.self,
            provider: Provider()
        ) { entry in
            // Some View here
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

Les configurations nécessitent un “Provider” qui sera l’objet portant les données nécessaires à l’affichage.

Dynamic Island

Avec l’arrivée d’iOS 14, l’encoche pour l’appareil photo à l’avant devient dynamique.



Audrey nous montre comment afficher certaines informations et choisir l’emplacement de ces informations. Pour ce faire, on crée une extension Widget à notre application comme précédemment mais cette fois-ci, on crée un nouveau type de configuration : ActivityConfiguration.

import ActivityKit
import WidgetKit
import SwiftUI

@available(iOSApplicationExtension 16.1, *)
@main
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: MyAttributes.self) { context in
            // Some View here
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.center) {
                    // This define the view to be displayed when the Dynamic Island is expanded
                }
            } compactLeading: {
                // This define items to be displayed on the left side of the camera
                // For example: Image(systemName: "person")
                // draws an user icon on the left side
            } compactTrailing: {
                // Same as above but for the right side of the camera
            } minimal: {
                // The minimal view is displayed when there are at least two live activity widgets
            }
        }
    }
}

Ici, notre Widget utilise les propriétés de l’objet MyAttributes pour construire la DynamicIsland. Cette classe doit hériter de la classe ActivityAttributes et donc implémenter la propriété ContentState, propriété qui contient les valeurs dynamiques si besoin.

struct MyAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        // Some dynamic properties here
    }
    
    // Some static properties here
}

Les Widgets et les DynamicIsland sont assez simples à ajouter dans nos applications iOS. Toutefois, pour l’implémentation de cette dernière, il faut passer par la version bêta d’Xcode car elle n’est pas encore déployée au grand public. C’est une fonctionnalité que nous attendons avec impatience !

Twitter URL


Conclusion

De manière générale, tout le monde est d’accord pour dire que cette édition du FrenchKit fut une franche réussite. En somme, un pari réussi après deux années compliquées.

Comme nous avons pu le voir, notre équipe a énormément profité de ces deux jours pour assouvir sa curiosité et s’immerger encore un peu plus dans l’écosystème iOS/macOS.

Cette année, SwiftUI avait le vent en poupe. L’année prochaine, nous aurons droit à une version différente de ce salon, mais probablement tout aussi passionnante ! Et soyez sûrs qu’Ippon et la Team Mobile seront présents. ;-)