← Tous les articles

canOpenURL est déprécié : ce qu'il faut appeler à la place

Apple a mis à la retraite en trois phrases une méthode livrée depuis iOS 3.0 : « canOpenURL: est déprécié. Tentez d’ouvrir l’URL et gérez l’échec éventuel plutôt que de la valider au préalable. Utiliser des liens universels plutôt que des schémas d’URL personnalisés supprime entièrement le besoin de cette validation. »1

L’entrée porte le radar 179874781 et figure sous UIKit, Deprecations, juste en dessous de l’obligation de cycle de vie par scène qui empêche les applications de se lancer.1 L’entrée voisine nomme une conséquence. Celle de canOpenURL n’en nomme aucune.

Chacune des trois phrases est une consigne distincte, et seule la deuxième répond à la question que se pose réellement un développeur : qu’est-ce que j’écris à la place ? La réponse d’Apple change la forme de l’appel, pas seulement son nom.

TL;DR

  • Apple a déprécié canOpenURL(_:) en 27.0 sur iOS, iPadOS, Mac Catalyst, tvOS et visionOS, avec le message « Préférez tenter d’ouvrir les URL et gérer les échecs éventuels ».2 Aucune version de suppression, aucune conséquence à l’exécution.
  • Le changement qui mord est un chiffre, et il se cache dans la discussion de la méthode dépréciée plutôt que dans une note de version : « Les applications liées à partir d’iOS 27 sont limitées à un maximum de 25 entrées dans la clé LSApplicationQueriesSchemes », contre 50 auparavant.2 Le déclencheur, c’est le SDK avec lequel vous liez votre application.
  • Le remplacement mécanique, c’est open(_:options:completionHandler:) et son booléen, qui n’exige aucune entrée dans la liste d’autorisation : « la méthode open(_:options:completionHandler:) n’est pas soumise à l’exigence LSApplicationQueriesSchemes ».2
  • Un usage perd son remplaçant : canOpenURL répond avant que vous n’affichiez quoi que ce soit, alors que la tentative répond par ses conséquences, puisqu’une réussite met une autre application au premier plan.2 Ce qui survit, c’est universalLinksOnly, une option d’open présente depuis iOS 10 et qu’Apple n’a pas dépréciée : elle « n’ouvre l’URL que s’il s’agit d’un lien universel valide et qu’une application installée est capable de l’ouvrir ».3
  • SwiftUI n’a jamais livré la méthode, et le booléen de sa closure de complétion signifie déjà « peut ouvrir » et non « a ouvert ».415 Sur sept de mes applications publiées et 463 fichiers Swift maison, canOpenURL et LSApplicationQueriesSchemes apparaissent zéro fois chacun.6

La dépréciation sans conséquence, et le chiffre qui en a une

Apple a écrit une dépréciation, pas une suppression. La page du symbole porte deprecatedAt 27.0 sur les cinq plateformes où UIApplication existe, et le résumé, la discussion, le contrat de valeur de retour et la déclaration restent tous en place.2 Aucune page d’Apple ne nomme la version où la méthode cessera de fonctionner, et le précédent le plus proche pointe dans l’autre sens : openURL(_:), dépréciée en iOS 10.0, a toujours sa page, dont la note indique désormais « Appeler cette méthode n’a aucun effet ».7 Dix ans de dépréciation ont produit une méthode inerte plutôt qu’absente : c’est un précédent, pas un calendrier.

L’annonce est par ailleurs plus étroite que la dépréciation. La note de version se trouve sur une page qu’Apple intitule « iOS & iPadOS 27 Beta 4 Release Notes » et n’apparaît nulle part ailleurs, alors que les métadonnées du symbole déprécient tout autant la méthode sur tvOS et visionOS, et que ni la page des nouveautés UIKit de juin 2026 ni les notes de version de Xcode 27 n’en soufflent mot.1289 La page du symbole est ici la moitié durable du dossier.

Ce qui fait du chiffre enfoui la véritable information. Dans la discussion de la méthode dépréciée elle-même, dans le même encadré qui impose la déclaration à l’origine : « Les applications liées à partir d’iOS 15 sont limitées à un maximum de 50 entrées dans la clé LSApplicationQueriesSchemes. Les applications liées à partir d’iOS 27 sont limitées à un maximum de 25 entrées dans la clé LSApplicationQueriesSchemes. »2

La liste d’autorisation survit à la dépréciation de la seule méthode qu’elle sert, et le plafond est divisé par deux pour les applications liées au nouveau SDK. Apple énonce le plafond et s’arrête là. Rien ne dit ce qui se passe au-delà de la 25e entrée, et lire ensemble les deux moitiés de l’encadré donne une réponse probable, pas une réponse documentée : les schémas non déclarés renvoient toujours false, donc une application qui en déclare 40 et qu’on relie au nouveau SDK traitera vraisemblablement 15 d’entre eux comme absents. Apple ne dit jamais lesquels, ni même que la troncature suit l’ordre du tableau. Considérez le mécanisme comme une déduction et le risque comme réel, car un false a exactement la même allure, que ce soit l’application ou la déclaration qui manque.

