← 所有文章

canOpenURL 已棄用:該改呼叫什麼

Apple 用三句話,讓一個自 iOS 3.0 就存在的方法退場:「canOpenURL: 已棄用。請直接嘗試開啟該 URL 並處理任何失敗,而不是先行驗證。改用通用連結而非自訂 URL scheme,可以完全免去這道驗證。」1

這條記錄掛著 radar 179874781,位在 UIKit 的 Deprecations 段落,緊接在那條會讓 App 無法啟動的 scene 生命週期強制規定底下。1 鄰居那條寫明了後果,canOpenURL 這條一個字也沒提。

三句話其實是三道各自獨立的指示,而真正回答開發者疑問的只有第二句:那我該改寫成什麼?Apple 的答案改動的不只是名稱,而是整個呼叫的形狀。

重點摘要

  • Apple 在 iOS、iPadOS、Mac Catalyst、tvOS 與 visionOS 的 27.0 版棄用 canOpenURL(_:),訊息是「建議直接嘗試開啟 URL 並處理任何失敗」。2 沒有移除版本,也沒有執行階段的後果。
  • 真正有牙齒的變動是一個數字,而它藏在被棄用方法自己的說明段落裡,不在任何發行說明中:「在 iOS 27 或之後連結的 App,LSApplicationQueriesSchemes 鍵最多只能有 25 個項目」,比原本的 50 少了一半。2 觸發條件是您連結的那份 SDK。
  • 機械式的替換就是 open(_:options:completionHandler:) 和它回傳的布林值,完全不需要允許清單項目:「open(_:options:completionHandler:) 方法不像本方法那樣受 LSApplicationQueriesSchemes 要求的限制。」2
  • 有一種模式找不到替代品:canOpenURL 在您畫出任何東西之前就給答案,而「先嘗試」是用後果來回答——成功就意味著另一個 App 被推到前景。2 倖存下來的是 universalLinksOnly,這個自 iOS 10 就存在的 open 選項至今未被棄用:它「只有在該 URL 是有效的通用連結、且裝置上裝有能開啟它的 App 時,才會開啟這個 URL」。3
  • SwiftUI 從未提供這個方法,而它 completion 的布林值本來就代表「能不能開」而非「有沒有開」。415 在我七個已上架 App、463 個自有 Swift 檔案裡,canOpenURLLSApplicationQueriesSchemes 各出現 0 次。6

沒有後果的棄用,以及那個有後果的數字

Apple 寫的是棄用,不是移除。在 UIApplication 存在的五個平台上,符號頁面都標著 deprecatedAt 27.0,而摘要、說明、回傳值契約與宣告全都原封不動。2 沒有任何一頁 Apple 文件指出這個方法會在哪一版停止運作,最接近的前例反而指向另一個方向:openURL(_:) 在 iOS 10.0 被棄用,頁面至今還在,附註現在寫的是「呼叫此方法不會有任何作用」。7 十年的棄用換來的是一個失效但仍在的方法——那是前例,不是時程表。

公告的範圍也比棄用本身窄。那則發行說明只出現在 Apple 標題為「iOS & iPadOS 27 Beta 4 Release Notes」的頁面上,別處都找不到;但符號的中繼資料照樣在 tvOS 與 visionOS 上棄用了這個方法,而 Apple 2026 年 6 月的 UIKit 更新頁與 Xcode 27 發行說明則完全沒提。1289 在這件事上,符號頁面才是紀錄中比較耐久的那一半。

於是那個被埋起來的數字才是重點。它就在被棄用方法自己的說明裡,跟當初要求您做宣告的那段補充放在一起:「在 iOS 15 或之後連結的 App,LSApplicationQueriesSchemes 鍵最多 50 個項目。在 iOS 27 或之後連結的 App,LSApplicationQueriesSchemes 鍵最多 25 個項目。」2

這份允許清單活得比它唯一服務的那個方法還久,而對於以新 SDK 連結的 App,上限直接砍半。Apple 只說了上限,然後就停在那裡。超過第 25 筆會發生什麼事,一個字也沒有;把那段補充的前後兩半併起來讀,只能得到一個合理推測,而非白紙黑字的答案:未宣告的 scheme 一律回傳 false,所以一個宣告了 40 個 scheme 的 App 重新連結後,很可能開始把其中 15 個當成不存在。至於是哪 15 個,甚至截斷是否真的照陣列順序來,Apple 都沒說。請把機制當成推論,把風險當成真的——因為不論是 App 沒裝,還是宣告漏了,回傳的 false 長得一模一樣。

