Rendre la cible vraiment « spécifique à MAS »
En Partie 1, nous avons enregistré un Bundle ID exclusif MAS et dupliqué la cible de build FocusTimer MAS. Mais cette cible n’est encore qu’une copie de la cible de distribution directe.
Un build MAS doit différer du build de distribution directe sur trois points.
- Entitlements (droits d’accès) — uniquement l’ensemble minimum approprié pour l’App Store
- Info.plist — supprimer les clés Sparkle, ajouter les métadonnées App Store
- Code — brancher pour qu’il compile même sans Sparkle
Dans cet article, nous séparerons les trois.
Comme en Partie 1,
FocusTimer,com.example.FocusTimer.mas, etc. sont tous des valeurs d’exemple.
Étape 1 — Le fichier entitlements exclusif MAS
Les entitlements constituent un fichier qui liste les permissions système qu’une application demande. Dans le build de distribution directe, Sparkle avait besoin de permissions supplémentaires pour installer des mises à jour, mais le build MAS n’a pas Sparkle, donc ces permissions sont inutiles.
Créez un nouveau fichier FocusTimer-MAS.entitlements à la racine du projet.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
Comparé au FocusTimer.entitlements de distribution directe, voici comment il diffère.
- Pas de permissions liées à Sparkle — Les entrées
temporary-exception.mach-lookup.*du build de distribution directe (l’exception qui permet à Sparkle de communiquer avec le helper d’installation) n’ont aucune raison d’être dans le build MAS. - Pas de
network.client— Avec Sparkle supprimé, l’application FocusTimer elle-même n’effectue aucun appel réseau. Il est honnête de supprimer les permissions que vous n’utilisez pas, et les permissions inutiles sont également signalées lors de la validation. Plus la surface de permissions qu’une application demande est petite, mieux c’est.
L’exemple ci-dessus est la forme la plus simple possible, avec juste assez de permission pour « lire et écrire des fichiers que l’utilisateur sélectionne explicitement ». Si votre application utilise réellement le réseau ou d’autres ressources, ajoutez les permissions correspondantes, mais n’incluez jamais des permissions que vous n’utilisez pas.
Étape 2 — Le fichier Info.plist exclusif MAS
De même, créez un nouveau FocusTimer-MAS-Info.plist à la racine du projet.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict>
</plist>
Il y a deux différences par rapport au FocusTimer-Info.plist de distribution directe.
① Toutes les clés Sparkle SU* sont supprimées
Le Info.plist de distribution directe contenait des clés de configuration Sparkle comme SUFeedURL et SUPublicEDKey. Le Info.plist MAS ne doit contenir aucune de ces clés. La validation de l’App Store interdit les mises à jour automatiques intégrées, donc même une clé qui suggère simplement une telle fonctionnalité restant dans le plist peut poser problème. Il est plus sûr de ne pas inclure la clé du tout.
② Les clés de métadonnées App Store sont ajoutées
LSApplicationCategoryType— la catégorie à laquelle appartient l’application sur l’App Store. Lepublic.app-category.productivitydans l’exemple ci-dessus signifie la catégorie « Productivité ». Cette valeur doit correspondre à la catégorie que vous définissez dans App Store Connect en Partie 3, pour qu’un avertissement de non-concordance n’apparaisse pas lors de la validation.ITSAppUsesNonExemptEncryption— si l’application utilise une technologie de chiffrement soumise à des réglementations d’exportation. Si vous utilisez uniquement du chiffrement standard (par ex., HTTPS, API système standard) ou pas de chiffrement du tout, vous pouvez mettrefalse. Coder cette clé en dur à l’avance permet de passer automatiquement le questionnaire sur le chiffrement qui apparaît à chaque fois que vous téléversez un build.
Avant de mettre
ITSAppUsesNonExemptEncryptionàfalse, vérifiez que votre application n’utilise vraiment pas de chiffrement non standard. Si vous n’êtes pas sûr, il est plus sûr de consulter la documentation d’Apple sur le chiffrement.
Étape 3 — Paramètres de build de la cible MAS
Voici maintenant la mise à jour des paramètres de build que nous avons reportée en Partie 1, Section 2-3. Dans TARGETS → FocusTimer MAS → Build Settings, alignez les points suivants.
| Clé de paramètre de build | Valeur |
|---|---|
PRODUCT_BUNDLE_IDENTIFIER | com.example.FocusTimer.mas |
INFOPLIST_FILE | FocusTimer-MAS-Info.plist |
CODE_SIGN_ENTITLEMENTS | FocusTimer-MAS.entitlements |
ENABLE_APP_SANDBOX | YES |
ENABLE_HARDENED_RUNTIME | YES |
ENABLE_USER_SELECTED_FILES | readwrite |
CODE_SIGN_STYLE | Automatic |
MARKETING_VERSION, CURRENT_PROJECT_VERSION | Identiques à la cible de distribution directe |
Les points clés sont les suivants.
INFOPLIST_FILEetCODE_SIGN_ENTITLEMENTSdoivent pointer vers les fichiers exclusifs MAS que vous venez de créer. Une erreur courante consiste à laisser ces deux lignes pointant vers les fichiers de distribution directe.- Définir
CODE_SIGN_STYLEsurAutomaticpermet à Xcode d’émettre et de faire correspondre automatiquement le profil de provisionnement Mac App Store et le certificat Apple Distribution. Vous n’avez pas besoin de gérer la signature vous-même. - Si votre application n’utilise pas le réseau, ne définissez pas
ENABLE_OUTGOING_NETWORK_CONNECTIONS. C’est le même raisonnement que la suppression denetwork.clientdes entitlements. - Gardez les numéros de version (
MARKETING_VERSION, etc.) aux mêmes valeurs que la cible de distribution directe, pour que les versions des deux canaux ne divergent pas.
Étape 4 — Brancher le code Sparkle (#if canImport(Sparkle))
Même après avoir séparé les fichiers de configuration, un problème demeure. Quelque part dans le code source, il y a import Sparkle et du code qui utilise Sparkle, mais Sparkle n’est pas lié dans la cible MAS, donc l’instruction import elle-même produit une erreur de compilation.
La solution consiste à envelopper le code lié à Sparkle avec la directive de compilation conditionnelle Swift #if canImport(Sparkle).
Pourquoi canImport — Pourquoi ne pas utiliser un flag séparé
Vous pourriez également brancher avec un flag de compilation personnalisé comme #if MAS_BUILD. Mais vous devriez alors maintenir manuellement le réglage « activer le flag pour la cible MAS, désactiver pour la cible de distribution directe ». C’est facile à désynchroniser à mesure que les cibles se multiplient ou que les paramètres changent.
canImport(Sparkle) est différent. Il fait vérifier directement par le compilateur si « Sparkle.framework est lié dans cette cible ». Puisque nous avons supprimé Sparkle des dépendances de packages de la cible MAS en Partie 1, canImport(Sparkle) est automatiquement faux dans la cible MAS. Il n’y a pas de flag séparé à maintenir synchronisé. C’est pourquoi les applications macOS utilisent ce modèle comme norme de facto lors de la séparation d’un canal MAS.
Branchement ① — Envelopper entièrement les fichiers exclusifs Sparkle
Un fichier qui contient uniquement la logique de mise à jour automatique (par ex., UpdaterCoordinator.swift) reçoit l’intégralité du fichier enveloppée dans #if.
#if canImport(Sparkle)
import Foundation
import Sparkle
import Combine
@Observable
@MainActor
final class UpdaterCoordinator {
// Code wrapping the Sparkle updater …
}
#endif
Dans le build MAS, ce fichier compile comme s’il était vide et n’a aucun effet.
Branchement ② — Envelopper également les utilisateurs de Sparkle
Le code qui crée UpdaterCoordinator et le passe à l’interface utilisateur doit également être branché. Sinon le build MAS ferait référence à « un type qui n’existe pas ».
@main
struct FocusTimerApp: App {
@State private var viewModel = CompositionRoot.makeContentViewModel()
#if canImport(Sparkle)
@State private var updater = UpdaterCoordinator()
#endif
var body: some Scene {
Settings {
#if canImport(Sparkle)
PreferenceView(viewModel: viewModel, updater: updater)
#else
PreferenceView(viewModel: viewModel)
#endif
}
MenuBarExtra {
#if canImport(Sparkle)
MenuBarContent(updater: updater)
#else
MenuBarContent()
#endif
} label: {
// Menu bar icon …
}
}
}
private struct MenuBarContent: View {
#if canImport(Sparkle)
@Bindable var updater: UpdaterCoordinator
#endif
var body: some View {
Button("Show FocusTimer") { /* … */ }
#if canImport(Sparkle)
Button("Check for Updates…") { updater.checkForUpdates() }
.disabled(!updater.canCheckForUpdates)
#endif
// Other shared menu items …
}
}
Il y a trois points clés.
- La déclaration du champ
updaterelle-même est enveloppée dans#if. - Tout vue qui reçoit
updater(PreferenceView,MenuBarContent) est branchée en deux formes — une pour quand Sparkle est présent et une pour quand il ne l’est pas. - Les éléments d’interface utilisateur exclusifs Sparkle comme « Check for Updates… » sont également enveloppés dans
#if. Dans le build MAS, cet élément de menu n’apparaît pas du tout — et c’est juste, l’édition App Store ne devrait pas avoir de menu de mise à jour automatique.
Les éléments tels que la bascule « Activer les mises à jour automatiques » dans l’écran de paramètres (PreferenceView) doivent tous être enveloppés avec le même modèle.
Récapitulatif de la Partie 2
Si vous avez suivi jusqu’ici, vous disposez maintenant des éléments suivants :
- ✅ Un fichier entitlements exclusif MAS
FocusTimer-MAS.entitlements(permissions minimales) - ✅ Un
FocusTimer-MAS-Info.plistexclusif MAS (clés Sparkle supprimées, métadonnées App Store ajoutées) - ✅ Les paramètres de build de la cible MAS réglés
- ✅ Le code de mise à jour automatique branché avec
#if canImport(Sparkle)
Maintenant la cible FocusTimer MAS est vraiment différente de la cible de distribution directe — elle est dans une forme qui peut être mise sur l’App Store. Il reste à mettre en place le chemin pour téléverser réellement ce build vers App Store Connect.
Dans la prochaine partie, nous conclurons la série en créant un ExportOptions-MAS.plist pour le téléversement, en enregistrant un enregistrement d’application dans App Store Connect, et en expliquant comment vérifier que les deux canaux restent fonctionnels.