Le paragraphe suivant de la même discussion porte une seconde limite, et les deux sont des alternatives plutôt qu’un cumul. Apple les indexe sur le SDK avec lequel vous avez lié votre application : « Si vous liez votre application avec une version antérieure d’iOS mais qu’elle s’exécute sous iOS 9.0 ou ultérieur, vous pouvez appeler cette méthode jusqu’à 50 fois. Une fois cette limite atteinte, les appels suivants renvoient toujours false. Si l’utilisateur réinstalle ou met à jour l’application, iOS réinitialise la limite. »2 Lisez la condition. Ce budget appartient aux applications liées avec une version antérieure à iOS 9, ce que plus personne ne peut publier aujourd’hui. Toutes les applications actuelles relèvent de l’autre branche, le plafond de déclaration, celui-là même qu’Apple a ramené de 50 entrées à 25. Les deux s’épuisent dans le même false silencieux, et les deux ne sont documentés qu’à l’intérieur de la méthode qu’Apple vient de déprécier.

La lecture « vie privée » relève elle aussi de la déduction. canOpenURL était le moyen standard de détecter quelles applications une personne avait installées, un signal d’appareil plutôt qu’une vérification de lien, et la définition qu’Apple donne du fingerprinting couvre les API « détournées pour accéder à des signaux de l’appareil afin de tenter d’identifier l’appareil ou l’utilisateur ».10 Apple n’établit jamais le lien : la méthode ne figure sur aucune liste d’API à motif obligatoire, et la note de version, le message de dépréciation et la page du symbole donnent tous une justification purement mécanique.1210 Un plafond de requêtes divisé par deux colle au récit de la vie privée, et Apple ne l’a pas écrit.

Ce que canOpenURL promettait vraiment

Un true était une garantie sur l’appel suivant, pas une description de l’URL : « Lorsque cette méthode renvoie true, iOS garantit que les appels suivants à la méthode open(_:options:completionHandler:) avec la même URL lanceront avec succès une application capable de traiter cette URL. »2 Un false était ambigu par construction, avec deux causes et aucun moyen de savoir laquelle s’était déclenchée : « false si l’appareil n’a pas d’application installée enregistrée pour traiter le schéma de l’URL, ou si vous n’avez pas déclaré le schéma de l’URL dans votre fichier Info.plist ».2

Trois choses auxquelles elle n’a jamais répondu, et qu’on lui prête volontiers : « La valeur de retour n’indique ni la validité de l’URL, ni l’existence de la ressource spécifiée, ni, dans le cas d’un lien universel, si l’appareil dispose d’une application installée enregistrée pour répondre à ce lien universel. »2 La troisième proposition compte pour la migration recommandée par Apple, et elle revient plus bas.

La propriété qui rendait la méthode structurellement utile est justement celle que rien ne remplace. canOpenURL porte le mot-clé nonisolated dans sa déclaration, et Apple l’écrit sans détour : « vous pouvez appeler cette méthode en toute sécurité depuis un thread autre que le thread principal ».2 Un booléen synchrone disponible hors du main actor peut conditionner une décision de mise en page avant tout rendu.

Tenter et gérer : la forme qui change

La consigne de remplacement d’Apple tient en une proposition, et l’avant/après paraît trivial.

// Before: validate, then open. Requires an Info.plist declaration.
let url = URL(string: "someapp://profile/42")!
if UIApplication.shared.canOpenURL(url) {
    UIApplication.shared.open(url)
} else {
    presentWebFallback()
}
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>someapp</string>
</array>
// After: attempt, then handle. No declaration needed.
let url = URL(string: "someapp://profile/42")!
let opened = await UIApplication.shared.open(url)
if !opened {
    presentWebFallback()
}

L’entrée Info.plist disparaît, et Apple le dit directement : « Contrairement à cette méthode, la méthode open(_:options:completionHandler:) n’est pas soumise à l’exigence LSApplicationQueriesSchemes. Si une application est disponible pour traiter l’URL, le système la lancera, même si vous n’avez pas déclaré le schéma. »2 Une base de code qui migre entièrement supprime la clé, et avec elle le problème des 25 entrées.

Deux différences survivent à la réécriture, et toutes deux sont structurelles.

La première tient à l’isolation et au moment de l’appel. La déclaration d’UIApplication s’écrit @MainActor class UIApplication, donc open s’exécute sur le main actor, et son résumé appelle un chat un chat : « Tente d’ouvrir de façon asynchrone la ressource à l’URL spécifiée. »1112 Le booléen synchrone, appelable hors du thread principal, disparaît : toute logique de validation préalable installée dans une couche de modèle, une file d’attente en arrière-plan ou un utilitaire non isolé doit déménager ou devenir async.

La seconde, c’est qu’on ne peut plus poser la question discrètement. Quand la tentative réussit, une autre application passe au premier plan : « iOS lance cette application et lui transmet l’URL. (Lancer l’application amène l’autre application au premier plan.) »12 La réponse arrive comme effet de bord de l’action. Apple ne documente rien de visible pour le cas d’échec, seulement que « le gestionnaire de complétion est appelé avec le paramètre success à false », mais tous les usages qui avaient besoin de la réponse avant l’action la perdent : n’afficher une ligne « ouvrir dans une autre application » que si l’application est présente, trier une feuille de partage selon ce qui est installé, ou choisir une cible par défaut parmi plusieurs concurrentes.12

La documentation d’Apple elle-même n’a pas suivi. La page d’open(_:options:completionHandler:) continue d’indiquer : « Pour déterminer si une application capable de traiter l’URL est installée, appelez la méthode canOpenURL(_:) avant celle-ci. »12 La page du remplaçant recommande la méthode dépréciée.

La vérification de présence qui survit

