L’App Store n’est pas un endroit indulgent. Rien qu’en 2024, Apple a examiné 7,77 millions de soumissions d’applications et en a rejeté près de 1,93 million, soit environ une sur quatre. Les problèmes de performance, les atteintes à la vie privée et les incohérences de design étaient les principales raisons de rejet, et bon nombre de ces problèmes auraient pu être détectés lors d’une bonne revue de code avant même la soumission.
Selon Business of Apps, les dépenses des consommateurs sur iOS ont atteint 117,6 milliards de dollars en 2025, et les utilisateurs iOS dépensent près de deux fois plus par application que les utilisateurs Android. Ce type d’écosystème récompense les applications construites selon des normes élevées, et pénalise celles qui rognent sur la qualité ou la sécurité.
Une revue de code structurée est l’un des investissements les plus directs qu’une équipe iOS puisse réaliser avant un lancement ou une levée de fonds. C’est également indispensable si vous prévoyez de confier votre base de code à un nouveau partenaire de développement. C’est l’étape qui transforme une application fonctionnelle en une application qui continue de fonctionner.
Aujourd’hui, nous partageons avec vous une liste de contrôle qui reprend ce que nos ingénieurs signalent lors de chaque revue, organisée autour des problèmes qui font réellement échouer les projets iOS en production et lors de la validation par l’App Store. Notez que Apple se montre encore plus sévère envers les applications développées en « vibe coding ». Par conséquent, si vous voulez que la vôtre réussisse, un audit professionnel pourrait bien être la clé pour qu’elle soit validée par l’App Store.
Préparation avant la revue
Une revue de code productive commence avant même la lecture de la première ligne. Bien poser le contexte en amont permet à la revue elle-même de se concentrer sur les véritables problèmes plutôt que sur la confusion liée à la configuration.
Définir la portée et les objectifs :
- Déterminer si la revue porte sur l’ensemble de la base de code, un module fonctionnel spécifique, une soumission App Store en attente, ou une préparation à un audit préalable pour des investisseurs
- Définir des critères de réussite mesurables : réduction des plantages, résolution des problèmes de sécurité, amélioration de la couverture de tests, ou préparation à une version spécifique d’iOS
- Clarifier les appareils cibles de l’application et la version minimale d’iOS, car chaque version a ses propres API, exigences de permissions et particularités de comportement
- Identifier quelles extensions sont concernées : les App Clips, les extensions WidgetKit, les Live Activities et les Share Extensions ont chacune leur propre périmètre de revue
Vérifier l’environnement et les outils :
- Confirmer que le projet se compile proprement dans Xcode, sans avertissement ni erreur, en configuration debug comme en configuration release
- Vérifier que la version stable et correcte de Xcode est utilisée. Apple impose des exigences de SDK, et l’utilisation d’une version obsolète peut totalement empêcher la soumission à l’App Store
- S’assurer que tous les membres de l’équipe utilisent les mêmes versions de Swift et des dépendances, car des versions différentes introduisent des bugs subtils et difficiles à tracer
- Exécuter le projet sur un appareil physique, pas seulement sur le simulateur, car certains problèmes de mémoire, de performance et de matériel n’apparaissent que sur du matériel réel
Vérifier les dépendances et la gestion des packages :
- Confirmer que tous les packages tiers sont gérés de manière cohérente via Swift Package Manager, CocoaPods ou Carthage, et non via un mélange des trois sans justification documentée
- Effectuer un audit des dépendances et vérifier qu’aucun package ne comporte de vulnérabilités de sécurité connues
- Vérifier la date de dernière publication et l’activité de maintenance de chaque dépendance. Une bibliothèque abandonnée représente un risque futur de compatibilité et de sécurité
- Confirmer que les fichiers de verrouillage des dépendances (Package.resolved, Podfile.lock) sont bien commités dans le contrôle de version, afin que tous les membres de l’équipe compilent avec des versions identiques
- Vérifier qu’aucune dépendance n’introduit de package transitif sous licence restrictive ou incompatible. Une dépendance transitive sous licence GPL dans une application commerciale de l’App Store constitue un problème juridique
- Vérifier que les dépendances Swift Package Manager sont épinglées à des versions exactes ou à des hachages de commit dans les projets sensibles en matière de sécurité, plutôt qu’à des plages flottantes comme .upToNextMajor
Vérifier la documentation et l’historique du projet :
- S’assurer que le README décrit clairement comment configurer, compiler, exécuter et tester le projet à partir d’une machine vierge
- Examiner les commits récents pour comprendre ce qui a changé et pourquoi, avant de passer en revue le code lui-même
- Vérifier que les pull requests ouvertes sont rebasées sur la dernière version de la branche principale
Organisation du code et architecture
La structure d’un projet iOS en dit long sur l’équipe qui l’a conçu. Un projet bien organisé est plus facile à étendre, plus facile à tester, et bien moins coûteux à maintenir à mesure que les besoins évoluent.
Structure des dossiers et séparation des modules :
- Vérifier que le projet suit une structure de dossiers cohérente : des répertoires distincts pour les modèles, les vues, les view models ou contrôleurs, les services, les utilitaires et les ressources
- Confirmer que l’application applique un modèle architectural défini de manière cohérente dans toute la base de code ; MVVM, MVC, VIPER ou Clean Architecture sont tous des choix valables, mais quel que soit le modèle retenu, il doit être appliqué uniformément, et non abandonné en cours de projet
- S’assurer que la logique métier ne réside pas dans les sous-classes de UIViewController. Les view controllers doivent uniquement gérer la navigation et les interactions utilisateur, en déléguant le traitement des données et les règles métier à des couches de service ou de view model dédiées
Mauvaise pratique :
// ViewController doing too much
class OrderViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest<Order>(entityName: "Order")
request.predicate = NSPredicate(format: "status == %0", "pending")
let orders = CoreDataStack.shared.context.fetch(request)
let total = orders.reduce(0) { $0 + $1.price * $1.quantity * taxRate }
sendConfirmationEmail(to: currentUser.email, total: total)
}
}
Bonne pratique :
// ViewController delegates to a service
class OrderViewController: UIViewController {
private let orderService: OrderService
override func viewDidLoad() {
super.viewDidLoad()
Task {
let summary = await orderService.fetchPendingSummary()
updateUI(with: summary)
}
}
}
Conventions de nommage :
- Utiliser le camelCase pour les variables, fonctions, propriétés et constantes globales déclarées avec let ; le PascalCase pour les types, protocoles et énumérations
- UPPER_SNAKE_CASE est une convention issue d’Objective-C et du C, et n’est pas idiomatique en Swift – à éviter dans le nouveau code Swift
- S’assurer que les noms de fichiers correspondent au type qu’ils contiennent : un fichier nommé UserProfileViewModel.swift ne devrait contenir que le type UserProfileViewModel, et rien d’autre
- Éviter les noms de variables d’une seule lettre en dehors des closures très courtes. Par exemple, user est toujours plus clair que u, et completion toujours plus clair que c
Utilisation des protocoles et des extensions :
- Vérifier que les protocoles sont utilisés pour définir un comportement partagé plutôt que des chaînes de sous-classement. Les hiérarchies de classes profondes dans le code iOS sont plus difficiles à tester et à modifier
- Vérifier que les extensions sont utilisées pour organiser les fonctionnalités en groupes logiques, plutôt que de créer des fichiers de plusieurs milliers de lignes
- Confirmer que les fichiers d’extension portent des noms explicites, comme String+Validation.swift ou UIColor+BrandColors.swift, afin que leur objectif soit immédiatement clair
Langage Swift et qualité du code
Swift met à votre disposition de nombreux outils pour écrire du code sûr et expressif. La revue de code est l’occasion de vérifier que ces outils sont bien utilisés, et non contournés.
Optionnels et déballage forcé :
- Examiner chaque occurrence de l’opérateur de déballage forcé (!) et confirmer qu’elle est justifiée ; déballer de force une valeur nil fait planter l’application immédiatement, et la plupart des usages peuvent être remplacés par des alternatives plus sûres
- Vérifier que guard let et if let sont utilisés pour déballer les optionnels en toute sécurité, et que les chemins d’échec traitent réellement la valeur absente plutôt que de simplement retourner silencieusement
Mauvaise pratique :
let user = userRepository.findUser(by: id)! // crashes if not found
let name = user.profile!.displayName! // crashes on nil profile or name
Bonne pratique :
guard let user = userRepository.findUser(by: id) else {
logger.warning("User not found for id: (id)")
showErrorState()
return
}
let name = user.profile?.displayName ?? "Anonymous"
Gestion des erreurs :
- Vérifier que les fonctions pouvant échouer utilisent le mécanisme throws de Swift plutôt que de retourner nil ou de s’appuyer sur des optionnels pour signaler des erreurs. Les appelants méritent de savoir ce qui s’est mal passé
- Vérifier que tous les appels try à l’intérieur des blocs do-catch disposent de clauses catch spécifiques pour les types d’erreurs connus, car un bloc catch-all unique qui ignore les erreurs ne constitue pas une gestion des erreurs, mais plutôt une « dissimulation d’erreurs »
- Confirmer que les erreurs sont journalisées avec suffisamment de contexte pour qu’un développeur comprenne ce qui s’est passé, quelle entrée l’a déclenchée, et à quel endroit de la base de code l’échec s’est produit
Types valeur et types référence :
- Examiner l’usage de struct par rapport à class ; les structs sont copiées, ce qui les rend plus sûres dans le code concurrent et plus faciles à raisonner ; utiliser des classes lorsque vous avez explicitement besoin d’une sémantique de référence ou de sous-classement
- Vérifier que les structs se conforment à Equatable et Hashable lorsque ces conformités sont utiles, par exemple lorsque le type est stocké dans des sets ou utilisé comme clé de dictionnaire
- Vérifier que les grandes structs ne transitent pas par des chemins de code sensibles aux performances où la copie serait coûteuse ; profiler d’abord, optimiser ensuite
Concurrence :
- Confirmer que la base de code utilise le modèle de concurrence moderne de Swift (async/await, actor, Task) plutôt qu’un mélange de DispatchQueue, OperationQueue et de completion handlers produisant des effets de bord imprévisibles
- Vérifier que toutes les mises à jour de l’interface utilisateur se font sur le thread principal ; mettre à jour un UILabel ou appeler tableView.reloadData() depuis un thread en arrière-plan produit des plantages intermittents et notoirement difficiles à reproduire
Mauvaise pratique :
URLSession.shared.dataTask(with: url) { data, _, _ in
self.titleLabel.text = parseTitle(from: data) // UI update on background thread
}.resume()
Bonne pratique :
Task {
let title = try await fetchTitle(from: url) // runs off main thread
await MainActor.run {
self.titleLabel.text = title // UI update on main thread
}
}
- Vérifier les usages de @MainActor pour s’assurer que les view models et le code lié à l’UI sont correctement isolés sur le thread principal
Préparation à la concurrence Swift 6 :
Swift 6 introduit une vérification stricte des data races. Les projets doivent être audités dès maintenant pour évaluer leur état de préparation, même si la migration se fait par étapes.
- Vérifier le paramètre de compilation SWIFT_STRICT_CONCURRENCY du projet. Une valeur complete active une vérification des data races au niveau de Swift 6 et constitue l’état cible pour tout nouveau code. Les projets encore en minimal ou targeted doivent disposer d’un plan de migration documenté
- Examiner tous les types passés à travers les frontières de concurrence pour vérifier leur conformité à Sendable. Le compilateur signale les types non-Sendable en mode strict, mais en mode souple, ce sont des data races silencieuses qui apparaissent sous charge
- Auditer les usages de Task.detached. Contrairement à un Task { } structuré, les tâches détachées n’héritent pas du contexte d’acteur de l’appelant ni des valeurs locales à la tâche. Chaque appel à Task.detached devrait comporter un commentaire expliquant pourquoi il est intentionnellement non structuré
Audit des dépréciations :
Les avertissements de dépréciation s’accumulent avec le temps et bloquent l’adoption des futurs SDK. Il est donc impératif de régler ces problèmes avant qu’ils ne nuisent à l’avancement du développement de votre application.
- Lancer une compilation avec -warn-deprecated-declarations et résoudre tous les avertissements avant la soumission
- Vérifier l’usage de UIWebView (totalement banni de l’App Store depuis 2023), d’OpenGL ES (déprécié), et des motifs de pont Objective-C qui disposent d’équivalents modernes en Swift
- Vérifier que la cible de déploiement minimale est définie intentionnellement, et non laissée à une ancienne version d’iOS qui impose l’usage d’API dépréciées
Gestion de la mémoire
Les problèmes de gestion de la mémoire sont la principale cause de plantages en production des applications iOS. Swift utilise l’Automatic Reference Counting (ARC) pour gérer la mémoire, mais ARC exige tout de même des développeurs qu’ils fassent des choix réfléchis sur la manière dont les objets se référencent entre eux.
Cycles de rétention :
- Rechercher les closures qui capturent fortement self. C’est l’une des sources les plus courantes de fuites mémoire dans les applications iOS, notamment dans les callbacks réseau, les timers et les blocs d’animation
Mauvaise pratique :
// Strong capture of self creates a retain cycle
networkClient.fetchData { response in
self.processResponse(response) // self retains networkClient, networkClient retains self
}
Bonne pratique :
networkClient.fetchData { [weak self] response in
guard let self else { return }
self.processResponse(response)
}
- Vérifier que les propriétés delegate sont déclarées comme weak, car une référence delegate forte est presque toujours un cycle de rétention en puissance
- Examiner les usages de unowned pour confirmer que l’objet référencé survivra toujours à l’objet qui le référence. En cas de doute, utiliser weak à la place, car une référence unowned pendante fait planter l’application
Détection des fuites mémoire :
- Confirmer que l’équipe utilise le Memory Graph Debugger de Xcode et l’outil Leaks d’Instruments dans le cadre du processus de développement et de revue
- Vérifier que les méthodes deinit sont implémentées dans les classes clés et qu’elles s’exécutent comme prévu ; une classe dont le deinit n’est jamais appelé est une classe qui fuit
- Vérifier que les observateurs NotificationCenter sont supprimés lorsqu’un view controller ou un objet est désalloué, car des observateurs non libérés maintiennent l’objet en vie et se déclenchent sur des cibles désallouées
Gestion des ressources :
- Examiner le chargement des images et des assets pour confirmer que les grandes images ne sont pas conservées en mémoire plus longtemps que nécessaire. Utiliser UIImage(named:) pour les images fréquemment réutilisées et UIImage(contentsOfFile:) pour les grandes images qui ne doivent pas être mises en cache
- Vérifier que les données chargées depuis le réseau ou le disque ne sont pas conservées indéfiniment en mémoire alors qu’elles pourraient être stockées dans un cache disque ou récupérées à la demande
Réseau et gestion des données
Le code réseau est souvent l’endroit où de nombreuses applications iOS accumulent de la dette technique. Des couches réseau mal structurées deviennent coûteuses à maintenir à mesure que la surface de l’API s’étend.
Organisation de la couche réseau :
- Confirmer que toutes les requêtes réseau sont centralisées dans une couche réseau dédiée, plutôt que dispersées entre les view controllers et les view models
- Vérifier que les endpoints d’API, les URL de base et les jetons d’authentification ne sont pas codés en dur dans des fichiers individuels, car ils devraient être gérés via la configuration d’environnement
- Vérifier que les requêtes réseau utilisent URLSession ou une bibliothèque bien maintenue comme Alamofire, avec une stratégie cohérente de gestion des erreurs
Analyse et validation des réponses :
- Examiner l’usage de Codable pour l’analyse JSON, car les modèles doivent se décoder avec souplesse et gérer les champs manquants ou inattendus sans planter
- Confirmer que les réponses du serveur sont validées avant utilisation, et ne jamais supposer qu’un champ marqué optionnel dans la documentation de l’API l’est réellement en pratique
- Vérifier que la pagination, la gestion des délais d’expiration et la logique de nouvelle tentative sont implémentées pour toute requête susceptible de rencontrer des conditions réseau instables
SwiftData et Core Data :
À l’heure actuelle, la couche de persistance recommandée pour les applications ciblant iOS 17 et versions ultérieures est SwiftData. Cependant, Core Data reste pleinement pris en charge pour les applications qui l’utilisent déjà. Vous êtes donc libre de choisir l’option la plus adaptée à votre cas, tant que vous le faites de manière sûre.
- Si l’application cible iOS 17 ou une version ultérieure, vérifier si SwiftData est utilisé ou envisagé. La macro @Model de SwiftData remplace NSManagedObject et s’intègre nativement avec la concurrence Swift
- Pour les projets utilisant Core Data, confirmer que les requêtes fetch utilisent NSFetchRequest avec des chaînes de format NSPredicate paramétrées – jamais d’interpolation de chaînes – afin d’éviter les injections de prédicats
- Vérifier que les contextes d’objets gérés par Core Data sont utilisés sur le bon thread. NSManagedObjectContext n’est pas thread-safe ; utiliser performAndWait ou des contextes en arrière-plan pour le travail hors du thread principal
- Vérifier que les stratégies de migration SwiftData ou Core Data sont documentées et testées avant chaque version qui modifie le modèle
Données sensibles en transit :
- Confirmer que l’App Transport Security (ATS) est activé et qu’aucune exception n’a été ajoutée à Info.plist sans raison documentée. Les exceptions ATS autorisent les connexions HTTP en clair et constituent une voie directe vers l’interception des données
- Pour les applications à haute exigence de sécurité, comme les applications bancaires ou de santé, envisager la mise en place du SSL pinning afin de garantir que l’application ne communique qu’avec des serveurs de confiance, même si une autorité de certification est compromise. Privilégier l’épinglage de clé publique (SPKI) plutôt que l’épinglage complet du certificat, car ce dernier casse dès que le serveur renouvelle son certificat et nécessite une mise à jour d’urgence de l’application. Pour la plupart des applications, ATS combiné à un TLS moderne offre une protection suffisante sans ce risque opérationnel
Bonnes pratiques de sécurité
iOS dispose de solides capacités de sécurité intégrées, ce qui est un atout indéniable. Cependant, elles ne sont utiles que si elles sont correctement mises en œuvre. Les directives d’Apple placent la confidentialité et la sécurité au sommet de leurs critères de revue. Le rapport de transparence 2024 de l’App Store en apporte la preuve, confirmant que les atteintes à la vie privée restent parmi les principales causes de rejet et de retrait.
Trousseau (Keychain) et stockage sécurisé :
- Confirmer que les données sensibles telles que les jetons d’authentification, les mots de passe et les clés cryptographiques sont stockées dans le Keychain iOS, et non dans UserDefaults, des fichiers en clair ou NSUserDefaults. Les éléments du Keychain sont chiffrés et liés à l’identité de l’application
- Vérifier que les éléments du Keychain utilisent des paramètres d’accessibilité appropriés : kSecAttrAccessibleWhenUnlockedThisDeviceOnly convient à la plupart des données sensibles et empêche la sauvegarde iCloud et la migration vers de nouveaux appareils
- Examiner tout usage du Secure Enclave pour les opérations cryptographiques sur les appareils qui le prennent en charge. Le Secure Enclave effectue les opérations cryptographiques dans le matériel, sans jamais exposer la clé à la mémoire de l’application
Protection des données :
- Confirmer que les fichiers contenant des données sensibles sont créés avec la classe de protection des données NSFileProtectionComplete, qui les chiffre lorsque l’appareil est verrouillé
- Vérifier que les données sensibles sont effacées de la mémoire dès qu’elles ne sont plus nécessaires ; après avoir utilisé un mot de passe ou un jeton, écraser la variable et la mettre à nil plutôt que de la laisser en mémoire jusqu’au prochain passage du ramasse-miettes
Validation des entrées et prévention des injections :
- Examiner tous les points où une saisie fournie par l’utilisateur atteint une requête de base de données, un chemin de fichier ou une construction d’URL, car ce sont des points d’injection potentiels qui doivent être validés et nettoyés avant utilisation
- Confirmer que les vues web n’exécutent pas de JavaScript provenant de sources non fiables. Si une WKWebView charge du contenu externe, désactiver JavaScript sauf si celui-ci est nécessaire
Authentification et gestion des sessions :
- Vérifier que l’application utilise Sign in with Apple ou un flux OAuth 2.0 sécurisé, plutôt que de développer sa propre implémentation d’authentification
- Vérifier que les jetons de session sont actualisés de manière appropriée, et que les jetons expirés sont supprimés du stockage. Un jeton valide il y a six mois ne devrait plus donner accès aujourd’hui
- Confirmer que l’authentification biométrique (Face ID, Touch ID) passe par LocalAuthentication avec un repli sur le code d’accès de l’appareil, et non comme substitut à l’authentification côté serveur
Optimisation des performances
Les utilisateurs iOS attendent une réactivité instantanée de leurs applications. Une application lente est donc une application supprimée. La bonne nouvelle, c’est que la plupart des problèmes de performance proviennent de schémas spécifiques et identifiables, qu’une revue de code peut détecter avant qu’ils n’atteignent les utilisateurs.
Performance du thread principal :
- Identifier toute opération d’E/S sur fichier, lecture de base de données, appel réseau ou calcul lourd s’exécutant sur le thread principal. Tout cela devrait être déplacé vers des threads en arrière-plan, les résultats étant ensuite renvoyés au thread principal pour les mises à jour de l’interface
- Vérifier que viewDidLoad et viewWillAppear ne contiennent pas de traitements synchrones coûteux. Ces méthodes s’exécutent sur le thread principal, et tout délai bloque directement l’interface
Optimisation des table views et collection views :
- Vérifier que la réutilisation des cellules UITableView et UICollectionView est correctement implémentée, et que les cellules ne conservent pas l’état des éléments précédents après avoir été réutilisées
- Vérifier que les images affichées dans les cellules sont chargées de manière asynchrone, et qu’un placeholder est affiché pendant leur récupération, car un chargement synchrone dans une vue défilante provoque des saccades visibles
- Confirmer que la hauteur des cellules est calculée efficacement ; appeler systemLayoutSizeFitting sur chaque cellule dans tableView(_:heightForRowAt:) est une cause fréquente de saccades lors du défilement
Temps de lancement et performance au démarrage :
- Examiner la séquence de démarrage de l’application et confirmer que seules les tâches essentielles s’exécutent avant l’affichage du premier écran. Le chargement différé et l’initialisation paresseuse doivent être utilisés pour tout ce qui n’est pas requis au lancement
- Vérifier que les gros assets et ressources ne sont pas chargés de manière synchrone au démarrage de l’application, et utiliser des files d’attente en arrière-plan avec un état de chargement si nécessaire
- Vérifier que l’application a été profilée avec l’instrument Time Profiler de Xcode, car identifier les véritables goulots d’étranglement est toujours plus productif que de deviner
Efficacité énergétique et des ressources :
- Examiner l’usage des tâches en arrière-plan et confirmer que les modes d’arrière-plan déclarés dans les entitlements de l’application sont réellement nécessaires. Des modes d’arrière-plan superflus épuisent la batterie et attirent un examen plus poussé de la part de l’App Store
- Vérifier que les mises à jour de localisation utilisent le niveau de précision le plus bas requis par la fonctionnalité ; demander kCLLocationAccuracyBest pour une fonctionnalité qui n’a besoin que de la ville de l’utilisateur constitue une consommation de batterie importante
Xcode Instruments
- Time Profiler – identifier les points chauds CPU sur le thread principal. Rechercher toute tâche dépassant 16 ms susceptible de bloquer une frame
- Allocations – suivre la croissance du tas au cours d’une session utilisateur type. Un graphique qui ne se stabilise jamais en utilisation normale signale une fuite ou un cache non borné
- Leaks – détection automatisée des cycles de rétention. À exécuter après chaque fonctionnalité majeure, et pas seulement avant la soumission
- Memory Graph Debugger – intégré à Xcode, il affiche le graphe d’objets en direct. Le moyen le plus rapide de prouver l’existence d’un cycle de rétention et d’identifier précisément quelle référence le maintient
- Hangs – détecte les blocages du thread principal au-delà d’un certain seuil. Apple utilise ces mêmes données pour la métrique Hang Rate visible dans Xcode Organizer
- Energy Log / CPU Usage – mesure l’impact sur la batterie des tâches en arrière-plan, des mises à jour de localisation et du Bluetooth. Les applications qui épuisent la batterie sont supprimées et mal notées
- Network – simuler des connexions de mauvaise qualité (Edge, 3G, perte de paquets) pour détecter les failles de gestion des délais d’expiration qui n’apparaissent que dans des conditions réelles
- Modèle Core Data – inspecter les performances des requêtes fetch, les schémas de déclenchement des faults et les E/S du magasin persistant pour les applications utilisant Core Data
- SwiftUI – nombre de re-rendus du body par vue. Un tueur de performance invisible dans les applications SwiftUI où l’identité des vues est mal configurée
Taille de l’application et analyse du binaire
- Examiner la taille non compressée du binaire de l’application et confirmer qu’elle reste sous le seuil d’avertissement de téléchargement cellulaire d’Apple (actuellement 200 Mo)
- Utiliser le rapport App Size Report de Xcode, généré lors de l’archivage, pour identifier quels frameworks, assets et ressources contribuent le plus à la taille du binaire
- Vérifier qu’aucun asset inutilisé n’est intégré au bundle
- Vérifier que les gros assets multimédias utilisent les On-Demand Resources s’ils ne sont pas nécessaires au premier lancement
Configuration de compilation et signature du code
Les problèmes de configuration de compilation sont une source fréquente de bugs en production et de rejets de la part de l’App Store. Ils sont rarement couverts par les revues de code, mais toujours pénalisants lorsqu’ils sont négligés.
Configurations de compilation Debug et Release
- Confirmer que les configurations Debug et Release diffèrent réellement : assertions activées en Debug, supprimées en Release ; journalisation de débogage désactivée en Release
- Vérifier que le drapeau DEBUG est utilisé de manière cohérente, et qu’aucun chemin de code réservé au débogage – comptes de test, endpoints de journalisation, feature flags – ne peut se retrouver dans un binaire de production
- Vérifier que les optimisations du compilateur sont correctement configurées : None [-O0] pour Debug (compilations plus rapides, meilleur débogueur) et Optimize for Speed [-O] ou Optimize for Size [-Oz] pour Release
- Vérifier que la suppression du code mort est activée pour les compilations Release, afin de réduire la taille du binaire et d’éliminer les chemins de code inaccessibles
Signature du code et provisioning
- Confirmer que les profils de provisioning sont gérés via la signature automatique de Xcode ou un outil comme Fastlane Match, et non téléchargés manuellement, ce qui expose à des profils expirant sans avertissement et cassant les pipelines CI
- Vérifier que les entitlements du fichier .entitlements correspondent exactement à ce qui est activé dans la configuration des capacités d’App Store Connect. Toute divergence provoque des échecs d’archivage ou une rupture silencieuse des capacités à l’exécution
- Vérifier que les certificats de notification push ou les clés d’authentification sont renouvelés avant expiration, et que l’équipe dispose d’un processus de renouvellement documenté
Tests et couverture de code
Les tests constituent le contrat écrit entre votre code et le comportement qu’il promet. Des tests complets sont donc une étape incontournable si vous voulez que votre application soit validée par l’App Store et ait une chance d’attirer des utilisateurs. De plus, sans tests récurrents, chaque changement comporte des risques cachés.
Tests unitaires et d’intégration :
- Vérifier que toute la logique métier, les fonctions de transformation de données et les classes de service disposent de tests unitaires couvrant le cas nominal, les cas limites et les conditions d’échec
- Confirmer que les tests utilisent l’injection de dépendances ou des abstractions basées sur des protocoles, afin que les appels réseau réels, les bases de données et le matériel soient remplacés par des doublures de test
- Vérifier que les tests d’intégration couvrent de bout en bout les parcours utilisateur critiques, notamment l’authentification, le traitement des paiements et la persistance des données
Tests d’interface utilisateur :
- Confirmer que les parcours utilisateur critiques disposent de tests d’interface s’exécutant sur des états d’appareil réels ou simulés, et pas seulement des assertions au niveau unitaire
- Vérifier que les tests d’interface utilisent des identifiants d’accessibilité stables et uniques, plutôt que des requêtes positionnelles comme « le troisième bouton de la deuxième section », qui se cassent dès que la mise en page change
Qualité des tests :
- Examiner les noms des tests pour confirmer qu’ils décrivent le comportement attendu, par exemple test_login_withExpiredToken_shouldRedirectToLoginScreen() plutôt que testLogin()
- Confirmer qu’aucun test n’est désactivé de manière permanente avec .skip ou XCTSkip sans raison documentée et suivie pour cette exclusion
- Vérifier que le pipeline CI exécute l’intégralité de la suite de tests sur chaque pull request et bloque les fusions en cas d’échec des tests
Rapport de plantages et observabilité
Les tests vous indiquent ce que fait le code dans un environnement contrôlé. Cependant, il vous faut des rapports de plantages et de l’observabilité pour savoir ce qu’il fait dans le monde réel, sur de vrais appareils, dans des conditions réelles.
Rapport de plantages :
- Confirmer qu’un SDK de rapport de plantages est intégré (Firebase Crashlytics, Sentry, ou équivalent) et que les fichiers dSYM sont téléversés automatiquement pour chaque build de release, afin que les plantages soient symbolisés et lisibles
- Vérifier que le SDK de rapport de plantages lui-même n’introduit pas de violations du privacy manifest. Plusieurs SDK populaires ont ajouté, dans leurs versions récentes, des API à raison requise devant être déclarées dans PrivacyInfo.xcprivacy
- Vérifier que les erreurs non fatales et les états inattendus sont journalisés comme des événements non fatals, et pas seulement les plantages. Un échec silencieux est plus difficile à déboguer qu’un plantage
Métriques Xcode Organizer et App Store Connect :
- Examiner les rapports de plantages de l’application dans Xcode Organizer avant toute revue d’une application en production. Ils regroupent les journaux de plantages symbolisés provenant d’utilisateurs réels, et constituent le moyen le plus rapide d’identifier les principaux problèmes en production
- Vérifier le Hang Rate et le Scroll Hitch Rate de l’application dans Organizer. Apple signale les applications situées dans les 25 % les moins performantes de leur catégorie, et ces métriques influencent directement la visibilité sur l’App Store
- Examiner les diagnostics Disk Writes. Des E/S disque excessives sont un problème courant dans les applications Core Data qui enregistrent à chaque frappe
- Vérifier le percentile de consommation de la batterie par rapport aux applications comparables. C’est la référence de base pour tout audit énergétique, disponible sans même exécuter Instruments
Conformité App Store et confidentialité
Apple a examiné 7,77 millions de soumissions en 2024, dont près de deux millions ont été rejetées. Les équipes qui réussissent dès la première soumission traitent les directives de l’App Store comme des exigences d’ingénierie, et non comme une réflexion après coup.
Privacy manifest et déclarations de données :
- Confirmer qu’un fichier Privacy Manifest (PrivacyInfo.xcprivacy) existe et déclare avec précision toutes les API utilisées, les données collectées et les raisons de leur usage. Apple applique cette exigence à tous les SDK tiers intégrés à l’application, ainsi qu’à l’application elle-même
- Vérifier que l’étiquette de confidentialité (privacy nutrition label) d’App Store Connect reflète fidèlement le comportement réel de collecte de données de l’application, car les écarts entre le comportement déclaré et le comportement réel sont une cause majeure de retrait
- Examiner tous les appels aux API sensibles en matière de confidentialité : localisation, appareil photo, microphone, contacts, photos, santé et suivi. Chacune doit disposer, dans Info.plist, d’une chaîne d’usage claire, visible par l’utilisateur, expliquant en langage simple pourquoi la permission est nécessaire
Moment de la demande de permission :
- Examiner le moment où sont émises les demandes de permission. Chaque permission devrait être demandée au moment où l’utilisateur effectue une action qui la nécessite, et non au lancement
- Afficher une brève explication dans l’application juste avant l’invite système, afin que l’utilisateur comprenne pourquoi la permission est nécessaire avant l’apparition de la boîte de dialogue système
- Une chaîne de description d’usage dans Info.plist est requise, mais elle ne remplace pas un bon moment de demande
Achats intégrés et entitlements :
- Confirmer que tout contenu numérique ou toute fonctionnalité déverrouillée dans l’application passe par le système d’achats intégrés d’Apple. Le contourner avec des liens de paiement externes constitue une violation des directives, entraînant un rejet et un retrait potentiel
- Vérifier qu’un bouton « Restaurer les achats » est accessible aux utilisateurs et qu’il fonctionne de manière fiable, car les examinateurs d’Apple testent explicitement cette fonction
- Vérifier que les entitlements déclarés dans l’application correspondent à ceux activés dans la configuration d’App Store Connect, car toute divergence provoque des échecs de signature
Suppression de compte :
- Confirmer que toute application proposant la création d’un compte offre également un moyen de supprimer ce compte directement dans l’application. C’est une exigence stricte depuis juin 2023, vérifiée à chaque revue
Accessibilité et localisation
Une application utilisée par le plus grand nombre est conçue dès le départ en tenant compte de l’accessibilité et de la localisation, et non ajoutées en dernière étape avant la mise en production.
Accessibilité :
- Vérifier que tous les éléments interactifs disposent de libellés d’accessibilité pertinents. Un bouton avec une icône mais sans libellé est invisible pour les utilisateurs de VoiceOver
- Vérifier que l’application respecte le réglage Dynamic Type du système, et que le texte s’adapte correctement sans casser la mise en page
- Confirmer que la couleur n’est jamais le seul moyen de transmettre une information. Les utilisateurs daltoniens doivent pouvoir comprendre tous les états et messages également via des libellés ou des icônes
- Vérifier que la taille minimale des zones tactiles respecte la recommandation des Human Interface Guidelines d’Apple, soit au moins 44 par 44 points
Localisation :
- Confirmer que toutes les chaînes visibles par l’utilisateur sont enveloppées dans NSLocalizedString, et qu’aucune n’est codée en dur directement dans les fichiers source
- Vérifier l’exhaustivité du fichier Localizable.strings ; les traductions manquantes basculent silencieusement vers la langue de développement, ce qui peut produire des écrans mélangeant plusieurs langues de façon confuse
- Vérifier que le formatage des dates, heures, devises et nombres utilise des formateurs sensibles à la locale plutôt que des chaînes de format fixes. Une date au format « MM/JJ/AAAA » ne signifie pas la même chose en Europe qu’aux États-Unis
Droite-à-gauche et pseudo-localisation
- Vérifier que l’application gère correctement les mises en page de droite à gauche pour les marchés utilisant l’arabe, l’hébreu ou le persan. Utiliser NSTextAlignment.natural ainsi que des contraintes Auto Layout leading/trailing plutôt que left/right
- Vérifier que le formatage des chaînes utilise String(format:locale:) plutôt que String(format:) seul lorsque le résultat est destiné à l’utilisateur
- Confirmer que l’application a été testée avec la pseudo-localisation afin de détecter les libellés tronqués et les hypothèses de mise en page codées en dur, avant que les véritables traductions ne soient livrées. Xcode prend cela en charge nativement dans les réglages de scheme
Pourquoi confier votre revue de code iOS à Redwerk
La plupart des problèmes de qualité sur iOS ne sont pas des mystères surgis de nulle part. Ce sont les mêmes schémas qui reviennent d’une base de code à l’autre :
- Des cycles de rétention que personne n’a profilés
- Des secrets du Keychain remplacés par UserDefaults
- Des mises à jour de l’UI déclenchées hors du thread principal
- Des soumissions à l’App Store envoyées sans vérifier le privacy manifest
Nous avons essayé de tous les couvrir dans cette liste de contrôle. Cependant, la parcourir ne vous mènera que jusqu’à un certain point. La vraie question est de savoir si votre équipe a le temps et le recul nécessaires pour la suivre honnêtement avant que les enjeux ne deviennent trop importants.
Vous pouvez voir un exemple concret de l’importance de tout cela dans notre propre pratique. Découvrez Gooroo, une plateforme de tutorat iOS que nous avons développée de A à Z en Swift. L’application mettait en relation tuteurs et étudiants à travers New York, et est devenue fournisseur officiel du NYC Department of Education. Elle a également obtenu une note de 5 étoiles sur l’App Store et s’est classée troisième sur Product Hunt.
Cependant, pour atteindre ce niveau de reconnaissance, nous avons dû nous assurer que le code était irréprochable à chaque niveau :
- Une architecture MVVM propre
- Une gestion de la mémoire tenant la charge réelle des utilisateurs
- Une gestion de la confidentialité ayant satisfait les examinateurs de l’App Store dès la première soumission
- Une suite de tests permettant à l’équipe d’itérer rapidement sans régressions
Un tel résultat ne s’obtient pas sans un engagement total et un souci du détail. Cela vient du fait de considérer la qualité du code comme une fonctionnalité à part entière, et non comme une tâche à reporter après le lancement.
Notre équipe livre et audite des applications iOS depuis plus de 20 ans. Nous couvrons l’architecture, l’analyse mémoire, le renforcement de la sécurité, la conformité App Store et la couverture de tests. Vous obtenez un rapport précis, hiérarchisé, rédigé aussi bien pour les décideurs que pour les développeurs. Nous ne fournissons jamais d’observations vagues ni de recommandations génériques.
Si vous souhaitez avoir une vision claire de l’état réel de votre base de code iOS, notre service de revue de code est le point de départ idéal. Parlons-en de ce que vous avez construit, et nous vous dirons exactement ce que nous trouvons.
Découvrez de vrais résultats de revue de code : plus de 80 améliorations et risques de sécurité identifiés pour une marketplace mobile