canOpenURL zostało wycofane: co wywoływać zamiast niego
Trzema zdaniami Apple pożegnało metodę obecną w systemie od iOS 3.0: „canOpenURL: jest wycofane. Zamiast wcześniejszej weryfikacji należy spróbować otworzyć adres URL i obsłużyć ewentualne niepowodzenie. Zastosowanie linków uniwersalnych zamiast własnych schematów URL całkowicie eliminuje potrzebę takiej weryfikacji”.1
Wpis nosi numer radaru 179874781 i znajduje się w sekcji UIKit, Deprecations, bezpośrednio pod wymogiem cyklu życia scen, który uniemożliwia uruchomienie aplikacji.1 Sąsiedni wpis nazywa konsekwencję. Wpis o canOpenURL nie nazywa żadnej.
Każde z trzech zdań to osobna instrukcja, a tylko drugie odpowiada na pytanie, które programista naprawdę sobie zadaje: co mam napisać zamiast tego? Odpowiedź Apple zmienia kształt wywołania, a nie tylko nazwę.
TL;DR
- Apple wycofało
canOpenURL(_:)w wersji 27.0 na iOS, iPadOS, Mac Catalyst, tvOS i visionOS, z komunikatem „Zaleca się próbę otwarcia adresów URL i obsługę ewentualnych niepowodzeń”.2 Bez wersji, w której metoda zniknie, i bez konsekwencji w czasie działania. - Zmianą, która naprawdę boli, jest pewna liczba — ukryta w omówieniu samej wycofanej metody, a nie w jakiejkolwiek nocie wydania: „Aplikacje konsolidowane z iOS 27 lub nowszym mogą mieć maksymalnie 25 wpisów w kluczu
LSApplicationQueriesSchemes”, czyli o połowę mniej niż dotychczasowe 50.2 Wyzwalaczem jest wersja SDK, względem której konsolidowana jest aplikacja. - Mechaniczną zamianą jest metoda
open(_:options:completionHandler:)i zwracana przez nią wartość logiczna, która nie wymaga wpisu na liście dozwolonych schematów: „metodaopen(_:options:completionHandler:)nie podlega wymogowiLSApplicationQueriesSchemes”.2 - Jeden wzorzec zostaje bez zamiennika:
canOpenURLodpowiada, zanim cokolwiek zostanie narysowane, podczas gdy próba otwarcia odpowiada skutkiem — powodzenie wysuwa inną aplikację na pierwszy plan.2 Przetrwał natomiastuniversalLinksOnly, opcja metodyopendostępna od iOS 10, której Apple nie wycofało: „otwiera adres URL tylko wtedy, gdy jest on prawidłowym linkiem uniwersalnym i zainstalowana jest aplikacja zdolna go otworzyć”.3 - SwiftUI nigdy nie udostępniło tej metody, a wartość logiczna przekazywana do jego domknięcia od początku znaczy „można otworzyć”, a nie „otworzono”.415 W siedmiu moich wydanych aplikacjach i 463 własnych plikach Swift
canOpenURLorazLSApplicationQueriesSchemesnie występują ani razu.6
Wycofanie bez konsekwencji i liczba, która ją niesie
Apple zapisało wycofanie, nie usunięcie. Strona symbolu podaje deprecatedAt 27.0 na wszystkich pięciu platformach, na których istnieje UIApplication, a streszczenie, omówienie, kontrakt wartości zwracanej i deklaracja pozostają na swoim miejscu.2 Żadna strona Apple nie wskazuje wersji, w której metoda przestanie działać, a najbliższy precedens prowadzi w przeciwną stronę: openURL(_:), wycofane w iOS 10.0, wciąż ma własną stronę, a jego adnotacja brzmi dziś „Wywołanie tej metody nie przynosi żadnego efektu”.7 Dekada wycofywania dała metodę bezwładną, a nie nieobecną — to precedens, nie harmonogram.
Samo ogłoszenie jest przy tym węższe niż wycofanie. Nota wydania znajduje się na stronie zatytułowanej przez Apple „iOS & iPadOS 27 Beta 4 Release Notes” i nie pojawia się nigdzie indziej, choć metadane symbolu wycofują metodę również na tvOS i visionOS, a ani strona aktualizacji UIKit za czerwiec 2026, ani informacje o wydaniu Xcode 27 nie wspominają o niej w ogóle.1289 To strona symbolu jest tu trwalszą połową zapisu.
Dlatego prawdziwą historią jest ukryta liczba. Znajduje się w omówieniu samej wycofanej metody, w tej samej uwadze na marginesie, która w ogóle wymaga deklaracji: „Aplikacje konsolidowane z iOS 15 lub nowszym mogą mieć maksymalnie 50 wpisów w kluczu LSApplicationQueriesSchemes. Aplikacje konsolidowane z iOS 27 lub nowszym mogą mieć maksymalnie 25 wpisów w kluczu LSApplicationQueriesSchemes”.2
Lista dozwolonych schematów przeżywa więc wycofanie jedynej metody, której służy, a limit spada o połowę dla aplikacji konsolidowanych z nową wersją SDK. Apple podaje limit i na tym poprzestaje. Nigdzie nie napisano, co dzieje się po wpisie 25., a zestawienie obu połówek tej uwagi daje odpowiedź prawdopodobną, nie udokumentowaną: niezadeklarowane schematy zawsze zwracają false, więc aplikacja deklarująca 40 schematów po ponownej konsolidacji zapewne zacznie traktować 15 z nich jako nieobecne. Apple nie mówi, które 15 ani czy obcięcie w ogóle podąża za kolejnością w tablicy. Mechanizm należy traktować jako wnioskowanie, a ryzyko jako realne, bo false wygląda identycznie niezależnie od tego, czy brakuje aplikacji, czy deklaracji.
Kolejny akapit tego samego omówienia niesie drugi limit, a oba są alternatywami, nie sumują się. Apple wiąże je z wersją SDK, względem której skonsolidowano aplikację: „Jeśli aplikacja jest konsolidowana ze starszą wersją iOS, ale działa w iOS 9.0 lub nowszym, metodę tę można wywołać do 50 razy. Po osiągnięciu tego limitu kolejne wywołania zawsze zwracają false. Jeśli użytkownik przeinstaluje lub zaktualizuje aplikację, iOS zeruje limit”.2 Warunek jest tu kluczowy. Ten budżet należy do aplikacji konsolidowanych przed iOS 9, a takiej dziś już się nie wyda. Każda współczesna aplikacja podlega drugiej gałęzi — limitowi deklaracji, temu samemu, który Apple ścięło z 50 wpisów do 25. Obie ścieżki kończą się tym samym milczącym false i obie udokumentowano wyłącznie wewnątrz metody, którą Apple właśnie wycofało.
Odczytanie tego przez pryzmat prywatności również jest wnioskowaniem. canOpenURL było standardowym sposobem wykrywania, jakie aplikacje ma zainstalowane dana osoba — sygnałem urządzenia, a nie sprawdzeniem linku — a definicja fingerprintingu podana przez samo Apple obejmuje API „nadużywane do sięgania po sygnały urządzenia w celu identyfikacji urządzenia lub użytkownika”.10 Apple nigdzie nie łączy tych dwóch rzeczy: metoda nie figuruje na żadnej liście API wymagających uzasadnienia, a nota wydania, komunikat o wycofaniu i strona symbolu podają wyłącznie mechaniczne uzasadnienie.1210 Obcięty o połowę limit zapytań pasuje do wątku prywatności, tyle że Apple tego nie zapisało.
Co tak naprawdę obiecywało canOpenURL
true było gwarancją dotyczącą następnego wywołania, a nie opisem adresu URL: „Gdy ta metoda zwraca true, iOS gwarantuje, że kolejne wywołania metody open(_:options:completionHandler:) z tym samym adresem URL pomyślnie uruchomią aplikację zdolną go obsłużyć”.2 false było natomiast wieloznaczne z założenia — miało dwie przyczyny i nie dawało sposobu, by rozstrzygnąć, która zadziałała: „false, jeśli na urządzeniu nie ma zainstalowanej aplikacji zarejestrowanej do obsługi schematu tego adresu URL albo jeśli schemat nie został zadeklarowany w pliku Info.plist”.2
Łatwo przypisać tej metodzie trzy odpowiedzi, których nigdy nie dawała: „Zwracana wartość nie mówi o poprawności adresu URL, o tym, czy wskazany zasób istnieje, ani — w przypadku linku uniwersalnego — o tym, czy na urządzeniu jest zainstalowana aplikacja zarejestrowana do jego obsługi”.2 Trzeci człon ma znaczenie dla migracji zalecanej przez Apple i wróci jeszcze niżej.
Cecha, która czyniła tę metodę strukturalnie użyteczną, jest zarazem tą, której nic nie zastępuje. Deklaracja canOpenURL zawiera słowo kluczowe nonisolated, a Apple mówi wprost: „metodę tę można bezpiecznie wywołać z wątku innego niż główny”.2 Synchroniczna wartość logiczna dostępna poza main actorem potrafi zadecydować o układzie interfejsu, zanim cokolwiek zostanie wyrenderowane.
Spróbuj i obsłuż: kształt wywołania się zmienia
Instrukcja zamiany od Apple mieści się w jednym zdaniu podrzędnym, a porównanie „przed” i „po” wygląda trywialnie.
// 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()
}
Wpis w Info.plist znika, a Apple mówi to wprost: „W przeciwieństwie do tej metody, metoda open(_:options:completionHandler:) nie podlega wymogowi LSApplicationQueriesSchemes. Jeśli dostępna jest aplikacja zdolna obsłużyć dany adres URL, system ją uruchomi, nawet gdy schemat nie został zadeklarowany”.2 Baza kodu, która przeprowadzi migrację do końca, usuwa ten klucz, a wraz z nim problem 25 wpisów.
Przepisanie kodu nie usuwa dwóch różnic — obie są strukturalne.
Pierwsza to izolacja i moment wywołania. Deklaracja UIApplication brzmi @MainActor class UIApplication, więc open działa na main actorze, a streszczenie metody nazywa rzecz po imieniu: „Podejmuje próbę asynchronicznego otwarcia zasobu spod wskazanego adresu URL”.1112 Synchroniczna wartość logiczna dostępna poza głównym wątkiem zniknęła, więc logika „sprawdź, potem otwórz” osadzona w warstwie modelu, w kolejce działającej w tle albo w nieizolowanej funkcji pomocniczej musi się przenieść albo stać się async.
Druga: pytania nie da się już zadać dyskretnie. Gdy próba się powiedzie, na pierwszym planie jest inna aplikacja: „iOS uruchamia tę aplikację i przekazuje jej adres URL. (Uruchomienie aplikacji wysuwa ją na pierwszy plan)”.12 Odpowiedź przychodzi jako efekt uboczny działania. Dla przypadku niepowodzenia Apple nie dokumentuje niczego widocznego — jedynie tyle, że „domknięcie kończące jest wywoływane z parametrem powodzenia ustawionym na false” — ale każdy wzorzec, który potrzebował odpowiedzi przed działaniem, traci ją: pokazanie wiersza „otwórz w innej aplikacji” tylko wtedy, gdy aplikacja jest obecna, uporządkowanie arkusza udostępniania według tego, co jest zainstalowane, czy wybór domyślnego celu spośród konkurencyjnych.12
Sama dokumentacja Apple też nie nadążyła. Strona open(_:options:completionHandler:) wciąż instruuje: „Aby ustalić, czy zainstalowana jest aplikacja zdolna obsłużyć dany adres URL, przed wywołaniem tej metody należy wywołać canOpenURL(_:)”.12 Strona zamiennika poleca metodę wycofaną.
Sprawdzenie obecności, które przetrwało
Jedna udokumentowana ścieżka odpowiada na pytanie canOpenURL przez próbę, nie pokazując użytkownikowi niczego, gdy odpowiedź brzmi „nie”. UIApplication.OpenExternalURLOptionsKey.universalLinksOnly istnieje od iOS 10, Apple go nie wycofało, a zachowanie to dokładnie sprawdzenie obecności: „metoda otwiera adres URL tylko wtedy, gdy jest on prawidłowym linkiem uniwersalnym i zainstalowana jest aplikacja zdolna go otworzyć”.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
}
Wartościowa jest gałąź false. Żadna przeglądarka się nie uruchomiła, żadna aplikacja nie wyszła na wierzch, a kod wywołujący dowiedział się tego, co dawniej mówiło mu canOpenURL. Kompromis kryje się w nazwie opcji: bez niej próba otwarcia adresu https kończy się powodzeniem zawsze, gdy podejmie się go przeglądarka, ponieważ „jeśli żadna aplikacja nie jest dostępna do obsługi linku uniwersalnego, iOS kieruje go do domyślnej przeglądarki użytkownika, pozwalając odpowiedzieć powiązanej witrynie”.2 Opcja daje sensowną wartość logiczną w zamian za wyłączenie tego zachowania zapasowego, dzięki któremu linki uniwersalne są przyjemne w użyciu. Apple określa typ tej wartości jako „obiekt NSNumber zawierający wartość logiczną”, co wartość true w Swifcie spełnia dzięki mostkowaniu do Objective-C.3
Cena jest architektoniczna i jest nią trzecie zdanie Apple. Linki uniwersalne wymagają obustronnego powiązania: „Gdy ktoś instaluje aplikację, system sprawdza plik przechowywany na serwerze WWW, aby potwierdzić, że witryna zezwala aplikacji na otwieranie adresów URL w jej imieniu. Tylko właściciel może umieścić ten plik na swoim serwerze, co zabezpiecza powiązanie witryny z aplikacją”.13 Plik na kontrolowanym przez siebie serwerze to dokładnie to, czego własny schemat nigdy nie wymagał — i dlatego migracja sprawdza się w obrębie własnej rodziny aplikacji, a nie daje nic wobec cudzej aplikacji, która publikuje wyłącznie theirapp://. Po drodze czekają dwie udokumentowane niespodzianki: otwarcie własnego linku uniwersalnego z własnej aplikacji nie prowadzi do tej aplikacji, a stuknięcie w link z tej samej domeny podczas przeglądania witryny w Safari pozostaje w Safari.13
I tu ląduje wspomniany wcześniej trzeci człon: canOpenURL nigdy nie odpowiadało na pytanie o obecność również w przypadku linku uniwersalnego.2 Migracja usuwa wykrywanie obecności, zamiast je przenosić, bo zwykła próba otwarcia https kończy się powodzeniem niezależnie od tego, czy odpowiedziała aplikacja, czy tylko witryna. Wzorzec, który działał wyłącznie dlatego, że własne schematy wyciekały informację o instalacji, odchodzi razem ze schematami, a nota wydania Apple przedstawia tę stratę jako powód do migracji.
SwiftUI nigdy nie miało tej metody
Po stronie SwiftUI historia jest krótka, a wieści dobre. EnvironmentValues.openURL, OpenURLAction i Link są dostępne od iOS 14.0, żadne nie zostało wycofane i żadne z tych trzech nie udostępnia sprawdzenia poprawności.4514
Zamiast tego SwiftUI dostarcza schemat „spróbuj i obsłuż” z semantyką, której Apple chce teraz wszędzie, a dokumentacja parametru domknięcia mówi, że wartość logiczna odpowiada na dawne pytanie: „Domknięcie, które metoda wywołuje po ustaleniu, czy może otworzyć adres URL, ale prawdopodobnie przed jego pełnym otwarciem. Domknięcie przyjmuje wartość logiczną wskazującą, czy metoda może otworzyć ten adres URL”.15 Może otworzyć, nie otworzyła. Przykład samego Apple to wypisuje:
openURL(url) { accepted in
print(accepted ? "Success" : "Failure")
}
Link nie udostępnia żadnej wartości logicznej i deleguje decyzję do środowiska, w którym domyślne zachowanie realizuje już historię linków uniwersalnych: „domyślne działanie otwiera Universal Link w powiązanej aplikacji, jeśli to możliwe, a w przeciwnym razie w domyślnej przeglądarce użytkownika”.5 Własna akcja OpenURLAction zwracająca .handled, .discarded lub .systemAction przechwytuje każdy Link i każdy link markdown w widoku Text, który odczytuje tę akcję ze środowiska.5
Przed zaplanowaniem migracji wyłącznie w SwiftUI liczy się jedna luka. OpenURLAction nie ma słownika opcji. Jego sygnatury wywołania to callAsFunction(_:), callAsFunction(_:completion:) oraz dodane w iOS 26 callAsFunction(_:prefersInApp:) — i żadna z tych trzech nie przyjmuje universalLinksOnly.1518 Sprawdzenie obecności bez efektów ubocznych wciąż oznacza wywołanie UIApplication.shared.open.
Siedem aplikacji, 463 pliki, zero wywołań
Zanim napisałem o cudzym kodzie, przeprowadziłem audyt własnego portfolio, spodziewając się listy rzeczy do migracji. Nie ma czego migrować.6
| Aplikacja | Pliki Swift | canOpenURL |
LSApplicationQueriesSchemes |
Miejsca wywołań otwierających 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 |
| Razem | 463 | 0 | 0 | 23 |
To zero przetrwało trzy przebiegi z coraz szerszym zakresem, aż po każdy dołączony pakiet Swift i każdy typ pliku w każdym repozytorium.6 W 192 plikach .plist, .pbxproj, .entitlements i .xcconfig również nic nie deklaruje odpytywanego schematu, co zresztą wynika z powyższego: aplikacja bez wywołania canOpenURL nie ma powodu, by cokolwiek deklarować.
Powód jest prozaiczny i pewnie powszechny. Spośród 23 miejsc wywołań dziewięć otwiera dokument prawny, cztery przekazują użytkownika do własnego produktu webowego aplikacji, dwa otwierają census.gov z poziomu alertu, aby użytkownik mógł znaleźć swojego przedstawiciela, dwa otwierają adres URL ogłoszenia o pracę pochodzący z API, dwa to eksporty file: wyłącznie na macOS, a jedno to UIApplication.openSettingsURLString ukryte za alertem „Health Access Required”. Żadne z tych 20 nie odpytuje innej aplikacji, bo każdy cel to albo https, na które Safari zawsze odpowiada, albo adres systemowy. Całe ciekawe zachowanie mieści się w pozostałych trzech.
Jedyne miejsce, które weryfikuje coś przed otwarciem, robi zupełnie co innego niż to, czego dotyczy wycofanie: przepływ płatności w ResumeGeni wysyła POST na /api/me/portal, a następnie weryfikuje url.scheme == "https" na adresie zwróconym przez serwer — na wypadek gdyby przejęty lub wadliwy serwer podał aplikacji adres file: albo z własnym schematem.6 Nota Apple celuje w sprawdzanie, czy aplikacja docelowa istnieje, i nie mówi nic o zaufaniu do danych zdalnych, więc usunięcie tego zabezpieczenia byłoby regresją bezpieczeństwa, a nie migracją.
Pozostałe dwa to przekazania w macOS-owej części Banana List, a jedno z nich zawiera jedyne prawdziwe sprawdzenie obecności w całym portfolio. Aplikacja pyta NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anthropic.claudefordesktop") != nil, zanim przekaże dołączone rozszerzenie .mcpb do Launch Services, a obok znajduje się przycisk otwierający claude.com/download dla osób, które najpierw potrzebują samej aplikacji. Komentarz w kodzie nazywa motyw: bez tego sprawdzenia macOS pokazuje własne okno „no application set to open the document”.6 Inna platforma, a zarazem najbardziej użyteczna obserwacja z całego audytu, bo pokazuje tryb awarii podejścia „spróbuj i obsłuż” w praktyce. Nieudana próba nie zawsze jest cicha, a gdy system przemawia w naszym imieniu, użytkownik czyta jego komunikat błędu zamiast naszego rozwiązania zapasowego.
Żadne repozytorium nie deklaruje applinks:, więc żadna z tych aplikacji nie obsługuje linków uniwersalnych, a architektoniczna poprawka Apple pozostaje nieopłacona również po mojej stronie.6 Własny schemat kosztuje jeden wpis w Info.plist; link uniwersalny kosztuje plik na serwerze WWW, uprawnienie (entitlement) i domenę, którą się kontroluje.
To pokazuje, gdzie leży prawdziwa praca: nie we własnym kodzie aplikacji, lecz w arkuszach udostępniania porządkowanych według zainstalowanych aplikacji, w listach wyboru „otwórz w” i w SDK do atrybucji — czego czyste repozytorium wcale nie wyklucza.
Sam audyt jest tu łatwiejszy niż gdzie indziej w cyklu 27. W przeciwieństwie do kluczy ekranu startowego, które przychodzą z ustawień kompilacji i całkowicie wymykają się wyszukiwaniu tekstowemu, LSApplicationQueriesSchemes nie ma odpowiednika INFOPLIST_KEY_ w dokumentacji ustawień kompilacji Apple, więc grep jest właściwym pierwszym ruchem, a to, czyje schematy figurują w tablicy, mówi, która zależność potrzebuje odpowiedzi.16
FAQ
Czy canOpenURL przestaje działać w iOS 27?
Nie, a Apple nie podało, kiedy tak się stanie. Symbol nosi deprecatedAt 27.0, a każda inna część strony — kontrakt wartości zwracanej, gwarancja dotycząca następnego wywołania open, limit deklaracji i budżet wywołań dla starszych aplikacji — pozostaje udokumentowana.2 Ani w nocie wydania, ani na stronie symbolu, ani w informacjach o wydaniu Xcode 27 nie pojawia się wersja usunięcia.129 Warto zakładać ostrzeżenie i powolny zanik, a nie nagłe odcięcie.
Co napisać zamiast tego, jeśli muszę wiedzieć, czy aplikacja jest zainstalowana?
Zależy to od tego, czy kontroluje się cel. Jeśli tak, należy opublikować link uniwersalny i przekazać universalLinksOnly do open — Apple dokumentuje tę opcję jako otwierającą adres URL tylko wtedy, gdy jest on prawidłowym linkiem uniwersalnym, a zainstalowana aplikacja może go przejąć, więc false oznacza, że nic się nie otworzyło i nic nie przełączyło.3 Kosztem jest plik obustronnego powiązania na własnym serwerze WWW.13 Jeśli cudza aplikacja publikuje wyłącznie własny schemat, zamiennika nie ma: canOpenURL wciąż odpowiada, wciąż wymaga deklaracji w Info.plist, a limit tej deklaracji spada o połowę, do 25 wpisów, dla aplikacji konsolidowanych z iOS 27 lub nowszym.2 Kod w SwiftUI nie wymaga na tym froncie żadnych zmian, ponieważ openURL i Link nigdy takiego sprawdzenia nie oferowały.414
Czy LSApplicationQueriesSchemes jest nadal potrzebny?
Tylko dla canOpenURL. Dokumentacja Launch Services definiuje ten klucz wyłącznie przez wycofaną metodę: „określa schematy URL, których aplikacja ma móc używać z metodą canOpenURL: klasy UIApplication”.17 Klucz nie ma w ogóle własnej strony we współczesnej dokumentacji Information Property List, a omówienie wycofanej metody odsyła po niego do archiwum.217 Zamiennik nie potrzebuje niczego, ponieważ Apple wprost zwalnia open z wymogu deklaracji.2 Migracja doprowadzona do końca usuwa tablicę; jedno pozostawione wywołanie zachowuje tablicę, wymóg deklaracji i mniejszy limit.
Czy App Store odrzuci kompilację z więcej niż 25 wpisami?
Żadna strona Apple tego nie mówi. Limit pojawia się w jednym miejscu — w omówieniu canOpenURL(_:) — i Apple formułuje go jako ograniczenie zawartości klucza, a nie regułę zgłoszeniową.2 Ani informacje o wydaniu iOS 27, ani strona aktualizacji UIKit, ani informacje o wydaniu Xcode 27 nie wiążą z nim konsekwencji w recenzji, a udokumentowanym objawem dla schematu traktowanego przez system jako niezadeklarowany jest zwykłe false w czasie działania.1289 W App Store Review Guidelines nie występuje ani LSApplicationQueriesSchemes, ani canOpenURL, ani liczba 25 wpisów — a to tam mieszkałaby reguła zgłoszeniowa, gdyby istniała.19 Planować trzeba więc pod błędną odpowiedź we własnym kodzie, a nie pod odrzuconą kompilację.
Najważniejsze wnioski
Dla programistów iOS:
- Zastąpić if canOpenURL(url) { open(url) } przez let opened = await open(url) z zachowaniem zapasowym na !opened, a gdy nie zostanie już żadne wywołanie, usunąć odpowiadający wpis LSApplicationQueriesSchemes.2
- Zachować każde sprawdzenie schematu wykonywane na adresie URL pochodzącym z serwera. Nota Apple dotyczy weryfikacji obecności aplikacji, a nie danych zdalnych, a w grep jedno wygląda dokładnie jak drugie.
Dla zespołów wydających deep linki w rodzinie aplikacji lub SDK do atrybucji:
- Gdy potrzebna jest odpowiedź o obecności, sięgać po universalLinksOnly, a nie canOpenURL, i uwzględnić w planie wymagany plik powiązanych domen.313 To jedyne udokumentowane sprawdzenie, które przy braku aplikacji nie pokazuje użytkownikowi niczego.
- Przeliczyć wpisy w tablicy LSApplicationQueriesSchemes przed konsolidacją z SDK dla iOS 27. Wszystko powyżej 25 wpisów wypada poza udokumentowany limit, a objawem niezadeklarowanego schematu jest u Apple zwykłe false, które czyta się dokładnie tak samo jak niezainstalowana aplikacja.2
Dla osób odpowiedzialnych za wydania:
- Nie planować tego wycofania jako blokera wydania. Brak daty usunięcia i brak konsekwencji w czasie działania stawia je za kluczem ekranu startowego, który kosztuje odrzucenie, za wymogiem scen, który blokuje uruchomienie, i za makrem @State, które blokuje kompilację.
- Limit 25 wpisów traktować jako jedyną pozycję z realnym wyzwalaczem, ponieważ zależy on od wersji SDK, względem której konsolidowana jest aplikacja, a nie od systemu, z którego korzystają użytkownicy.2
Cykl 27 przynosi zmiany o bardzo różnym ciężarze, a odczytanie tego ciężaru decyduje o tym, czy dobrze się ten cykl wykorzysta: klucz ekranu startowego blokuje zgłoszenie, wymóg scen nie pozwala aplikacji się uruchomić, makro @State zatrzymuje kompilację, On Demand Resources uruchamia zegar migracji, a canOpenURL tylko ostrzega. Produkowanie pilności wokół ostrzeżenia marnuje cały cykl. Wiersz wart obserwacji znajduje się w akapicie omówienia, a nie w nocie wydania — i jest liczbą. Pełny spis serii znajduje się tutaj: seria Apple Ecosystem.
Źródła
-
Apple, iOS & iPadOS 27 Release Notes, sekcja UIKit, Deprecations (radar 179874781). W chwili sprawdzania strona sama tytułowała się „iOS & iPadOS 27 Beta 4 Release Notes”, podobnie jak każda inna cytowana w tym artykule strona z notami wydania, więc całe brzmienie not wydania należy traktować jako tymczasowe; trwalszym zapisem są metadane dostępności na stronach symboli. Źródło pełnego cytowanego tu wpisu: „
canOpenURL:jest wycofane. Zamiast wcześniejszej weryfikacji należy spróbować otworzyć adres URL i obsłużyć ewentualne niepowodzenie. Zastosowanie linków uniwersalnych zamiast własnych schematów URL całkowicie eliminuje potrzebę takiej weryfikacji”. Wpis następuje bezpośrednio po wpisie o cyklu życia scen (radar 141837548): „Aplikacje zbudowane przy użyciu najnowszej wersji SDK muszą przyjąć cykl życia oparty na scenach, w przeciwnym razie nie uruchomią się”. Zweryfikowano względem pliku JSON z dokumentacją Apple 26 lipca 2026, ponieważ wersja HTML tej strony renderowana jest przez JavaScript. Ten sam plik JSON zawiera dokładnie jedno wystąpieniecanOpenURLi jedno numeru radaru 179874781. Noty wydania tvOS 27, visionOS 27, watchOS 27 i macOS 27 pobrano tego samego dnia — nosiły tytuły odpowiednio „tvOS 27 Beta 4”, „visionOS 27 Beta 4”, „watchOS 27 Beta 4” i „macOS 27 Golden Gate Beta 4” — i nie zawierają ani jednego wystąpienia żadnego z tych ciągów. ↩↩↩↩↩↩ -
Apple, canOpenURL(_:), dokumentacja metody instancji UIKit. Zadeklarowana jako
nonisolated func canOpenURL(_ url: URL) -> Bool, wprowadzona w iOS 3.0 (Mac Catalyst 13.1, tvOS 9.0, visionOS 1.0) i oznaczonadeprecatedAt27.0 na iOS, iPadOS, Mac Catalyst, tvOS i visionOS, w każdym przypadku z komunikatem dostępności „Zaleca się próbę otwarcia adresów URL i obsługę ewentualnych niepowodzeń”. Podsumowanie wycofania na tej stronie powtarza dosłownie dwa z trzech zdań noty wydania: „Zamiast wcześniejszej weryfikacji należy spróbować otworzyć adres URL i obsłużyć ewentualne niepowodzenie. Zastosowanie linków uniwersalnych zamiast własnych schematów URL całkowicie eliminuje potrzebę takiej weryfikacji”. Źródło dokumentacji wartości zwracanej („false, jeśli na urządzeniu nie ma zainstalowanej aplikacji zarejestrowanej do obsługi schematu tego adresu URL albo jeśli schemat nie został zadeklarowany w plikuInfo.plist; w przeciwnym razietrue”), gwarancji („Gdy ta metoda zwracatrue, iOS gwarantuje, że kolejne wywołania metodyopen(_:options:completionHandler:)z tym samym adresem URL pomyślnie uruchomią aplikację zdolną obsłużyć ten adres. Zwracana wartość nie mówi o poprawności adresu URL, o tym, czy wskazany zasób istnieje, ani — w przypadku linku uniwersalnego — o tym, czy na urządzeniu jest zainstalowana aplikacja zarejestrowana do odpowiedzi na ten link”), uwagi o wątkach („Metodę tę można bezpiecznie wywołać z wątku innego niż główny”), cytowanej tu uwagi o liście dozwolonych schematów wraz z oboma limitami („Aplikacje konsolidowane z iOS 15 lub nowszym mogą mieć maksymalnie 50 wpisów w kluczuLSApplicationQueriesSchemes. Aplikacje konsolidowane z iOS 27 lub nowszym mogą mieć maksymalnie 25 wpisów w kluczuLSApplicationQueriesSchemes”), cytowanego tu budżetu wywołań w czasie działania („Jeśli aplikacja jest konsolidowana ze starszą wersją iOS, ale działa w iOS 9.0 lub nowszym, metodę tę można wywołać do 50 razy. Po osiągnięciu tego limitu kolejne wywołania zawsze zwracająfalse. Jeśli użytkownik przeinstaluje lub zaktualizuje aplikację, iOS zeruje limit”), zwolnienia („W przeciwieństwie do tej metody, metodaopen(_:options:completionHandler:)nie podlega wymogowiLSApplicationQueriesSchemes. Jeśli dostępna jest aplikacja zdolna obsłużyć dany adres URL, system ją uruchomi, nawet gdy schemat nie został zadeklarowany”) oraz zachowania zapasowego linków uniwersalnych („jeśli żadna aplikacja nie jest dostępna do obsługi linku uniwersalnego, iOS kieruje go do domyślnej przeglądarki użytkownika, pozwalając odpowiedzieć powiązanej witrynie”). Jedno zdanie tej uwagi brzmi w opublikowanej postaci osobliwie: „Metoda ta zawsze zwracafalsedla niezadeklarowanych schematów, nawet jeśli na urządzeniu nie ma zainstalowanej zarejestrowanej aplikacji”. Twierdzenie o wieloznaczności wartościfalsew tym artykule opiera się na sekcji wartości zwracanej, a nie na tym zdaniu. Zweryfikowano względem pliku JSON z dokumentacją Apple 26 lipca 2026. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, UIApplication.OpenExternalURLOptionsKey.universalLinksOnly, dokumentacja właściwości typu w UIKit. Dostępna od iOS 10.0 (Mac Catalyst 13.1, tvOS 10.0, visionOS 1.0), bez metadanych wycofania na dzień 26 lipca 2026. Źródło streszczenia („Adresy URL muszą być linkami uniwersalnymi i mieć skonfigurowaną aplikację, która je otworzy”) oraz cytowanego tu omówienia: „Gdy ten klucz zostanie umieszczony w słowniku opcji metody
open(_:options:completionHandler:), metoda otwiera adres URL tylko wtedy, gdy jest on prawidłowym linkiem uniwersalnym i zainstalowana jest aplikacja zdolna go otworzyć. Wartością tego klucza jest obiektNSNumberzawierający wartość logiczną”. ↩↩↩↩↩ -
Apple, EnvironmentValues.openURL, dokumentacja właściwości instancji w SwiftUI. Zadeklarowana jako
@MainActor @preconcurrency var openURL: OpenURLAction, dostępna od iOS 14.0, iPadOS 14.0, Mac Catalyst 14.0, macOS 11.0, tvOS 14.0, visionOS 1.0 i watchOS 7.0, bez metadanych wycofania na dzień 26 lipca 2026. Źródło odtworzonego tu przykładuopenURL(url) { accepted in ... }oraz cytowanego w artykule opisu działania domyślnego. ↩↩↩ -
Apple, OpenURLAction, dokumentacja struktury SwiftUI. Zadeklarowana jako
@MainActor @preconcurrency struct OpenURLAction, dostępna od iOS 14.0, bez metadanych wycofania. Źródło zdania „System udostępnia domyślną akcję otwierania adresów URL, której zachowanie zależy od zawartości adresu. Na przykład domyślne działanie otwiera Universal Link w powiązanej aplikacji, jeśli to możliwe, a w przeciwnym razie w domyślnej przeglądarce użytkownika” oraz stwierdzenia, że własna akcja obejmuje „wbudowany widokLinki widokiTextz linkami markdown lub linkami w ciągach atrybutowanych”. Wymienione w artykule człony typuResultpochodzą ze strony podrzędnej: Apple, OpenURLAction.Result, która wyliczahandled,discarded,systemAction,systemAction(_:)oraz dodaną w iOS 26 metodę typusystemAction(_:prefersInApp:). ↩↩↩ -
Autorski przegląd siedmiu wydanych projektów na platformy Apple (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni i Yawara) na macOS 26.5.2, 26 lipca 2026, przy użyciu ripgrepa na własnym kodzie Swift, z wykluczeniem
build/,DerivedData/,.build/,Pods/,Carthage/,.swiftpm/,SourcePackages/oraz katalogówcheckouts/pakietów. Pokrycie plików uzgodniono, porównującfindzrgw każdym repozytorium (77, 57, 55, 26, 34, 71 i 143 pliki), aby potwierdzić, że nie pominięto własnego kodu Swift wykluczonego przez .gitignore. Zero dlacanOpenURLzweryfikowano na trzy sposoby: na własnym kodzie Swift; na kodzie Swift z opcjami--no-ignore --hidden, co obejmuje produkty kompilacji i każdy dołączony pakiet (3946 plików Swift w drzewie ResumeGeni, 15 760 we wspólnym drzewie 941Kit); oraz na wszystkich typach plików z--no-ignore. Zero w każdym przebiegu, łącznie z kodem zewnętrznym i dołączonym.LSApplicationQueriesSchemesiINFOPLIST_KEY_LSApplicationQueriesSchemesdały zero w 192 plikach.plist,.pbxproj,.entitlementsi.xcconfig. Na 23 miejsca wywołań składa się sześć widokówLinkw SwiftUI, trzy wywołaniaUIApplication.shared.open, 12 wywołańopenURL(...)(wszystkie w ResumeGeni, z sześciu deklaracji@Environment(\.openURL)) oraz dwa wywołaniaNSWorkspace.shared.openw macOS-owym kodzie Banana List; zliczanieLink(wymaga granicy słowa, ponieważ goły wzorzec dopasowuje równieżNavigationLink(i kilka własnych typów...Link(. W żadnym projekcie nie występujeSFSafariViewControlleraniWKWebView, a żadne repozytorium nie deklarujeapplinks:. Zabezpieczenie płatności w ResumeGeni toProfile/ProfileView.swift:1746; sprawdzenie obecności w Banana List i wyjaśniający je komentarz toBanana List/SettingsView.swift:321i:329, a cytowany w artykule zwrot „no application set to open the document” to brzmienie tego komentarza w kodzie, a nie transkrypcja okna dialogowego macOS; deep link do Ustawień w Return toReturn/ContentView.swift:222. Główny target aplikacji Reps deklarujeSUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; żadne twierdzenie o platformach w tym artykule nie wywodzi się z klucza*_DEPLOYMENT_TARGET, a pełnego zasięgu platformowego żadnej aplikacji nie wnioskowano zSUPPORTED_PLATFORMS, które ustawia zaledwie 40 z 80 konfiguracji kompilacji w tych projektach. ↩↩↩↩↩↩ -
Apple, openURL(_:), dokumentacja metody instancji UIKit. Zadeklarowana jako
func openURL(_ url: URL) -> Bool, wprowadzona w iOS 2.0 i wycofana w iOS 10.0 (Mac Catalyst 13.1). Źródło obecnej adnotacji o wycofaniu: „Wywołanie tej metody nie przynosi żadnego efektu. Zamiast niej należy używać metodyopen(_:options:completionHandler:)”. Sprawdzono 26 lipca 2026. ↩ -
Apple, UIKit updates, dokumentacja Apple Developer. Sekcja z czerwca 2026 zawiera cztery podsekcje (General, App life cycle, Drag and drop oraz Text views), a w części App life cycle znajduje się wymóg cyklu życia scen: „Od iOS 27 aplikacje zbudowane przy użyciu najnowszej wersji SDK muszą korzystać z cyklu życia opartego na scenach, w przeciwnym razie nie uruchomią się”. Wyszukiwania
canOpenURLiLSApplicationQueriesSchemesprzeprowadzono 26 lipca 2026; żadne z nich nie występuje nigdzie na tej stronie. ↩↩ -
Apple, Xcode 27 Release Notes. Wyszukiwania
canOpenURL,LSApplicationQueriesSchemesi numeru radaru 179874781 przeprowadzono 26 lipca 2026; żadne nie występuje. ↩↩↩ -
Apple, Describing use of required reason API, dokumentacja Bundle Resources. Źródło cytowanej tu definicji fingerprintingu: „Niektóre API, których aplikacja używa do realizacji swoich podstawowych funkcji… mogą być nadużywane do sięgania po sygnały urządzenia w celu identyfikacji urządzenia lub użytkownika, co nazywa się fingerprintingiem. Niezależnie od tego, czy użytkownik zezwolił aplikacji na śledzenie, fingerprinting jest niedozwolony”. Wyszukiwanie z 26 lipca 2026:
canOpenURLaniLSApplicationQueriesSchemesnie pojawiają się na tej stronie, co stanowi podstawę zawartego tu stwierdzenia, że Apple nigdzie nie łączy tej metody z fingerprintingiem. Strona opisuje wymóg raportowania, a listę kategorii pozostawia dokumentacjiNSPrivacyAccessedAPIType. Odczytanie tego wycofania przez pryzmat prywatności jest wnioskiem autora, a nie deklarowanym uzasadnieniem Apple. ↩↩ -
Apple, UIApplication, dokumentacja klasy UIKit. Zadeklarowana jako
@MainActor class UIApplication, dostępna od iOS 2.0, bez metadanych wycofania. Źródło informacji o izolacji do main actora, którą dziedziczyopen(_:options:completionHandler:)i z którejcanOpenURL(_:)rezygnuje słowemnonisolated. ↩ -
Apple, open(_:options:completionHandler:), dokumentacja metody instancji UIKit. Dostępna od iOS 10.0, bez metadanych wycofania, zadeklarowana w wariancie z domknięciem kończącym i w wariancie
async:func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: (@MainActor @Sendable (Bool) -> Void)? = nil)orazfunc open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool. Źródło streszczenia („Podejmuje próbę asynchronicznego otwarcia zasobu spod wskazanego adresu URL”), cytowanego tu opisu uruchamiania („Jeśli wskazany schemat URL jest obsługiwany przez inną aplikację, iOS uruchamia tę aplikację i przekazuje jej adres URL. (Uruchomienie aplikacji wysuwa ją na pierwszy plan). Jeśli żadna aplikacja nie jest zdolna obsłużyć wskazanego schematu, domknięcie kończące jest wywoływane z parametrem powodzenia ustawionym nafalse”) oraz instrukcji, która wciąż zaleca wycofaną metodę: „Aby ustalić, czy zainstalowana jest aplikacja zdolna obsłużyć dany adres URL, przed wywołaniem tej metody należy wywołaćcanOpenURL(_:). Warto zapoznać się z opisem tamtej metody — zawiera on ważną uwagę o rejestrowaniu schematów, których chce się używać”. Sprawdzono 26 lipca 2026. ↩↩↩↩ -
Apple, Allowing apps and websites to link to your content, dokumentacja Xcode. Źródło wymogu powiązania po stronie serwera („Gdy ktoś instaluje aplikację, system sprawdza plik przechowywany na serwerze WWW, aby potwierdzić, że witryna zezwala aplikacji na otwieranie adresów URL w jej imieniu. Tylko właściciel może umieścić ten plik na swoim serwerze, co zabezpiecza powiązanie witryny z aplikacją”), zachowania zapasowego przeglądarki („Jeśli dana osoba nie zainstalowała aplikacji, system otwiera adres URL w jej domyślnej przeglądarce, pozwalając witrynie go obsłużyć”), uwagi, że aplikacja otwierająca własny link uniwersalny nie zostaje przekierowana do siebie samej („Jeśli aplikacja użyje jednej z powyższych metod do otwarcia linku uniwersalnego prowadzącego do własnej witryny, link nie otworzy się w tej aplikacji”) oraz opisanego tu zachowania Safari w obrębie tej samej domeny. Strona wymienia
EnvironmentValues.openURLze SwiftUI iopen(_:options:completionHandler:)z UIKit wśród wywołań kierujących linki uniwersalne. ↩↩↩↩ -
Apple, Link, dokumentacja struktury SwiftUI. Zadeklarowana jako
@MainActor @preconcurrency struct Link<Label> where Label : View, dostępna od iOS 14.0, macOS 11.0 i watchOS 7.0, bez metadanych wycofania. Źródło cytowanego tu zachowania domyślnego: „Gdy użytkownik stuknie lub kliknieLink, domyślne zachowanie zależy od zawartości adresu URL. Na przykład SwiftUI otwiera Universal Link w powiązanej aplikacji, jeśli to możliwe, a w przeciwnym razie w domyślnej przeglądarce użytkownika”. ↩↩ -
Apple, OpenURLAction.callAsFunction(_:completion:), dokumentacja metody instancji w SwiftUI. Zadeklarowana jako
@MainActor @preconcurrency func callAsFunction(_ url: URL, completion: @escaping (Bool) -> Void). Źródło cytowanej tu semantyki domknięcia: „Domknięcie, które metoda wywołuje po ustaleniu, czy może otworzyć adres URL, ale prawdopodobnie przed jego pełnym otwarciem. Domknięcie przyjmuje wartość logiczną wskazującą, czy metoda może otworzyć ten adres URL”. SiostrzanacallAsFunction(_:)przyjmuje sam adres URL. Sprawdzono 26 lipca 2026. ↩↩↩ -
Apple, Build settings reference, dokumentacja Xcode. Wyszukiwanie
INFOPLIST_KEY_LSApplicationQueriesSchemesz 26 lipca 2026: ustawienie nie występuje, podczas gdyINFOPLIST_KEY_LSApplicationCategoryType,INFOPLIST_KEY_LSBackgroundOnly,INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlaceiINFOPLIST_KEY_LSUIElement— owszem. Krok kompilacji scalający własną listę właściwości wciąż może wstrzyknąć ten klucz, więc jego nieobecność czyni z przeszukania repozytorium właściwy pierwszy ruch, a nie kompletny audyt. ↩ -
Apple, Launch Services Keys, Information Property List Key Reference (archiwum Apple). Źródło definicji klucza: „
LSApplicationQueriesSchemes(tablica – iOS) Określa schematy URL, których aplikacja ma móc używać z metodącanOpenURL:klasyUIApplication. Każdy schemat URL, którego aplikacja ma używać z metodącanOpenURL:, należy dodać do tej tablicy jako ciąg znaków”. Strona podaje, że klucz „jest obsługiwany w iOS 9.0 i nowszych”, i nie wspomina o żadnym limicie wpisów. Archiwum jest celem odsyłacza umieszczonego w omówieniu samej metodycanOpenURL(_:). Klucz nie ma własnej strony w bieżącej dokumentacji Information Property List, co potwierdzono 26 lipca 2026:documentation/bundleresources/information-property-list/lsapplicationqueriesschemes.jsonzwraca HTTP 404, podczas gdy siostrzane strony kluczyLS*, takie jaklsapplicationcategorytypeilsbackgroundonly, zwracają 200. Indeksowy plik JSON samej dokumentacji też nie jest wiarygodnym testem w żadną stronę, ponieważ wylicza tylko siedem grup kluczy najwyższego poziomu i nie wymienia żadnego pojedynczego kluczaLS*;lsapplicationcategorytype, które ma działającą stronę, również w nim nie występuje. ↩↩ -
Apple, OpenURLAction.callAsFunction(_:prefersInApp:), dokumentacja metody instancji w SwiftUI. Zadeklarowana jako
@MainActor @preconcurrency func callAsFunction(_ url: URL, prefersInApp: Bool), dostępna od iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, macOS 26.0, tvOS 26.0, visionOS 26.0 i watchOS 26.0. Wymieniona na stronieOpenURLActionw sekcji Instance Methods, a nie w Calling the action. Przyjmuje pojedynczą wartość logiczną zamiast słownika opcji, jest więc trzecią i ostatnią sygnaturą wywołania, a żadna z tych trzech nie przyjmujeuniversalLinksOnly. Sprawdzono 26 lipca 2026. ↩ -
Apple, App Store Review Guidelines. Wyszukiwania
LSApplicationQueriesSchemes,canOpenURLoraz frazy „25 entries” z 26 lipca 2026: zero wystąpień każdego z nich. Przywołane na poparcie braku reguły zgłoszeniowej, a nie jakiegokolwiek twierdzenia pozytywnego. ↩