Un chemin documenté répond à la question de canOpenURL en tentant l’ouverture, sans rien montrer à l’utilisateur quand la réponse est non. UIApplication.OpenExternalURLOptionsKey.universalLinksOnly existe depuis iOS 10, Apple ne l’a pas dépréciée, et son comportement est exactement une vérification de présence : « la méthode n’ouvre l’URL que s’il s’agit d’un lien universel valide et qu’une application installée est capable de l’ouvrir ».3

// Presence check with no side effect when the app is absent.
let url = URL(string: "https://myphotoapp.example.com/albums?albumname=vacation")!
let installed = await UIApplication.shared.open(
    url,
    options: [.universalLinksOnly: true]
)
if !installed {
    presentWebFallback()   // nothing opened, nothing switched
}

La branche false est ce qui a de la valeur. Aucun navigateur lancé, aucune application au premier plan, et l’appelant a appris ce que canOpenURL lui disait autrefois. Le compromis se cache dans le nom de l’option : sans elle, une tentative en https réussit dès qu’un navigateur peut la prendre en charge, puisque « si aucune application n’est disponible pour traiter un lien universel, iOS l’achemine vers le navigateur par défaut de la personne, ce qui permet au site associé de répondre ».2 L’option achète un booléen porteur de sens en supprimant le repli qui rend les liens universels agréables. Apple documente la valeur comme « un objet NSNumber contenant une valeur booléenne », ce qu’un true Swift satisfait via le pont Objective-C.3

Le prix est architectural, et c’est la troisième phrase d’Apple. Les liens universels exigent une association bidirectionnelle : « Lorsque quelqu’un installe votre application, le système vérifie un fichier stocké sur votre serveur web pour confirmer que votre site autorise votre application à ouvrir des URL en son nom. Vous seul pouvez stocker ce fichier sur votre serveur, ce qui sécurise l’association entre votre site et votre application. »13 Un fichier serveur que vous contrôlez, c’est précisément ce qu’un schéma personnalisé n’a jamais exigé, et c’est pourquoi la migration fonctionne pour votre propre famille d’applications et ne donne rien face à un tiers qui ne publie que theirapp://. Deux surprises documentées s’invitent au passage : votre application qui ouvre son propre lien universel n’est pas redirigée vers elle-même, et un appui sur un lien du même domaine pendant que vous naviguez sur votre site dans Safari reste dans Safari.13

C’est là que retombe la troisième proposition évoquée plus haut : canOpenURL ne répondait pas non plus à la question de la présence pour un lien universel.2 Migrer supprime la détection de présence plutôt que de la porter, car une simple tentative en https réussit déjà, que ce soit l’application ou seulement le site qui ait répondu. Un usage qui ne fonctionnait que parce que les schémas personnalisés laissaient fuiter l’état d’installation s’en va avec les schémas, et la note de version d’Apple présente cette perte comme la raison même de migrer.

SwiftUI n’a jamais eu cette méthode

Du côté SwiftUI, c’est court et la nouvelle est bonne. EnvironmentValues.openURL, OpenURLAction et Link sont tous disponibles depuis iOS 14.0 sans dépréciation, et aucun des trois n’expose de vérification de validité.4514

Ce que SwiftUI livre à la place, c’est tenter puis gérer, avec la sémantique qu’Apple veut désormais partout, et la documentation du paramètre de complétion dit que le booléen répond à l’ancienne question : « Une closure que la méthode appelle après avoir déterminé si elle peut ouvrir l’URL, mais possiblement avant de l’avoir entièrement ouverte. La closure prend une valeur booléenne qui indique si la méthode peut ouvrir l’URL. »15 Peut ouvrir, pas a ouvert. L’exemple d’Apple lui-même l’affiche :

openURL(url) { accepted in
    print(accepted ? "Success" : "Failure")
}

Link n’expose aucun booléen et s’en remet à l’environnement, où le comportement par défaut met déjà en œuvre le scénario des liens universels : « l’action par défaut ouvre un Universal Link dans l’application associée si possible, sinon dans le navigateur web par défaut de l’utilisateur ».5 Une OpenURLAction personnalisée renvoyant .handled, .discarded ou .systemAction intercepte chaque Link et chaque lien markdown d’un Text qui lit l’action dans cet environnement.5

Une lacune compte avant de planifier une migration entièrement SwiftUI. OpenURLAction n’a pas de dictionnaire d’options. Ses signatures d’appel sont callAsFunction(_:), callAsFunction(_:completion:) et, ajoutée en iOS 26, callAsFunction(_:prefersInApp:), et aucune des trois n’accepte universalLinksOnly.1518 Une vérification de présence sans effet de bord passe toujours par un appel à UIApplication.shared.open.

Sept applications, 463 fichiers, zéro appel

J’ai audité mon propre portfolio avant d’écrire sur celui des autres, en m’attendant à une liste de migration. Il n’y a rien à migrer.6

Application Fichiers Swift canOpenURL LSApplicationQueriesSchemes Sites d’appel ouvrant une URL
Reps 77 0 0 5
Return 57 0 0 2
Banana List 55 0 0 2
Ace Citizenship 26 0 0 2
Water 34 0 0 0
ResumeGeni 71 0 0 12
Yawara 143 0 0 0
Total 463 0 0 23

Le zéro a survécu à trois passes d’élargissement du périmètre, jusqu’à chaque paquet Swift vendorisé et chaque type de fichier dans chaque dépôt.6 Aucun schéma interrogé n’est déclaré non plus dans les 192 fichiers .plist, .pbxproj, .entitlements et .xcconfig, ce qui se comprend : une application sans appel à canOpenURL n’a aucune raison d’en déclarer un.