同一段說明的下一個自然段還有第二個限制,兩者是二選一,不是相加。Apple 用您連結的 SDK 來區分:「如果您的 App 是針對較早版本的 iOS 連結,但執行在 iOS 9.0 或之後,最多可以呼叫此方法 50 次。達到上限之後,後續呼叫一律回傳 false。使用者重新安裝或升級 App 時,iOS 會重置這個上限。」2 請注意那個條件句。這份額度屬於在 iOS 9 之前連結的 App,也就是您今天根本不可能上架的東西。所有現行 App 都落在另一條分支——宣告上限,也正是 Apple 從 50 筆砍到 25 筆的那一條。兩者最終都會耗盡到同一個安靜的 false,而且都只記載在 Apple 剛剛棄用的那個方法內部。

隱私層面的解讀同樣是推論。canOpenURL 一向是偵測某人裝了哪些 App 的標準做法——那是裝置訊號,不是連結檢查——而 Apple 自己對指紋辨識的定義,涵蓋了「被濫用來存取裝置訊號、試圖辨識裝置或使用者」的 API。10 但 Apple 從未把兩者連起來:這個方法不在任何必要原因 API 清單上,而發行說明、棄用訊息與符號頁面給的都是純粹機械式的理由。1210 查詢上限砍半確實符合隱私這條敘事,只是 Apple 沒有把它寫下來。

canOpenURL 究竟承諾了什麼

回傳 true 是對下一次呼叫的保證,而不是對這個 URL 的描述:「當此方法回傳 true 時,iOS 保證後續以相同 URL 呼叫 open(_:options:completionHandler:) 方法,會成功啟動能處理該 URL 的 App。」2 回傳 false 則是刻意設計成模稜兩可:有兩個成因,而且無從分辨是哪一個觸發的——「若裝置上沒有安裝已註冊處理該 URL scheme 的 App,或您未在 Info.plist 檔案中宣告該 URL 的 scheme,則回傳 false。」2

有三件事它從來沒回答,卻很容易被誤以為有:「回傳值不代表 URL 是否有效、指定的資源是否存在;若是通用連結,也不代表裝置上是否安裝了已註冊回應該通用連結的 App。」2 第三個子句對 Apple 建議的遷移路徑很關鍵,稍後會再回到它。

讓這個方法在結構上真正好用的那項特性,沒有任何東西接手。canOpenURL 的宣告帶有 nonisolated 關鍵字,Apple 也明講「您可以安全地在非主執行緒上呼叫此方法」。2 一個能在 main actor 之外取得的同步布林值,可以在畫面渲染之前就決定版面要怎麼排。

先嘗試、再處理:形狀變了

Apple 給的替代指示只有一個子句長,前後對照看起來也微不足道。

// 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()
}

Info.plist 裡的項目消失了,Apple 也直說:「與此方法不同,open(_:options:completionHandler:) 方法不受 LSApplicationQueriesSchemes 要求的限制。只要有 App 能處理該 URL,即使您未宣告該 scheme,系統仍會啟動它。」2 完整遷移的專案可以刪掉這個鍵,25 筆的問題也跟著消失。

改寫之後有兩個差異留了下來,而且都是結構性的。

第一個是隔離與時機。UIApplication 的宣告寫著 @MainActor class UIApplication,因此 open 跑在 main actor 上,而它的摘要也老實說了:「嘗試以非同步方式開啟指定 URL 的資源。」1112 那個同步、可在非主執行緒使用的布林值不見了,所以放在模型層、背景佇列或非隔離輔助函式裡的「先驗證再開啟」邏輯,不是搬家,就是得改成 async

第二個是您再也不能私下發問。嘗試成功時,另一個 App 就到了前景:「iOS 會啟動該 App 並把 URL 交給它。(啟動 App 會把另一個 App 帶到前景。)」12 答案是行動的副作用。失敗的情況 Apple 沒有記載任何可見反應,只說「completion handler 會以 success 參數為 false 的方式被呼叫」——但凡是需要在行動之前先知道答案的模式都失去了依據:只在對方 App 存在時才顯示「用其他 App 開啟」那一列、依已安裝的 App 排序分享選單,或在數個競爭目標中挑一個預設值。12

Apple 自家文件也還沒跟上。open(_:options:completionHandler:) 的頁面仍然指示:「若要判斷是否安裝了能處理該 URL 的 App,請在呼叫本方法之前先呼叫 canOpenURL(_:) 方法。」12 替代方案的頁面,推薦的是那個被棄用的方法。

唯一倖存的存在性檢查

有一條記載在文件裡的路徑,能用「嘗試」的方式回答 canOpenURL 的問題,而且答案是否定時,使用者什麼也看不到。UIApplication.OpenExternalURLOptionsKey.universalLinksOnly 自 iOS 10 就存在,Apple 未曾棄用,行為正好就是一次存在性檢查:「只有在該 URL 是有效的通用連結、且裝置上安裝了能開啟它的 App 時,此方法才會開啟該 URL。」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
}

