Le parcours du combattant App Store : la préparation

« Je croyais que le plus dur, c’était de coder l’application. »

Le binaire était prêt. Je me suis dit que le gros du travail était fait.

Tous les développeurs se font cette illusion. Une seule fois.

Ce qu’Apple exige vraiment

Soumettre sur le Mac App Store, ce n’est pas envoyer un zip. La liste des prérequis est longue, et certains sont loin d’être évidents.

Le manifeste de confidentialité. Apple impose désormais un fichier PrivacyInfo.xcprivacy pour toute application qui utilise certaines API système. C’est un plist structuré déclarant quelles API sont utilisées et dans quel but. Les API d’accès aux fichiers, par exemple, exigent un code de justification prédéfini par Apple. On choisit celui qui correspond à son usage réel. Ce n’est pas une formalité : le réviseur vérifie la cohérence. Une erreur ici, c’est un rejet direct.

%%{init: {"theme": "default"}}%%
graph TD
    PRIV["PrivacyInfo.xcprivacy"] --> APIS["API à justification obligatoire<br/>(accès fichiers, user defaults)"]
    PRIV --> DATA["Types de données collectées<br/>(aucune collecte)"]
    PRIV --> SDK["Politiques SDK tiers<br/>(StoreKit : aucune donnée collectée)"]
    APIS --> CODES["Codes de justification Apple<br/>ex. C617.1, CA92.1"]

Les captures d’écran. Cinq captures, pile à 2880x1800 pixels. Sans écran 5K, impossible de les capturer nativement. J’ai utilisé un outil de composition à la bonne résolution. Chaque capture raconte un morceau de l’histoire : lancement d’un scan, treemap en détail, navigation dans la barre latérale, détection de doublons, parcours de nettoyage. Les captures d’écran, c’est un argumentaire commercial, pas de la documentation.

Les métadonnées. Description App Store, sous-titre, mots-clés (100 caractères, séparés par des virgules, pas d’espace après les virgules, pas de redondance avec le nom de l’application). Une URL de politique de confidentialité. Une URL de support. Une catégorie. Une tranche d’âge. L’essentiel est du remplissage, mais le champ mots-clés demande de la réflexion : ces 100 caractères influencent directement le référencement sur l’App Store.

La boîte à pourboires

Renala est gratuite. Si quelqu’un veut payer, autant lui en donner la possibilité.

StoreKit 2 rend la chose simple. Un achat intégré non récurrent, chargé via Product.products(for:) avec un jeu d’identifiants produit. L’utilisateur voit les montants proposés, en choisit un, StoreKit gère la transaction. Pas de serveur. Pas de droit à vérifier.

L’implémentation tient en peu de lignes : une classe observable TipJarManager, une TipJarView qui charge les produits à l’affichage et les présente dans une liste. Pas d’abonnement, pas de bouton de restauration, pas de fenêtre modale.

Configurer les produits dans App Store Connect est une étape manuelle à part. Chaque produit nécessite un nom d’affichage, une description et un palier de prix. Et il faut qu’ils soient dans un certain état de validation avant d’être visibles dans certains contextes. Retenez bien cette phrase. Elle reviendra.

Le pipeline CI/CD

Faire tout ça à la main une fois, ça passe. Le refaire après une correction de bogue, c’est là que le processus manuel s’effondre.

Le pipeline se scinde en deux branches :

graph LR
    TAG[Tag de version Git] --> GH[GitHub Actions]
    GH --> BUILD[xcodebuild archive]
    BUILD --> SIGN[Signature avec<br/>Developer ID]
    SIGN --> NOTA[xcrun notarytool<br/>notarisation]
    NOTA --> STAPLE[Agrafage du ticket<br/>de notarisation]
    STAPLE --> DMG[Création DMG<br/>arm64 + x86_64]
    DMG --> REL[GitHub Release]

    GH --> ABUILD[xcodebuild archive<br/>méthode App Store]
    ABUILD --> PKG[productbuild .pkg]
    PKG --> SUBMIT[xcrun altool<br/>soumission]
    SUBMIT --> REVIEW[Validation App Store]

La branche de gauche sert à la distribution directe : signature Developer ID, notarisation, création des DMG (une compilation par architecture, arm64 et x86_64), publication en tant que GitHub Release. La branche de droite vise l’App Store : archivage avec la méthode de distribution App Store, empaquetage en .pkg via productbuild, soumission via xcrun altool.

En local, le Makefile expose les mêmes opérations : make dmg-arm64, make dmg-x86_64, make notarize. Les identifiants sont lus depuis un fichier .env qui n’est pas versionné.

Le chemin App Store passe précisément par ces étapes :

%%{init: {"theme": "default"}}%%
graph LR
    BUILD["xcodebuild archive"] --> SIGN["Signature avec<br/>Developer ID ou<br/>certificat App Store"]
    SIGN --> NOTA["xcrun notarytool<br/>soumission"]
    NOTA --> STAPLE["Agrafage du ticket<br/>de notarisation"]
    STAPLE --> UPLOAD["xcrun altool<br/>téléversement"]
    UPLOAD --> REVIEW["Validation App Store"]
    REVIEW --> APPROVED["Approuvé :<br/>Prêt à la vente"]
    REVIEW --> REJECTED["Rejeté :<br/>Violation des directives<br/>+ journal de plantage"]

La notarisation, qu’est-ce que c’est

Quand un utilisateur télécharge votre application et double-clique dessus, macOS vérifie si Apple a analysé le binaire et l’a approuvé. Cette analyse, c’est la notarisation.

On soumet le binaire signé au service de notarisation d’Apple. S’il passe, Apple renvoie un ticket. On « agrafe » ce ticket au binaire, ce qui l’y intègre. Gatekeeper peut alors vérifier l’application hors ligne, sans contacter les serveurs d’Apple. Sans agrafage, un Mac sans réseau affiche un avertissement au lieu de lancer l’application.

C’est obligatoire pour tout logiciel Mac distribué hors de l’App Store. Pour les logiciels du store, Apple s’en charge dans le cadre de la validation.

Les pièges

Deux choses m’ont coincé sur le chemin de l’empaquetage App Store.

Premièrement : xcodebuild -exportArchive avec method: app-store vérifie que le certificat de signature correspond au profil de provisionnement. Cette vérification est plus stricte que la signature Developer ID. La parade : contourner l’étape d’export et appeler directement productbuild --component <app> /Applications --sign "3rd Party Mac Developer Installer: ...". On obtient le .pkg qu’attend xcrun altool.

Deuxièmement : l’authentification xcrun altool. Le drapeau s’appelle --username, pas --apple-id. Le message d’erreur quand on se trompe de drapeau n’aide pas beaucoup. C’est le genre de détail qui coûte vingt minutes la première fois et zéro ensuite, donc personne ne le note, et donc ça coûte toujours vingt minutes la première fois.

Ce que le pipeline apporte

Une fois les workflows en place, un tag de version déclenche tout : compilation, signature, notarisation, empaquetage, soumission. C’est surtout précieux après un rejet. On corrige le problème, on incrémente la version, on tague, on laisse le pipeline tourner. Faire confiance au pipeline, c’est savoir que le correctif a bien été livré, pas « je crois avoir suivi toutes les étapes ».

Le pipeline de soumission est aussi complexe qu’une petite application à part entière. Il faut l’automatiser avant d’en avoir besoin. On en aura besoin.

References