La raison est banale et probablement répandue. Sur les 23 sites d’appel, neuf ouvrent un document juridique, quatre renvoient vers le produit web de l’application, deux ouvrent census.gov depuis une alerte pour qu’un utilisateur trouve son élu, deux ouvrent l’URL d’une offre d’emploi renvoyée par une API, deux sont des exports file: réservés à macOS, et un est UIApplication.openSettingsURLString derrière une alerte « Accès Santé requis ». Aucun de ces 20 appels n’interroge une autre application, puisque chaque destination est soit une URL https, à laquelle Safari répond toujours, soit une URL système. Les trois restants concentrent tout le comportement intéressant.

Le seul endroit qui valide avant d’ouvrir ne fait pas ce que vise la dépréciation : le flux de facturation de ResumeGeni envoie un POST vers /api/me/portal, puis vérifie url.scheme == "https" sur l’URL renvoyée par le serveur, au cas où un serveur compromis ou bogué transmettrait à l’application une URL file: ou à schéma personnalisé.6 La note d’Apple vise la validation de l’existence d’une application cible et ne dit rien de la confiance accordée à une entrée distante : supprimer ce garde-fou serait une régression de sécurité, pas une migration.

Les deux autres sont les transferts macOS de Banana List, et l’un d’eux abrite la seule véritable vérification de présence du portfolio. L’application demande NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anthropic.claudefordesktop") != nil avant de confier une extension .mcpb embarquée à Launch Services, un bouton voisin ouvrant claude.com/download pour ceux à qui l’application manque encore. Le commentaire du code en donne le motif : sans cette vérification, macOS affiche sa propre boîte de dialogue « no application set to open the document ».6 Autre plateforme, et la donnée la plus utile de l’audit, parce qu’elle attrape sur le terrain le mode d’échec du tenter-puis-gérer. Une tentative ratée n’est pas toujours silencieuse, et quand le système parle à votre place, l’utilisateur lit une erreur système au lieu de votre repli.

Aucun dépôt ne déclare applinks:, donc aucune application ici ne prend en charge les liens universels, et le correctif architectural d’Apple reste à payer de mon côté aussi.6 Un schéma personnalisé coûte une entrée Info.plist ; un lien universel coûte un fichier sur un serveur web, un entitlement et un domaine que vous contrôlez.

Ce qui indique où se trouve le vrai travail : pas dans le code des applications maison, mais dans les feuilles de partage qui se réordonnent selon les applications installées, les sélecteurs « ouvrir dans », et les SDK d’attribution, qu’un dépôt propre n’exclut nullement.

L’audit lui-même est plus facile ici qu’ailleurs dans le cycle 27. Contrairement aux clés d’écran de lancement, qui proviennent des réglages de build et déjouent complètement une recherche textuelle, LSApplicationQueriesSchemes n’a pas d’équivalent INFOPLIST_KEY_ dans la référence des réglages de build d’Apple : un grep est donc le bon premier réflexe, et les schémas présents dans ce tableau vous disent quelle dépendance veut la réponse.16

FAQ

canOpenURL cesse-t-elle de fonctionner sous iOS 27 ?

Non, et Apple n’a pas dit quand ce serait le cas. Le symbole porte deprecatedAt 27.0, et tout le reste de la page, y compris le contrat de valeur de retour, la garantie sur l’appel open suivant, le plafond de déclaration et le budget d’appels hérité, reste documenté.2 Aucune version de suppression n’apparaît dans la note de version, sur la page du symbole ni dans les notes de version de Xcode 27.129 Anticipez un avertissement et un lent délitement plutôt qu’une rupture.

Qu’est-ce que j’écris à la place si j’ai besoin de savoir si l’application est installée ?

Tout dépend de qui contrôle la cible. Si c’est vous, publiez un lien universel et passez universalLinksOnly à open : Apple documente cette option comme n’ouvrant l’URL que lorsqu’il s’agit d’un lien universel valide avec une application installée pour le prendre en charge, si bien qu’un false signifie que rien ne s’est ouvert et que rien n’a basculé.3 Le coût est un fichier d’association bidirectionnelle sur votre serveur web.13 Si un tiers ne publie qu’un schéma personnalisé, il n’existe aucun remplaçant : canOpenURL répond toujours, exige toujours sa déclaration Info.plist, et le plafond de cette déclaration tombe à 25 entrées pour les applications liées à partir d’iOS 27.2 Le code SwiftUI n’a rien à changer sur ce point, puisque openURL et Link n’ont jamais proposé cette vérification.414

Ai-je encore besoin de LSApplicationQueriesSchemes ?

Uniquement pour canOpenURL. La documentation Launch Services d’Apple définit la clé entièrement à travers la méthode dépréciée : elle « spécifie les schémas d’URL que vous voulez que l’application puisse utiliser avec la méthode canOpenURL: de la classe UIApplication ».17 La clé n’a aucune page dans la référence moderne Information Property List d’Apple, et la discussion de la méthode dépréciée renvoie pour cela vers l’archive héritée.217 Le remplaçant n’a besoin de rien, puisqu’Apple exempte purement et simplement open de l’exigence de déclaration.2 Terminez la migration et le tableau disparaît ; gardez un seul appel et vous gardez le tableau, l’exigence de déclaration et le plafond réduit.