false 那條分支才是價值所在。沒有瀏覽器被啟動,沒有 App 跳出來,而呼叫端得到了以前 canOpenURL 會告訴它的資訊。代價就藏在選項名稱裡:少了它,只要瀏覽器接得住,https 的嘗試就會成功,因為「若沒有 App 可處理通用連結,iOS 會把它導向使用者的預設瀏覽器,讓對應的網站來回應」。2 這個選項是用壓抑那個讓通用連結好用的備援機制,換來一個有意義的布林值。Apple 把這個值的型別定為「內含布林值的 NSNumber 物件」,Swift 的 true 會透過 Objective-C 橋接滿足它。3

代價是架構層面的,也正是 Apple 那第三句話。通用連結需要雙向關聯:「當有人安裝您的 App 時,系統會檢查存放在您網頁伺服器上的一個檔案,以確認您的網站允許該 App 代其開啟 URL。只有您能把這個檔案放上伺服器,藉此保障網站與 App 之間關聯的安全。」13 一個由您掌控的伺服器檔案,正是自訂 scheme 從來不需要的東西;這也說明了為什麼這條遷移路徑對自家 App 家族行得通,對只公開 theirapp:// 的第三方卻毫無幫助。還有兩個記載在文件裡的意外一併附贈:您的 App 開啟自家的通用連結不會被導回自己的 App,而在 Safari 瀏覽自家網站時點擊同網域連結,也會留在 Safari 裡。13

前面那個第三子句的落點就在這裡:canOpenURL 本來也從不回答通用連結的存在性問題。2 所以遷移是移除存在性偵測,而不是把它搬過去——因為單純的 https 嘗試,不論回應的是 App 還是只有網站,都會成功。一個只因為自訂 scheme 洩漏了安裝狀態才成立的模式,會跟著 scheme 一起離場;而 Apple 的發行說明,正是把這項損失當成遷移的理由來寫。

SwiftUI 從來就沒有這個方法

SwiftUI 這邊很短,而且是好消息。EnvironmentValues.openURLOpenURLActionLink 都標示自 iOS 14.0 起可用、沒有任何棄用,三者也都沒有提供驗證用的檢查。4514

SwiftUI 提供的,正是 Apple 現在希望到處通行的「先嘗試、再處理」語意,而 completion 參數的文件表明,那個布林值回答的就是舊問題:「此閉包會在方法判斷出能否開啟該 URL 之後被呼叫,但可能早於 URL 完全開啟。閉包接收一個布林值,表示此方法是否能夠開啟該 URL。」15 是「能不能開」,不是「有沒有開」。Apple 自己的範例就把它印出來:

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

Link 完全不提供布林值,而是交給環境處理,而預設行為本身就已經實作了通用連結那套劇本:「預設動作會盡可能在對應的 App 中開啟通用連結,若無法則以使用者的預設網頁瀏覽器開啟。」5 自訂的 OpenURLAction 回傳 .handled.discarded.systemAction,就能攔截每一個 Link,以及每一個從該環境讀取動作的 Text 中的 markdown 連結。5

在規劃純 SwiftUI 的遷移之前,有一個缺口不能忽略。OpenURLAction 沒有選項字典。它的呼叫簽章只有 callAsFunction(_:)callAsFunction(_:completion:),以及 iOS 26 新增的 callAsFunction(_:prefersInApp:),三者都不接受 universalLinksOnly1518 想做沒有副作用的存在性檢查,還是得呼叫 UIApplication.shared.open

七個 App、463 個檔案、0 次呼叫

在評論別人的程式碼之前,我先稽核了自己的作品集,本來預期會整理出一份遷移清單。結果沒有東西需要遷移。6

App Swift 檔案數 canOpenURL LSApplicationQueriesSchemes 開啟 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
總計 463 0 0 23

這個 0 撐過了三輪逐步放寬範圍的搜尋,一路擴及每一個內嵌的 Swift 套件、每一個儲存庫裡的每一種檔案類型。6 192 個 .plist.pbxproj.entitlements.xcconfig 檔案中,也沒有任何一處宣告查詢用的 scheme——這很合理:沒有 canOpenURL 呼叫的 App,根本沒有理由去宣告。

原因很平凡,而且很可能相當普遍。23 個呼叫點裡,9 個開啟法律文件,4 個交棒給該 App 自家的網頁產品,2 個從警示視窗開啟 census.gov 讓使用者查詢自己的民意代表,2 個開啟由 API 傳來的職缺網址,2 個是 macOS 專屬的 file: 匯出,還有 1 個是藏在「Health Access Required」警示後面的 UIApplication.openSettingsURLString。這 20 個沒有一個在查詢別的 App,因為每個目的地不是 Safari 一定接得住的 https,就是系統 URL。有趣的行為全在剩下的 3 個裡。

唯一一處會在開啟前先驗證的程式碼,做的並不是這次棄用所針對的事:ResumeGeni 的帳務流程會 POST/api/me/portal,接著對伺服器回傳的 URL 檢查 url.scheme == "https",以防被入侵或有瑕疵的伺服器丟給 App 一個 file: 或自訂 scheme 的網址。6 Apple 那則附註針對的是驗證目標 App 是否存在,完全沒談到遠端輸入的信任問題;刪掉這道防護是安全性倒退,不是遷移。

另外兩個是 Banana List 的 macOS 交接流程,其中一個藏著整個作品集裡唯一一次真正的存在性檢查。這個 App 會先問 NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anthropic.claudefordesktop") != nil,才把隨附的 .mcpb 擴充功能交給 Launch Services,旁邊還有一顆按鈕,讓還沒安裝的人開啟 claude.com/download。程式碼註解點明了動機:少了這道檢查,macOS 會自己跳出「沒有設定用來開啟此文件的應用程式」對話框。6 平台不同,卻是整場稽核裡最有用的一筆資料——因為它在真實世界裡逮到了「先嘗試、再處理」的失敗樣態。失敗的嘗試不見得安靜,而當系統替您發言時,使用者讀到的是系統錯誤,不是您準備的備援方案。

沒有任何一個儲存庫宣告 applinks:,所以這裡沒有 App 支援通用連結,Apple 那套架構解法的帳單,我這邊同樣還沒付。6 自訂 scheme 的代價是一個 Info.plist 項目;通用連結的代價是網頁伺服器上的一個檔案、一項 entitlement,以及一個由您掌控的網域。

這也指出真正的工作在哪裡:不在自家 App 的程式碼,而在會依已安裝 App 重新排序的分享選單、「用其他 App 開啟」的挑選介面,以及歸因用的 SDK——乾淨的儲存庫一個都排除不了。

這場稽核本身,比 27 週期裡的其他項目來得輕鬆。啟動畫面鍵來自建置設定,完全躲得過純文字搜尋;LSApplicationQueriesSchemes 不同,Apple 的建置設定參考裡沒有對應的 INFOPLIST_KEY_ 版本,所以 grep 就是正確的第一步,而陣列裡出現的是誰的 scheme,也會告訴您是哪個相依套件想要那個答案。16

常見問題

canOpenURL 會在 iOS 27 停止運作嗎?

不會,而且 Apple 也沒說什麼時候會。這個符號標著 deprecatedAt 27.0,但頁面其餘部分——包括回傳值契約、對後續 open 呼叫的保證、宣告上限,以及舊版呼叫額度——全都還在文件裡。2 發行說明、符號頁面與 Xcode 27 發行說明,都沒有出現移除版本。129 請把它當成一個警告與緩慢的衰退,而不是斷裂。

如果我需要知道 App 是否已安裝,該改寫什麼?

要看您是否掌控目標。如果掌控,就發布通用連結,並在 open 帶上 universalLinksOnly;Apple 記載它只在該 URL 是有效通用連結、且有已安裝的 App 能接手時才開啟,所以 false 代表什麼都沒開、什麼都沒切換。3 代價是您網頁伺服器上的那份雙向關聯檔案。13 如果第三方只公開自訂 scheme,就沒有替代品:canOpenURL 仍然會回答,仍然需要它的 Info.plist 宣告,而對在 iOS 27 或之後連結的 App,那份宣告的上限砍半成 25 筆。2 SwiftUI 的程式碼在這方面不必改,因為 openURLLink 從來就沒提供這項檢查。414

我還需要 LSApplicationQueriesSchemes 嗎?

只有 canOpenURL 需要。Apple 的 Launch Services 文件完全以這個被棄用的方法來定義該鍵:它「指定您希望 App 能搭配 UIApplication 類別的 canOpenURL: 方法使用的 URL scheme」。17 這個鍵在 Apple 現行的 Information Property List 參考裡根本沒有頁面,被棄用方法的說明也只能連向舊版封存。217 替代方案什麼都不需要,因為 Apple 直接豁免了 open 的宣告要求。2 遷移完成,陣列就可以刪掉;留著一次呼叫,就得留著陣列、宣告要求,以及那個變小的上限。

超過 25 筆項目的建置版本會被 App Store 退件嗎?

沒有任何一頁 Apple 文件這麼說。這個上限只出現在一個地方——canOpenURL(_:) 的說明——而 Apple 的措辭把它寫成該鍵能容納多少的限制,不是送審規則。2 iOS 27 發行說明、UIKit 更新頁與 Xcode 27 發行說明,都沒有替它掛上任何審查後果,而系統把某個 scheme 視為未宣告時,文件記載的症狀是執行階段回傳單純的 false1289 App Store 審查指南裡完全找不到 LSApplicationQueriesSchemescanOpenURL 或 25 筆這個數字——若真有送審規則,那才是它該待的地方。19 所以該預防的失敗,是您自己程式碼裡得到錯誤答案,而不是被退件的建置版本。

重點整理