L’App Store rejettera-t-il un build comportant plus de 25 entrées ?

Aucune page d’Apple ne le dit. Le plafond apparaît à un seul endroit, la discussion de canOpenURL(_:), et Apple le formule comme une limite sur ce que contient la clé, pas comme une règle de soumission.2 Rien dans les notes de version d’iOS 27, la page des nouveautés UIKit ou les notes de version de Xcode 27 n’y attache de conséquence lors de l’examen, et le symptôme documenté pour un schéma que le système considère comme non déclaré est un simple false à l’exécution.1289 Les App Store Review Guidelines ne contiennent aucune occurrence de LSApplicationQueriesSchemes, de canOpenURL ni du chiffre de 25 entrées, alors que c’est là qu’une règle de soumission figurerait si elle existait.19 L’échec à anticiper est donc une mauvaise réponse dans votre propre code, pas un build rejeté.

À retenir

Pour les développeurs iOS : - Remplacez if canOpenURL(url) { open(url) } par let opened = await open(url) avec un repli sur !opened, puis supprimez l’entrée LSApplicationQueriesSchemes correspondante une fois qu’il ne reste plus aucun appel.2 - Conservez toute vérification de schéma appliquée à une URL provenant d’un serveur. La note d’Apple vise la validation de présence d’une application, pas les entrées distantes, et les deux se ressemblent trait pour trait dans un grep.

Pour les équipes qui livrent des liens profonds entre applications d’une même famille ou des SDK : - Optez pour universalLinksOnly plutôt que canOpenURL quand vous avez besoin d’une réponse de présence, et prévoyez le fichier d’association de domaines que cela exige.313 C’est la seule vérification documentée qui ne laisse rien de visible pour l’utilisateur quand l’application est absente. - Comptez les entrées de votre tableau LSApplicationQueriesSchemes avant de lier votre application avec le SDK d’iOS 27. Tout ce qui dépasse 25 entrées sort du plafond documenté, et le symptôme d’Apple pour un schéma non déclaré est un simple false, qui se lit exactement comme une application non installée.2

Pour les responsables de publication : - N’inscrivez pas cette dépréciation comme bloquant de publication. Sans date de suppression ni conséquence à l’exécution, elle passe derrière la clé d’écran de lancement qui coûte un rejet, l’obligation de scène qui empêche le lancement, et la macro @State qui casse la compilation. - Traitez le plafond de 25 entrées comme le seul point doté d’un vrai déclencheur, car il dépend du SDK avec lequel vous liez votre application, pas de l’OS qu’exécutent vos utilisateurs.2


Le cycle 27 continue d’arriver avec des poids différents, et lire ce poids, c’est bien employer le cycle : la clé d’écran de lancement bloque une soumission, l’obligation de scène empêche une application de se lancer, la macro @State casse la compilation, On Demand Resources déclenche un compte à rebours de migration, et canOpenURL se contente d’avertir. Fabriquer de l’urgence autour d’un avertissement gaspille un cycle. La ligne à surveiller se trouve dans un paragraphe de discussion plutôt que dans une note de version, et c’est un chiffre. Le sommaire complet est la série Écosystème Apple.