iOS 開發者: - 把 if canOpenURL(url) { open(url) } 換成 let opened = await open(url) 加上 !opened 的備援分支,等到再也沒有呼叫時,就刪掉對應的 LSApplicationQueriesSchemes 項目。2 - 凡是對伺服器回傳的 URL 所做的 scheme 檢查,都請保留。Apple 那則附註針對的是 App 存在性驗證,不是遠端輸入,而這兩者在 grep 裡長得一模一樣。

推出 App 家族深層連結或 SDK 的團隊: - 需要存在性答案時,請改用 universalLinksOnly 而非 canOpenURL,並把它所需的關聯網域(Associated Domains)檔案算進成本。313 這是唯一一項在 App 不存在時、使用者完全看不到任何反應的文件化檢查。 - 在以 iOS 27 SDK 連結之前,先數一數 LSApplicationQueriesSchemes 陣列。超過 25 筆的部分就落在文件所載的上限之外,而 Apple 對未宣告 scheme 給出的症狀是單純的 false——那看起來和「App 沒安裝」一模一樣。2

版本發布負責人: - 別把這次棄用排進發布阻擋項目。沒有移除日期、沒有執行階段後果,它的優先序落在會害您被退件的啟動畫面鍵、會讓 App 無法啟動的 scene 強制規定,以及會讓建置失敗的 @State 巨集之後。 - 25 筆上限才是唯一有真正觸發條件的項目,因為它取決於您連結的 SDK,而不是使用者執行的作業系統版本。2


27 週期的東西不斷送來,份量各不相同;讀懂份量,才是把這個週期過好的方法:啟動畫面鍵擋住送審,scene 強制規定讓 App 開不起來,@State 巨集讓建置失敗,On Demand Resources 啟動了一個遷移的倒數計時,而 canOpenURL 只是發出警告。為一個警告製造急迫感,只會浪費一個週期。真正值得盯著的那一行不在發行說明裡,而在一段說明文字裡,而且它是一個數字。完整的系列彙整頁在Apple 生態系系列

參考資料


  1. Apple,iOS & iPadOS 27 Release Notes,UIKit 章節的 Deprecations(radar 179874781)。查閱時該頁的標題其實是「iOS & iPadOS 27 Beta 4 Release Notes」,本文引用的其他發行說明頁面也都一樣,因此此處所有發行說明的措辭都請視為暫定;符號頁面的可用性中繼資料才是比較耐久的紀錄。本文引用的完整條目出處:「canOpenURL: 已棄用。請直接嘗試開啟該 URL 並處理任何失敗,而不是先行驗證。改用通用連結而非自訂 URL scheme,可以完全免去這道驗證。」該條目緊接在 scene 生命週期條目(radar 141837548)之後:「以最新 SDK 建置的 App 必須採用以 scene 為基礎的生命週期,否則將無法啟動。」由於 HTML 頁面透過 JavaScript 渲染,已於 2026 年 7 月 26 日對照 Apple 的文件 JSON 驗證。同一份 JSON 中,canOpenURL 恰好出現一次,radar 179874781 也恰好出現一次。tvOS 27、visionOS 27、watchOS 27 與 macOS 27 的發行說明於同日取得,標題分別為「tvOS 27 Beta 4」、「visionOS 27 Beta 4」、「watchOS 27 Beta 4」與「macOS 27 Golden Gate Beta 4」,兩個字串的出現次數皆為 0。 

  2. Apple,canOpenURL(_:),UIKit 實例方法參考。宣告為 nonisolated func canOpenURL(_ url: URL) -> Bool,於 iOS 3.0(Mac Catalyst 13.1、tvOS 9.0、visionOS 1.0)導入,並在 iOS、iPadOS、Mac Catalyst、tvOS 與 visionOS 標記 deprecatedAt 27.0,各平台的可用性訊息都是「建議直接嘗試開啟 URL 並處理任何失敗」。該頁的棄用摘要逐字重複了發行說明三句中的兩句:「請直接嘗試開啟該 URL 並處理任何失敗,而不是先行驗證。改用通用連結而非自訂 URL scheme,可以完全免去這道驗證。」以下內容的出處皆為此頁:回傳值文件(「若裝置上沒有安裝已註冊處理該 URL scheme 的 App,或您未在 Info.plist 檔案中宣告該 URL 的 scheme,則回傳 false;否則回傳 true」)、那項保證(「當此方法回傳 true 時,iOS 保證後續以相同 URL 呼叫 open(_:options:completionHandler:) 方法,會成功啟動能處理該 URL 的 App。回傳值不代表 URL 是否有效、指定的資源是否存在;若是通用連結,也不代表裝置上是否安裝了已註冊回應該通用連結的 App」)、執行緒附註(「您可以安全地在非主執行緒上呼叫此方法」)、本文引用含兩個上限的允許清單補充(「在 iOS 15 或之後連結的 App,LSApplicationQueriesSchemes 鍵最多 50 個項目。在 iOS 27 或之後連結的 App,LSApplicationQueriesSchemes 鍵最多 25 個項目」)、本文引用的執行階段呼叫額度(「如果您的 App 是針對較早版本的 iOS 連結,但執行在 iOS 9.0 或之後,最多可以呼叫此方法 50 次。達到上限之後,後續呼叫一律回傳 false。使用者重新安裝或升級 App 時,iOS 會重置這個上限」)、豁免說明(「與此方法不同,open(_:options:completionHandler:) 方法不受 LSApplicationQueriesSchemes 要求的限制。只要有 App 能處理該 URL,即使您未宣告該 scheme,系統仍會啟動它」),以及通用連結的備援行為(「若沒有 App 可處理通用連結,iOS 會把它導向使用者的預設瀏覽器,讓對應的網站來回應」)。就目前公開發布的版本而言,該補充中有一句話讀來相當突兀:「對於未宣告的 scheme,此方法一律回傳 false,即使裝置上並未安裝已註冊的 App。」本文關於 false 語意模糊的主張,依據的是回傳值章節,而非這句話。已於 2026 年 7 月 26 日對照 Apple 的文件 JSON 驗證。 

  3. Apple,UIApplication.OpenExternalURLOptionsKey.universalLinksOnly,UIKit 型別屬性參考。自 iOS 10.0(Mac Catalyst 13.1、tvOS 10.0、visionOS 1.0)起可用,截至 2026 年 7 月 26 日不帶任何棄用中繼資料。摘要(「URL 必須是通用連結,且已設定可開啟它的 App」)與本文引用的說明皆出自此頁:「當您把這個鍵放進 open(_:options:completionHandler:) 方法的 options 字典時,只有在該 URL 是有效的通用連結、且裝置上安裝了能開啟它的 App 時,此方法才會開啟該 URL。這個鍵的值是內含布林值的 NSNumber 物件。」 

  4. Apple,EnvironmentValues.openURL,SwiftUI 實例屬性參考。宣告為 @MainActor @preconcurrency var openURL: OpenURLAction,自 iOS 14.0、iPadOS 14.0、Mac Catalyst 14.0、macOS 11.0、tvOS 14.0、visionOS 1.0 與 watchOS 7.0 起可用,截至 2026 年 7 月 26 日沒有棄用中繼資料。本文重製的 openURL(url) { accepted in ... } 範例,以及所引用的預設動作描述,皆出自此頁。 

  5. Apple,OpenURLAction,SwiftUI 結構參考。宣告為 @MainActor @preconcurrency struct OpenURLAction,自 iOS 14.0 起可用,沒有棄用中繼資料。以下內容出自此頁:「系統提供預設的開啟 URL 動作,其行為取決於 URL 的內容。例如,預設動作會盡可能在對應的 App 中開啟通用連結,若無法則以使用者的預設網頁瀏覽器開啟」,以及自訂動作適用於「內建的 Link 視圖、含 markdown 連結的 Text 視圖,或屬性化字串中的連結」這項說明。本文提到的 Result 成員出自子頁面 Apple,OpenURLAction.Result,該頁列出 handleddiscardedsystemActionsystemAction(_:) 以及 iOS 26 新增的型別方法 systemAction(_:prefersInApp:)。 

  6. 作者於 2026 年 7 月 26 日在 macOS 26.5.2 上,對七個已上架的 Apple 平台專案(Reps、Return、Banana List、Ace Citizenship、Water、ResumeGeni 與 Yawara)所做的調查,以 ripgrep 搜尋自有 Swift 程式碼,並排除 build/DerivedData/.build/Pods/Carthage/.swiftpm/SourcePackages/ 與套件的 checkouts/。每個儲存庫的檔案涵蓋範圍都以 findrg 交叉核對(77、57、55、26、34、71 與 143 個檔案),確認沒有漏掉被 gitignore 的自有 Swift 程式碼。canOpenURL 的 0 以三種方式驗證:自有 Swift;加上 --no-ignore --hidden 的 Swift 搜尋,涵蓋建置產物與每一個內嵌套件的 checkout(ResumeGeni 樹中 3,946 個 Swift 檔案,共用的 941Kit 樹中 15,760 個);以及加上 --no-ignore 的全檔案類型搜尋。每一輪都是 0,第三方與內嵌程式碼也包含在內。LSApplicationQueriesSchemesINFOPLIST_KEY_LSApplicationQueriesSchemes 在 192 個 .plist.pbxproj.entitlements.xcconfig 檔案中同樣是 0。23 個呼叫點包含六個 SwiftUI Link 視圖、三個 UIApplication.shared.open 呼叫、12 個 openURL(...) 呼叫(全在 ResumeGeni,來自六處 @Environment(\.openURL) 宣告),以及 Banana List macOS 程式碼中的兩個 NSWorkspace.shared.open 呼叫;計算 Link( 時必須加上單字邊界,否則單純的樣式也會比對到 NavigationLink( 與數個專案自訂的 ...Link( 型別。所有專案中 SFSafariViewControllerWKWebView 的使用次數皆為 0,也沒有任何儲存庫宣告 applinks:。ResumeGeni 的帳務防護在 Profile/ProfileView.swift:1746;Banana List 的存在性檢查與其說明註解分別在 Banana List/SettingsView.swift:321:329,本文引用的「沒有設定用來開啟此文件的應用程式」(原文為 “no application set to open the document”)是該處原始碼註解的措辭,而非 macOS 對話框的逐字轉錄;Return 的「設定」深層連結在 Return/ContentView.swift:222。Reps 的主要 App target 宣告 SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";本文沒有任何平台主張是從 *_DEPLOYMENT_TARGET 鍵推導而來,也沒有任何 App 的完整平台涵蓋範圍是由 SUPPORTED_PLATFORMS 推論——這些專案的 80 個建置組態中,只有 40 個設定了它。 

  7. Apple,openURL(_:),UIKit 實例方法參考。宣告為 func openURL(_ url: URL) -> Bool,於 iOS 2.0 導入,iOS 10.0(Mac Catalyst 13.1)棄用。現行棄用附註出處:「呼叫此方法不會有任何作用。請改用 open(_:options:completionHandler:) 方法。」查閱日期為 2026 年 7 月 26 日。 

  8. Apple,UIKit updates,Apple Developer Documentation。2026 年 6 月的段落含四個子節(General、App life cycle、Drag and drop 與 Text views),並在 App life cycle 之下收錄 scene 生命週期強制規定:「自 iOS 27 起,以最新 SDK 建置的 App 必須使用以 scene 為基礎的生命週期,否則將無法啟動。」2026 年 7 月 26 日搜尋 canOpenURLLSApplicationQueriesSchemes:整頁皆未出現。 

  9. Apple,Xcode 27 Release Notes。2026 年 7 月 26 日搜尋 canOpenURLLSApplicationQueriesSchemes 與 radar 179874781:皆未出現。 

  10. Apple,Describing use of required reason API,Bundle Resources 文件。本文引用的 Apple 指紋辨識定義出處:「您的 App 用來提供核心功能的某些 API……有可能被濫用來存取裝置訊號,試圖辨識裝置或使用者,也就是所謂的指紋辨識。無論使用者是否授權您的 App 進行追蹤,指紋辨識都是不被允許的。」2026 年 7 月 26 日搜尋:canOpenURLLSApplicationQueriesSchemes 皆未出現在該頁,這也是本文「Apple 從未把這個方法與指紋辨識連起來」這項陳述的依據。該頁說明的是申報要求,並把類別清單交給 NSPrivacyAccessedAPIType 的文件。本文對這次棄用的隱私解讀是作者的推論,不是 Apple 陳述的理由。 

  11. Apple,UIApplication,UIKit 類別參考。宣告為 @MainActor class UIApplication,自 iOS 2.0 起可用,沒有棄用中繼資料。open(_:options:completionHandler:) 所繼承的 main actor 隔離,以及 canOpenURL(_:)nonisolated 選擇退出的依據,皆出自此頁。 

  12. Apple,open(_:options:completionHandler:),UIKit 實例方法參考。自 iOS 10.0 起可用,沒有棄用中繼資料,同時提供 completion handler 與 async 兩種宣告形式:func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: (@MainActor @Sendable (Bool) -> Void)? = nil)func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool。以下內容出自此頁:摘要(「嘗試以非同步方式開啟指定 URL 的資源」)、本文引用的啟動行為(「若指定的 URL scheme 由另一個 App 處理,iOS 會啟動該 App 並把 URL 交給它。(啟動 App 會把另一個 App 帶到前景。)若沒有任何 App 能處理指定的 scheme,completion handler 會以 success 參數為 false 的方式被呼叫」),以及那句至今仍推薦被棄用方法的指示:「若要判斷是否安裝了能處理該 URL 的 App,請在呼叫本方法之前先呼叫 canOpenURL(_:) 方法。請務必詳讀該方法的說明,其中有關於註冊您要使用的 scheme 的重要註記。」查閱日期為 2026 年 7 月 26 日。 

  13. Apple,Allowing apps and websites to link to your content,Xcode 文件。以下內容出自此頁:伺服器端關聯要求(「當有人安裝您的 App 時,系統會檢查存放在您網頁伺服器上的一個檔案,以確認您的網站允許該 App 代其開啟 URL。只有您能把這個檔案放上伺服器,藉此保障網站與 App 之間關聯的安全」)、瀏覽器備援(「若對方尚未安裝您的 App,系統會以其預設網頁瀏覽器開啟該 URL,交由您的網站處理」)、App 開啟自家通用連結不會被導回自己的說明(「若您的 App 以上述任一方式開啟指向自家網站的通用連結,該連結不會在您的 App 中開啟」),以及本文描述的同網域 Safari 行為。該頁把 SwiftUI 的 EnvironmentValues.openURL 與 UIKit 的 open(_:options:completionHandler:) 列在會導向通用連結的呼叫之中。 

  14. Apple,Link,SwiftUI 結構參考。宣告為 @MainActor @preconcurrency struct Link<Label> where Label : View,自 iOS 14.0、macOS 11.0 與 watchOS 7.0 起可用,沒有棄用中繼資料。本文引用的預設行為出處:「當使用者點按 Link 時,預設行為取決於 URL 的內容。例如,SwiftUI 會盡可能在對應的 App 中開啟通用連結,若無法則以使用者的預設網頁瀏覽器開啟。」 

  15. Apple,OpenURLAction.callAsFunction(_:completion:),SwiftUI 實例方法參考。宣告為 @MainActor @preconcurrency func callAsFunction(_ url: URL, completion: @escaping (Bool) -> Void)。本文引用的 completion 語意出處:「此閉包會在方法判斷出能否開啟該 URL 之後被呼叫,但可能早於 URL 完全開啟。閉包接收一個布林值,表示此方法是否能夠開啟該 URL。」同層的 callAsFunction(_:) 只接受一個 URL。查閱日期為 2026 年 7 月 26 日。 

  16. Apple,Build settings reference,Xcode 文件。2026 年 7 月 26 日搜尋 INFOPLIST_KEY_LSApplicationQueriesSchemes:該設定並未出現,而 INFOPLIST_KEY_LSApplicationCategoryTypeINFOPLIST_KEY_LSBackgroundOnlyINFOPLIST_KEY_LSSupportsOpeningDocumentsInPlaceINFOPLIST_KEY_LSUIElement 都在。自行合併屬性清單的建置步驟仍可能注入這個鍵,因此它的缺席只代表在儲存庫裡搜尋是正確的第一步,而不是一次完整的稽核。 

  17. Apple,Launch Services Keys,Information Property List Key Reference(Apple 封存文件)。該鍵定義的出處:「LSApplicationQueriesSchemes(陣列 - iOS)指定您希望 App 能搭配 UIApplication 類別的 canOpenURL: 方法使用的 URL scheme。每一個您希望搭配 canOpenURL: 方法使用的 URL scheme,都要以字串形式加入此陣列。」該頁指出這個鍵「在 iOS 9.0 與之後的版本受支援」,並未提及任何項目上限。這份封存文件正是 canOpenURL(_:) 自身說明中那個連結的目的地。此鍵在 Apple 現行的 Information Property List 參考中沒有頁面,已於 2026 年 7 月 26 日驗證:documentation/bundleresources/information-property-list/lsapplicationqueriesschemes.json 回傳 HTTP 404,而同層的 LS* 鍵頁面(例如 lsapplicationcategorytypelsbackgroundonly)回傳 200。該參考本身的索引 JSON 兩邊都不能當作判準,因為它只列舉七個頂層鍵群組,沒有點名任何個別的 LS* 鍵;擁有實際頁面的 lsapplicationcategorytype 同樣不在其中。 

  18. Apple,OpenURLAction.callAsFunction(_:prefersInApp:),SwiftUI 實例方法參考。宣告為 @MainActor @preconcurrency func callAsFunction(_ url: URL, prefersInApp: Bool),自 iOS 26.0、iPadOS 26.0、Mac Catalyst 26.0、macOS 26.0、tvOS 26.0、visionOS 26.0 與 watchOS 26.0 起可用。它列在 OpenURLAction 頁面的 Instance Methods 之下,而非 Calling the action 之下。它接受的是單一布林值而非選項字典,因此這是第三個、也是最後一個呼叫簽章,三者都不接受 universalLinksOnly。查閱日期為 2026 年 7 月 26 日。 

  19. Apple,App Store Review Guidelines。2026 年 7 月 26 日搜尋 LSApplicationQueriesSchemescanOpenURL 與「25 entries」:三者出現次數皆為 0。引用此來源是為了支持「並不存在送審規則」這項不存在性判斷,而非任何肯定性主張。 

相關文章

On Demand Resources 已棄用:Background Assets 的代價

Apple 用 13 個字宣告 ODR 棄用。替代方案分成三條路線、設下 iOS 26 門檻,還把系統原本代勞的磁碟管理丟回給您。

8 分鐘閱讀

iOS 27 啟動畫面規則:四個鍵擇一,否則退件

以 iOS 27 SDK 建置的 App 必須宣告啟動畫面,否則 App Store 會直接退件。本文說明這四個鍵,以及如何稽核 plist 由建置系統自動產生的 target。

5 分鐘閱讀