Références


  1. Apple, iOS & iPadOS 27 Release Notes, section UIKit, Deprecations (radar 179874781). La page s’intitulait elle-même « iOS & iPadOS 27 Beta 4 Release Notes » lors de la consultation, comme toutes les autres pages de notes de version citées dans cet article : considérez donc toute formulation issue des notes de version comme provisoire ; les métadonnées de disponibilité des pages de symboles constituent le dossier le plus durable. Source de l’entrée complète citée ici : « canOpenURL: est déprécié. Tentez d’ouvrir l’URL et gérez l’échec éventuel plutôt que de la valider au préalable. Utiliser des liens universels plutôt que des schémas d’URL personnalisés supprime entièrement le besoin de cette validation. » L’entrée suit immédiatement celle du cycle de vie par scène (radar 141837548) : « Les applications compilées avec le dernier SDK doivent adopter le cycle de vie par scène, faute de quoi elles ne se lancent pas. » Vérifié dans le JSON de la documentation d’Apple le 26 juillet 2026, la page HTML étant rendue via JavaScript. Ce même JSON contient exactement une occurrence de canOpenURL et une du radar 179874781. Les notes de version de tvOS 27, visionOS 27, watchOS 27 et macOS 27 ont été récupérées le même jour, intitulées respectivement « tvOS 27 Beta 4 », « visionOS 27 Beta 4 », « watchOS 27 Beta 4 » et « macOS 27 Golden Gate Beta 4 », et ne contiennent aucune occurrence de l’une ou l’autre chaîne. 

  2. Apple, canOpenURL(_:), référence de méthode d’instance UIKit. Déclarée nonisolated func canOpenURL(_ url: URL) -> Bool, introduite en iOS 3.0 (Mac Catalyst 13.1, tvOS 9.0, visionOS 1.0) et marquée deprecatedAt 27.0 sur iOS, iPadOS, Mac Catalyst, tvOS et visionOS, chaque fois avec le message de disponibilité « Préférez tenter d’ouvrir les URL et gérer les échecs éventuels ». Le résumé de dépréciation de la page reprend mot pour mot deux des trois phrases de la note de version : « Tentez d’ouvrir l’URL et gérez l’échec éventuel plutôt que de la valider au préalable. Utiliser des liens universels plutôt que des schémas d’URL personnalisés supprime entièrement le besoin de cette validation. » Source de la documentation de la valeur de retour (« false si l’appareil n’a pas d’application installée enregistrée pour traiter le schéma de l’URL, ou si vous n’avez pas déclaré le schéma de l’URL dans votre fichier Info.plist ; sinon, true »), de la garantie (« Lorsque cette méthode renvoie true, iOS garantit que les appels suivants à la méthode open(_:options:completionHandler:) avec la même URL lanceront avec succès une application capable de traiter cette URL. La valeur de retour n’indique ni la validité de l’URL, ni l’existence de la ressource spécifiée, ni, dans le cas d’un lien universel, si l’appareil dispose d’une application installée enregistrée pour répondre à ce lien universel »), de la note sur les threads (« Vous pouvez appeler cette méthode en toute sécurité depuis un thread autre que le thread principal »), de l’encadré sur la liste d’autorisation cité ici avec ses deux plafonds (« Les applications liées à partir d’iOS 15 sont limitées à un maximum de 50 entrées dans la clé LSApplicationQueriesSchemes. Les applications liées à partir d’iOS 27 sont limitées à un maximum de 25 entrées dans la clé LSApplicationQueriesSchemes »), du budget d’appels à l’exécution cité ici (« Si vous liez votre application avec une version antérieure d’iOS mais qu’elle s’exécute sous iOS 9.0 ou ultérieur, vous pouvez appeler cette méthode jusqu’à 50 fois. Une fois cette limite atteinte, les appels suivants renvoient toujours false. Si l’utilisateur réinstalle ou met à jour l’application, iOS réinitialise la limite »), de l’exemption (« Contrairement à cette méthode, la méthode open(_:options:completionHandler:) n’est pas soumise à l’exigence LSApplicationQueriesSchemes. Si une application est disponible pour traiter l’URL, le système la lancera, même si vous n’avez pas déclaré le schéma ») et du repli des liens universels (« si aucune application n’est disponible pour traiter un lien universel, iOS l’achemine vers le navigateur par défaut de la personne, ce qui permet au site associé de répondre »). Une phrase de l’encadré se lit étrangement telle que publiée : « Cette méthode renvoie toujours false pour les schémas non déclarés, même si l’appareil n’a pas d’application enregistrée installée. » L’affirmation de cet article sur l’ambiguïté des valeurs false s’appuie sur la section de la valeur de retour plutôt que sur cette phrase. Vérifié dans le JSON de la documentation d’Apple le 26 juillet 2026. 

  3. Apple, UIApplication.OpenExternalURLOptionsKey.universalLinksOnly, référence de propriété de type UIKit. Disponible depuis iOS 10.0 (Mac Catalyst 13.1, tvOS 10.0, visionOS 1.0), sans métadonnée de dépréciation au 26 juillet 2026. Source du résumé (« Les URL doivent être des liens universels et disposer d’une application configurée pour les ouvrir ») et de la discussion citée ici : « Lorsque vous incluez cette clé dans le dictionnaire d’options de la méthode open(_:options:completionHandler:), la méthode n’ouvre l’URL que s’il s’agit d’un lien universel valide et qu’une application installée est capable de l’ouvrir. La valeur de cette clé est un objet NSNumber contenant une valeur booléenne. » 

  4. Apple, EnvironmentValues.openURL, référence de propriété d’instance SwiftUI. Déclarée @MainActor @preconcurrency var openURL: OpenURLAction, disponible depuis iOS 14.0, iPadOS 14.0, Mac Catalyst 14.0, macOS 11.0, tvOS 14.0, visionOS 1.0 et watchOS 7.0, sans métadonnée de dépréciation au 26 juillet 2026. Source de l’exemple openURL(url) { accepted in ... } reproduit ici et de la description de l’action par défaut citée dans cet article. 

  5. Apple, OpenURLAction, référence de structure SwiftUI. Déclarée @MainActor @preconcurrency struct OpenURLAction, disponible depuis iOS 14.0 sans métadonnée de dépréciation. Source de « Le système fournit une action d’ouverture d’URL par défaut dont le comportement dépend du contenu de l’URL. Par exemple, l’action par défaut ouvre un Universal Link dans l’application associée si possible, sinon dans le navigateur web par défaut de l’utilisateur », ainsi que de l’affirmation selon laquelle une action personnalisée s’applique à « la vue Link intégrée et aux vues Text comportant des liens markdown, ou aux liens dans des chaînes attribuées ». Les membres de Result nommés dans cet article proviennent de la page enfant, Apple, OpenURLAction.Result, qui énumère handled, discarded, systemAction, systemAction(_:) et la méthode de type iOS 26 systemAction(_:prefersInApp:)

  6. Relevé de l’auteur sur sept projets publiés pour les plateformes Apple (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni et Yawara) sous macOS 26.5.2, le 26 juillet 2026, avec ripgrep sur le Swift maison, en excluant build/, DerivedData/, .build/, Pods/, Carthage/, .swiftpm/, SourcePackages/ et les checkouts/ de paquets. La couverture des fichiers a été réconciliée entre find et rg dépôt par dépôt (77, 57, 55, 26, 34, 71 et 143 fichiers) pour confirmer qu’aucun fichier Swift maison ignoré par git n’avait été omis. Le zéro pour canOpenURL a été vérifié de trois façons : sur le Swift maison ; sur le Swift avec --no-ignore --hidden, qui inclut les produits de build et chaque checkout de paquet vendorisé (3 946 fichiers Swift dans l’arborescence ResumeGeni, 15 760 dans l’arborescence partagée 941Kit) ; et sur tous les types de fichiers avec --no-ignore. Zéro à chaque passe, y compris dans le code tiers et vendorisé. LSApplicationQueriesSchemes et INFOPLIST_KEY_LSApplicationQueriesSchemes renvoient zéro sur 192 fichiers .plist, .pbxproj, .entitlements et .xcconfig. Les 23 sites d’appel se composent de six vues Link SwiftUI, de trois appels UIApplication.shared.open, de 12 appels openURL(...) (tous dans ResumeGeni, issus de six déclarations @Environment(\.openURL)) et de deux appels NSWorkspace.shared.open dans le code macOS de Banana List ; le comptage de Link( exige une limite de mot, car un motif brut capture aussi NavigationLink( et plusieurs types ...Link( propres aux projets. Aucun usage de SFSafariViewController ni de WKWebView n’existe dans le moindre projet, et aucun dépôt ne déclare applinks:. Le garde-fou de facturation de ResumeGeni se trouve en Profile/ProfileView.swift:1746 ; la vérification de présence de Banana List et son commentaire explicatif sont en Banana List/SettingsView.swift:321 et :329, et la formule « no application set to open the document » citée dans cet article reprend le libellé de ce commentaire source plutôt qu’une transcription de la boîte de dialogue macOS ; le lien profond vers les Réglages dans Return est en Return/ContentView.swift:222. La cible principale de Reps déclare SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx" ; aucune affirmation de plateforme dans cet article ne dérive d’une clé *_DEPLOYMENT_TARGET, et la portée complète d’aucune application n’est déduite de SUPPORTED_PLATFORMS, que seules 40 des 80 configurations de build de ces projets renseignent. 

  7. Apple, openURL(_:), référence de méthode d’instance UIKit. Déclarée func openURL(_ url: URL) -> Bool, introduite en iOS 2.0 et dépréciée en iOS 10.0 (Mac Catalyst 13.1). Source de la note de dépréciation actuelle : « Appeler cette méthode n’a aucun effet. Utilisez plutôt la méthode open(_:options:completionHandler:). » Consultée le 26 juillet 2026. 

  8. Apple, UIKit updates, documentation développeur Apple. La section de juin 2026 comporte quatre sous-sections (General, App life cycle, Drag and drop et Text views) et inclut l’obligation de cycle de vie par scène sous App life cycle : « À partir d’iOS 27, les applications compilées avec le dernier SDK doivent utiliser le cycle de vie par scène, faute de quoi elles ne se lancent pas. » Recherche de canOpenURL et LSApplicationQueriesSchemes effectuée le 26 juillet 2026 : ni l’un ni l’autre n’apparaît sur la page. 

  9. Apple, Xcode 27 Release Notes. Recherche de canOpenURL, LSApplicationQueriesSchemes et du radar 179874781 effectuée le 26 juillet 2026 : aucun n’apparaît. 

  10. Apple, Describing use of required reason API, documentation Bundle Resources. Source de la définition du fingerprinting citée ici : « Certaines API que votre application utilise pour offrir ses fonctionnalités essentielles… peuvent être détournées pour accéder à des signaux de l’appareil afin de tenter d’identifier l’appareil ou l’utilisateur, ce qu’on appelle le fingerprinting. Que l’utilisateur autorise ou non votre application à faire du suivi, le fingerprinting est interdit. » Recherche effectuée le 26 juillet 2026 : canOpenURL et LSApplicationQueriesSchemes n’apparaissent pas sur la page, ce qui fonde l’affirmation faite ici selon laquelle Apple ne relie jamais la méthode au fingerprinting. La page décrit l’obligation de déclaration et renvoie la liste des catégories à la documentation NSPrivacyAccessedAPIType. La lecture « vie privée » de la dépréciation proposée dans cet article est une déduction de l’auteur, pas la justification énoncée par Apple. 

  11. Apple, UIApplication, référence de classe UIKit. Déclarée @MainActor class UIApplication, disponible depuis iOS 2.0 sans métadonnée de dépréciation. Source de l’isolation au main actor dont hérite open(_:options:completionHandler:) et à laquelle canOpenURL(_:) se soustrait avec nonisolated

  12. Apple, open(_:options:completionHandler:), référence de méthode d’instance UIKit. Disponible depuis iOS 10.0 sans métadonnée de dépréciation, déclarée sous forme à gestionnaire de complétion et sous forme async : func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: (@MainActor @Sendable (Bool) -> Void)? = nil) et func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool. Source du résumé (« Tente d’ouvrir de façon asynchrone la ressource à l’URL spécifiée »), du comportement de lancement cité ici (« Si le schéma d’URL spécifié est traité par une autre application, iOS lance cette application et lui transmet l’URL. (Lancer l’application amène l’autre application au premier plan.) Si aucune application n’est capable de traiter le schéma spécifié, le gestionnaire de complétion est appelé avec le paramètre success à false ») et de la consigne qui recommande encore la méthode dépréciée : « Pour déterminer si une application capable de traiter l’URL est installée, appelez la méthode canOpenURL(_:) avant celle-ci. Veillez à lire la description de cette méthode pour une remarque importante sur l’enregistrement des schémas que vous souhaitez employer. » Consultée le 26 juillet 2026. 

  13. Apple, Allowing apps and websites to link to your content, documentation Xcode. Source de l’exigence d’association côté serveur (« Lorsque quelqu’un installe votre application, le système vérifie un fichier stocké sur votre serveur web pour confirmer que votre site autorise votre application à ouvrir des URL en son nom. Vous seul pouvez stocker ce fichier sur votre serveur, ce qui sécurise l’association entre votre site et votre application »), du repli navigateur (« Si la personne n’a pas installé votre application, le système ouvre l’URL dans son navigateur web par défaut, ce qui permet à votre site de la traiter »), de la remarque selon laquelle une application ouvrant son propre lien universel n’est pas redirigée vers elle-même (« Si votre application utilise l’une des méthodes ci-dessus pour ouvrir un lien universel vers votre site, le lien ne s’ouvrira pas dans votre application ») et du comportement décrit ici pour un lien du même domaine dans Safari. La page cite EnvironmentValues.openURL de SwiftUI et open(_:options:completionHandler:) d’UIKit parmi les appels qui acheminent les liens universels. 

  14. Apple, Link, référence de structure SwiftUI. Déclarée @MainActor @preconcurrency struct Link<Label> where Label : View, disponible depuis iOS 14.0, macOS 11.0 et watchOS 7.0 sans métadonnée de dépréciation. Source du comportement par défaut cité ici : « Lorsqu’un utilisateur appuie ou clique sur un Link, le comportement par défaut dépend du contenu de l’URL. Par exemple, SwiftUI ouvre un Universal Link dans l’application associée si possible, sinon dans le navigateur web par défaut de l’utilisateur. » 

  15. Apple, OpenURLAction.callAsFunction(_:completion:), référence de méthode d’instance SwiftUI. Déclarée @MainActor @preconcurrency func callAsFunction(_ url: URL, completion: @escaping (Bool) -> Void). Source de la sémantique de complétion citée ici : « Une closure que la méthode appelle après avoir déterminé si elle peut ouvrir l’URL, mais possiblement avant de l’avoir entièrement ouverte. La closure prend une valeur booléenne qui indique si la méthode peut ouvrir l’URL. » La méthode sœur callAsFunction(_:) ne prend qu’une URL. Consultée le 26 juillet 2026. 

  16. Apple, Build settings reference, documentation Xcode. Recherche effectuée le 26 juillet 2026 pour INFOPLIST_KEY_LSApplicationQueriesSchemes : le réglage n’y figure pas, alors que INFOPLIST_KEY_LSApplicationCategoryType, INFOPLIST_KEY_LSBackgroundOnly, INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace et INFOPLIST_KEY_LSUIElement y figurent. Une étape de build qui fusionne sa propre property list peut tout de même injecter la clé : cette absence fait donc d’une recherche dans le dépôt le bon premier réflexe, pas un audit complet. 

  17. Apple, Launch Services Keys, Information Property List Key Reference (archive Apple). Source de la définition de la clé : « LSApplicationQueriesSchemes (Array - iOS) Spécifie les schémas d’URL que vous voulez que l’application puisse utiliser avec la méthode canOpenURL: de la classe UIApplication. Pour chaque schéma d’URL que vous voulez utiliser avec la méthode canOpenURL:, ajoutez-le comme chaîne dans ce tableau. » La page indique que la clé « est prise en charge dans iOS 9.0 et ultérieur » et ne mentionne aucun plafond d’entrées. L’archive est la destination du lien contenu dans la discussion même de canOpenURL(_:). La clé n’a aucune page dans la référence Information Property List actuelle d’Apple, vérifié le 26 juillet 2026 : documentation/bundleresources/information-property-list/lsapplicationqueriesschemes.json renvoie un HTTP 404 tandis que les pages de clés LS* voisines telles que lsapplicationcategorytype et lsbackgroundonly renvoient un 200. Le JSON d’index de la référence n’est pas non plus un test exploitable dans un sens ou dans l’autre, puisqu’il n’énumère que sept groupes de clés de premier niveau et ne nomme aucune clé LS* individuelle ; lsapplicationcategorytype, qui dispose d’une page en ligne, en est tout aussi absent. 

  18. Apple, OpenURLAction.callAsFunction(_:prefersInApp:), référence de méthode d’instance SwiftUI. Déclarée @MainActor @preconcurrency func callAsFunction(_ url: URL, prefersInApp: Bool), disponible depuis iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, macOS 26.0, tvOS 26.0, visionOS 26.0 et watchOS 26.0. Listée sur la page OpenURLAction sous Instance Methods plutôt que sous Calling the action. Elle prend un simple booléen et non un dictionnaire d’options : c’est donc la troisième et dernière signature d’appel, et aucune des trois n’accepte universalLinksOnly. Consultée le 26 juillet 2026. 

  19. Apple, App Store Review Guidelines. Recherche effectuée le 26 juillet 2026 pour LSApplicationQueriesSchemes, canOpenURL et « 25 entrées » : zéro occurrence pour chacun. Cité pour étayer l’absence de règle de soumission, et non une affirmation positive. 

Articles connexes

La règle de l'écran de lancement d'iOS 27 : quatre clés ou refus

Compilées avec le SDK iOS 27, les apps sans écran de lancement sont refusées par l'App Store. Les quatre clés à déclarer…

15 min de lecture

On Demand Resources est déprécié : ce que coûte Background Assets

Apple a déprécié ODR en 13 mots. Le remplacement se scinde en trois voies, impose un plancher iOS 26 et vous rend la ges…

23 min de lecture