← 所有文章

canOpenURL 已弃用:改用什么方法

Apple 用三句话,送走了一个自 iOS 3.0 起就随系统发布的方法:”canOpenURL: 已弃用。请直接尝试打开该 URL 并处理可能的失败,而不是先做校验。改用 universal links 而非自定义 URL scheme,可以彻底消除这类校验的必要。”1

这条记录挂着 radar 179874781,位于 UIKit 的 Deprecations 小节,紧挨着那条”不改就起不来”的场景生命周期强制要求。1 邻居那条写明了后果。canOpenURL 这条一个字也没写。

三句话其实是三条独立指令,而只有第二句回答了开发者真正关心的问题:那我该写什么?Apple 给出的答案改变的不只是名字,还有调用本身的形状。

TL;DR

  • Apple 在 iOS、iPadOS、Mac Catalyst、tvOS 和 visionOS 的 27.0 上弃用了 canOpenURL(_:),附带提示为”优先尝试打开 URL 并处理失败”。2 没有给出移除版本,也没有任何运行时后果。
  • 真正有牙齿的变化是一个数字,而且它藏在这个被弃用方法自己的讨论段落里,任何发行说明都没提:”针对 iOS 27 或更高版本链接的应用,LSApplicationQueriesSchemes 键最多只能有 25 条。”此前是 50 条。2 触发条件是您链接所用的 SDK。
  • 机械式的替换就是 open(_:options:completionHandler:) 及其布尔返回值,无需任何白名单声明:”open(_:options:completionHandler:) 方法不受 LSApplicationQueriesSchemes 要求的约束。”2
  • 有一类模式找不到替代品:canOpenURL 是在您绘制任何界面之前就给出答案,而”直接尝试”是用后果来回答——成功意味着另一个应用被推到了前台。2 幸存下来的是 universalLinksOnly,这是 iOS 10 起就有、Apple 至今没有弃用的一个 open 选项,它”仅当该 URL 是有效的 universal link 且设备上装有能够打开它的应用时,才打开这个 URL”。3
  • SwiftUI 从未提供过这个方法,它的完成回调布尔值本来表达的就是”能否打开”,而不是”是否已打开”。415 在我已上架的 7 个应用、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 或更高版本链接的应用,LSApplicationQueriesSchemes 键最多 50 条。针对 iOS 27 或更高版本链接的应用,LSApplicationQueriesSchemes 键最多 25 条。”2

白名单活得比它唯一服务的那个方法的弃用还久,而对用新 SDK 链接的应用来说,上限直接砍半。Apple 只把上限写出来,然后就没了。第 25 条之后会发生什么,一个字都没有;把这段旁注的两半合起来读,能推出一个大概率的答案,但那不是文档写明的:未声明的 scheme 永远返回 false,因此一个声明了 40 个 scheme 的应用在重新链接后,很可能会开始把其中 15 个当成”不存在”。至于是哪 15 个,或者截断是否真的按数组顺序进行,Apple 从未说明。请把这套机制当成推断,把风险当成真的——因为无论是应用没装还是声明没写,false 看起来一模一样。

同一段讨论的下一自然段还有第二个限制,两者是二选一的关系,而非叠加。Apple 用您链接所依据的 SDK 来区分它们:”如果您的应用链接的是更早版本的 iOS,但运行在 iOS 9.0 或更高版本上,则最多可以调用此方法 50 次。达到该上限后,后续调用一律返回 false。如果用户重新安装或升级应用,iOS 会重置该上限。”2 请读一下条件。这份配额属于在 iOS 9 之前链接的应用,而那是今天不可能再上架的东西。所有当下的应用都落在另一个分支上,也就是声明条数上限——正是 Apple 从 50 砍到 25 的那个。两者最终都花光在同一个静默的 false 上,而且都只写在 Apple 刚刚弃用的这个方法内部。

隐私层面的解读同样是推断。canOpenURL 一直是探测用户装了哪些应用的标准手段——那是设备信号,而不是链接检查——而 Apple 自己对指纹识别的定义,涵盖了”被滥用于获取设备信号、试图识别设备或用户”的 API。10 Apple 从未把两者连起来:该方法不在任何”必需理由”API 清单上,而发行说明、弃用提示和符号页面给出的理由,也纯粹是机械层面的。1210 查询上限砍半确实吻合隐私叙事,只是 Apple 没有把它写下来。

canOpenURL 到底承诺了什么

true 是对下一次调用的担保,而不是对这个 URL 的描述:”当此方法返回 true 时,iOS 保证以相同 URL 后续调用 open(_:options:completionHandler:) 方法,将成功启动一个能够处理该 URL 的应用。”2false 从设计上就是含混的,它有两种成因,且无从分辨是哪一种触发的:”如果设备上没有安装注册处理该 URL scheme 的应用,或者您没有在 Info.plist 文件中声明该 URL 的 scheme,则返回 false。”2

有三件事它从来没回答过,却很容易被想当然:”返回值并不表明该 URL 是否有效、指定资源是否存在,就 universal link 而言,也不表明设备上是否安装了注册响应该 universal link 的应用。”2 第三点与 Apple 推荐的迁移路径直接相关,后文还会回到它。

让这个方法在结构上真正好用的那条性质,恰恰是无物可以替代的。canOpenURL 的声明带着 nonisolated 关键字,Apple 也把话说得很直白:”您可以在非主线程上安全地调用此方法。”2 一个能在主 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 要求的约束。只要有应用能够处理该 URL,系统就会启动它,即使您没有声明该 scheme。”2 完成迁移的代码库会把这个键整个删掉,25 条的问题也随之消失。

有两处差异在重写之后依然存在,而且都是结构性的。

第一处是隔离与时机。UIApplication 的声明是 @MainActor class UIApplication,因此 open 跑在主 actor 上,而它的摘要也把这件事说清楚了:”尝试异步打开指定 URL 处的资源。”1112 那个同步的、可以脱离主线程的布尔值没有了,于是散落在模型层、后台队列或非隔离辅助方法里的”先校验再打开”逻辑,要么搬家,要么改成 async

第二处是,您没法再悄悄地问了。尝试成功时,另一个应用就已经在前台了:”iOS 会启动那个应用并把 URL 传给它。(启动该应用会把它带到前台。)”12 答案是动手之后的副作用。失败的情形,Apple 没有记录任何可见反馈,只说”完成回调被调用时 success 参数为 false“;但凡需要在动手之前拿到答案的模式,都失去了依托:只在目标应用存在时才显示”在其他应用中打开”这一行、按已安装情况给分享面板排序,或者在若干竞争目标之间挑一个默认项。12

Apple 自己的文档也还没跟上。open(_:options:completionHandler:) 页面至今仍在指示:”要判断是否安装了能够处理该 URL 的应用,请在调用本方法之前先调用 canOpenURL(_:) 方法。”12 替代方案的页面,推荐的是被弃用的那个方法。

幸存下来的存在性检查

有一条见于文档的路径,能用”尝试”的方式回答 canOpenURL 的问题,而当答案是否定时,用户什么也看不到。UIApplication.OpenExternalURLOptionsKey.universalLinksOnly 自 iOS 10 起就存在,Apple 没有弃用它,其行为恰恰就是一次存在性检查:”仅当该 URL 是有效的 universal link 且设备上装有能够打开它的应用时,此方法才打开该 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 这一分支才是价值所在。没有浏览器被拉起,没有应用被切到前台,调用方拿到了过去由 canOpenURL 告诉它的信息。代价藏在这个选项的名字里:不加它,只要有浏览器接得住,https 的尝试就会成功——因为”如果没有应用可以处理某个 universal link,iOS 会把它交给用户的默认浏览器,让关联网站来响应”。2 这个选项用一个有意义的布尔值,换掉了让 universal links 用起来舒服的那层兜底。Apple 把该值的类型写成”包含布尔值的 NSNumber 对象”,Swift 的 true 通过 Objective-C 桥接即可满足。3

代价是架构层面的,也正是 Apple 那第三句话。universal links 需要一次双向关联:”当有人安装您的应用时,系统会检查存放在您 Web 服务器上的一个文件,以验证您的网站允许您的应用代表它打开 URL。只有您能把这个文件放到您的服务器上,从而确保网站与应用之间关联的安全。”13 一个由您掌控的服务器文件,恰恰是自定义 scheme 从不需要的东西——这也是为什么这套迁移对自家应用矩阵成立,而对一个只公布 theirapp:// 的第三方毫无作用。另有两处文档写明的意外随之而来:您的应用打开自己的 universal link 并不会跳回自己的应用;在 Safari 中浏览您的网站时点击同域链接,也会留在 Safari 里。13

前文那第三点,落点正在这里:对于 universal link,canOpenURL 本来也回答不了存在性问题。2 迁移是把存在性探测拿掉,而不是把它迁过去——因为一次普通的 https 尝试,无论应答的是应用还是仅仅是网站,都算成功。一种只因自定义 scheme 泄露了安装状态才成立的模式,会随着 scheme 一起离场,而 Apple 的发行说明正是把这份损失当作迁移的理由。

SwiftUI 从来就没有这个方法

SwiftUI 这一侧篇幅很短,消息也是好消息。EnvironmentValues.openURLOpenURLActionLink 三者的可用性都从 iOS 14.0 起算,均无弃用标记,而且都没有暴露任何有效性检查。4514

SwiftUI 提供的,正是 Apple 如今希望到处推行的那套”先尝试、再处理”语义,而完成参数的文档说明,这个布尔值回答的就是那个老问题:”方法在判断出能否打开该 URL 之后调用的闭包,调用时机可能早于 URL 被完全打开。该闭包接收一个布尔值,表示方法能否打开这个 URL。”15 是能否打开,不是是否已打开。Apple 自己的示例就把它打印了出来:

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

Link 根本不暴露布尔值,而是交给环境处理,那里的默认行为已经实现了 universal link 的那套逻辑:”默认行为会尽可能在关联的应用中打开 Universal Link,否则在用户的默认浏览器中打开。”5 一个返回 .handled.discarded.systemAction 的自定义 OpenURLAction,会拦截每一个 Link,以及从该环境读取该行为的 Text 中的每一个 markdown 链接。5

在规划一次纯 SwiftUI 的迁移之前,有一个缺口需要留意。OpenURLAction 没有选项字典。它的调用签名只有 callAsFunction(_:)callAsFunction(_:completion:),以及 iOS 26 新增的 callAsFunction(_:prefersInApp:),三者都不接受 universalLinksOnly1518 想要一次没有副作用的存在性检查,仍然只能去调 UIApplication.shared.open

七个应用,463 个文件,零次调用

在评点别人的代码之前,我先审了自己的作品集,本以为会得到一份迁移清单。结果没有任何东西需要迁移。6

应用 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

这个零经受住了三轮逐步放宽范围的检索,一直放到每个仓库里所有内置的 Swift 包和所有文件类型。6 192 个 .plist.pbxproj.entitlements.xcconfig 文件中,同样没有任何一处声明被查询的 scheme——这也顺理成章:一个不调用 canOpenURL 的应用,没有理由去声明。

原因很平淡,而且大概相当普遍。23 个调用点里,9 个打开的是法律文档,4 个跳转到应用自己的 Web 产品,2 个从弹窗打开 census.gov 好让用户找到自己的议员,2 个打开由 API 返回的职位链接,2 个是仅限 macOS 的 file: 导出,还有 1 个是藏在”需要健康数据访问权限”弹窗后面的 UIApplication.openSettingsURLString。这 20 个没有一个在查询别的应用,因为目标要么是 Safari 一定会接管的 https,要么是系统 URL。剩下的 3 个,承载了全部有意思的行为。

唯一一处”打开前先校验”的代码,做的并不是这次弃用所针对的事:ResumeGeni 的计费流程会向 /api/me/portalPOST,然后对服务器返回的 URL 检查 url.scheme == "https",以防被入侵或有缺陷的服务器丢给应用一个 file: 或自定义 scheme 的 URL。6 Apple 的说明针对的是校验目标应用是否存在,对是否该信任远端输入只字未提,因此删掉这道防线是安全性倒退,不是迁移。

另外两处是 Banana List 的 macOS 交接逻辑,其中一处是整个作品集里唯一一次货真价实的存在性检查。应用在把打包的 .mcpb 扩展交给 Launch Services 之前,会先问 NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anthropic.claudefordesktop") != nil,旁边还有一个按钮指向 claude.com/download,供尚未安装的人使用。代码注释点明了动机:不做这个检查,macOS 就会弹出它自己的”没有设置用于打开此文稿的应用程序”对话框。6 平台不同,但这是整次审计中最有用的一个数据点,因为它在真实代码里抓住了”先尝试再处理”的失败形态。一次失败的尝试并不总是静默的;当系统替您开口时,用户读到的是系统报错,而不是您准备的兜底。

没有任何仓库声明 applinks:,所以这里没有一个应用支持 universal links,Apple 那套架构级修法的账,我这边同样没付。6 一个自定义 scheme 的成本是 Info.plist 里的一条;一个 universal link 的成本是 Web 服务器上的一个文件、一项 entitlement,外加一个由您掌控的域名。

这也指出了真正的活儿在哪里:不在自有应用代码里,而在那些按已安装应用重排的分享面板、”用其他应用打开”的选择器,以及归因用的 SDK 上——干净的仓库并不能排除其中任何一项。

这一项的审计,比 27 周期里的其他条目都轻松。启动屏幕键来自构建设置,文本检索完全奈何不了它;LSApplicationQueriesSchemes 则在 Apple 的构建设置参考里没有 INFOPLIST_KEY_ 对应项,所以先 grep 一遍是正确的第一步,而数组里出现的是谁家的 scheme,会告诉您哪个依赖想要这个答案。16

常见问题

iOS 27 里 canOpenURL 会失效吗?

不会,Apple 也没说什么时候会。该符号带着 deprecatedAt 27.0,而页面的其他部分——返回值约定、对后续 open 调用的担保、声明条数上限、遗留调用配额——统统仍在文档中。2 发行说明、符号页面和 Xcode 27 发行说明里,都没有出现移除版本。129 请按”一条警告加缓慢衰减”来规划,而不是按某天突然断裂。

如果我必须知道应用是否已安装,那该写什么?

这取决于目标是不是您自己的。如果是,就发布一个 universal link,并给 open 传入 universalLinksOnly——Apple 的文档说明,只有当 URL 是有效的 universal link 且有已安装的应用接得住时才会打开,因此 false 意味着什么都没打开、什么都没切换。3 代价是 Web 服务器上那份双向关联文件。13 如果第三方只公布了自定义 scheme,那就没有替代品:canOpenURL 依然会回答,依然需要 Info.plist 声明,而针对 iOS 27 或更高版本链接的应用,这份声明的上限砍半到 25 条。2 SwiftUI 代码在这件事上无需改动,因为 openURLLink 从未提供过这项检查。414

我还需要 LSApplicationQueriesSchemes 吗?

只有 canOpenURL 才需要。Apple 的 Launch Services 文档完全是围绕这个被弃用的方法来定义该键的:它”指定您希望应用能够配合 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 的说明针对的是应用是否存在的校验,而非远端输入,可这两者在 grep 里长得一模一样。

给交付应用矩阵深链或归因 SDK 的团队: - 需要”是否存在”这个答案时,请伸手去拿 universalLinksOnly 而不是 canOpenURL,并为它所需的关联域名文件留出预算。313 它是文档中唯一一种在应用缺失时不给用户留下任何可见动静的检查。 - 在用 iOS 27 的 SDK 链接之前,先数一遍您的 LSApplicationQueriesSchemes 数组。超出 25 条的部分都落在文档给出的上限之外,而 Apple 为未声明 scheme 给出的症状是一个平淡的 false——它读起来和应用没装一模一样。2

给发布负责人: - 不要把这次弃用排成发布阻塞项。没有移除日期、没有运行时后果,意味着它的优先级排在会导致拒审的启动屏幕键、会让应用起不来的场景生命周期强制要求,以及会让构建失败的 @State之后。 - 把 25 条这个上限当成唯一有真实触发条件的事项,因为它取决于您链接所用的 SDK,而不是用户运行的系统版本。2


27 周期里的变化分量各不相同,读懂分量,才能把这个周期花在刀刃上:启动屏幕键会挡住提交,场景生命周期强制要求会让应用起不来,@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 并处理可能的失败,而不是先做校验。改用 universal links 而非自定义 URL scheme,可以彻底消除这类校验的必要。”该条目紧随场景生命周期条目(radar 141837548):”使用最新 SDK 构建的应用必须采用基于场景的生命周期,否则将无法启动。”于 2026 年 7 月 26 日对照 Apple 文档 JSON 核验,因为 HTML 页面是通过 JavaScript 渲染的。同一份 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”,两个字符串的出现次数均为零。 

  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 并处理可能的失败,而不是先做校验。改用 universal links 而非自定义 URL scheme,可以彻底消除这类校验的必要。”以下内容的出处均为该页面:返回值文档(”如果设备上没有安装注册处理该 URL scheme 的应用,或者您没有在 Info.plist 文件中声明该 URL 的 scheme,则返回 false;否则返回 true“)、担保(”当此方法返回 true 时,iOS 保证以相同 URL 后续调用 open(_:options:completionHandler:) 方法,将成功启动一个能够处理该 URL 的应用。返回值并不表明该 URL 是否有效、指定资源是否存在,就 universal link 而言,也不表明设备上是否安装了注册响应该 universal link 的应用”)、线程注记(”您可以在非主线程上安全地调用此方法”)、本文引用的白名单旁注及其两个上限(”针对 iOS 15 或更高版本链接的应用,LSApplicationQueriesSchemes 键最多 50 条。针对 iOS 27 或更高版本链接的应用,LSApplicationQueriesSchemes 键最多 25 条”)、本文引用的运行时调用配额(”如果您的应用链接的是更早版本的 iOS,但运行在 iOS 9.0 或更高版本上,则最多可以调用此方法 50 次。达到该上限后,后续调用一律返回 false。如果用户重新安装或升级应用,iOS 会重置该上限”)、豁免条款(”与此方法不同,open(_:options:completionHandler:) 方法不受 LSApplicationQueriesSchemes 要求的约束。只要有应用能够处理该 URL,系统就会启动它,即使您没有声明该 scheme”),以及 universal link 的兜底行为(”如果没有应用可以处理某个 universal link,iOS 会把它交给用户的默认浏览器,让关联网站来响应”)。旁注中有一句按已发布文本读起来颇为费解:”对于未声明的 scheme,此方法始终返回 false,即使设备上并未安装已注册的应用。”本文关于 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 必须是 universal links,并且已配置可打开它们的应用”)与讨论内容出处:”当您在 open(_:options:completionHandler:) 方法的选项字典中包含此键时,仅当该 URL 是有效的 universal link 且设备上装有能够打开它的应用时,此方法才打开该 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 的内容。例如,默认行为会尽可能在关联的应用中打开 Universal Link,否则在用户的默认浏览器中打开”,以及自定义行为适用于”内置的 Link 视图、带 markdown 链接的 Text 视图,或属性字符串中的链接”这一说明。本文提到的 Result 成员来自子页面 Apple,OpenURLAction.Result,其中枚举了 handleddiscardedsystemActionsystemAction(_:) 以及 iOS 26 新增的类型方法 systemAction(_:prefersInApp:)。 

  6. 作者对七个已上架 Apple 平台项目(Reps、Return、Banana List、Ace Citizenship、Water、ResumeGeni 和 Yawara)的调查,于 2026 年 7 月 26 日在 macOS 26.5.2 上完成,使用 ripgrep 检索自有 Swift 代码,排除 build/DerivedData/.build/Pods/Carthage/.swiftpm/SourcePackages/ 以及包的 checkouts/。文件覆盖率按仓库用 findrg 逐一对账(分别为 77、57、55、26、34、71 和 143 个文件),以确认没有遗漏被 gitignore 掉的自有 Swift 文件。canOpenURL 的零结果通过三种方式核验:自有 Swift;带 --no-ignore --hidden 的 Swift 检索,涵盖构建产物和所有内置包检出(ResumeGeni 目录树中 3,946 个 Swift 文件,共享的 941Kit 目录树中 15,760 个);以及带 --no-ignore 的全文件类型检索。每一轮都是零,包括第三方与内置代码。LSApplicationQueriesSchemesINFOPLIST_KEY_LSApplicationQueriesSchemes 在 192 个 .plist.pbxproj.entitlements.xcconfig 文件中的结果同样为零。23 个调用点包括 6 个 SwiftUI Link 视图、3 次 UIApplication.shared.open 调用、12 次 openURL(...) 调用(全部在 ResumeGeni 中,来自 6 处 @Environment(\.openURL) 声明),以及 Banana List macOS 代码中的 2 次 NSWorkspace.shared.open 调用;统计 Link( 需要加词边界,否则裸模式还会匹配到 NavigationLink( 和若干项目自定义的 ...Link( 类型。所有项目中 SFSafariViewControllerWKWebView 的使用次数均为零,也没有任何仓库声明 applinks:。ResumeGeni 的计费防护位于 Profile/ProfileView.swift:1746;Banana List 的存在性检查及其解释性注释位于 Banana List/SettingsView.swift:321:329,本文引用的”没有设置用于打开此文稿的应用程序”一语是该源码注释的措辞,而非对 macOS 对话框原文的转录;Return 的设置深链位于 Return/ContentView.swift:222。Reps 的主应用 target 声明了 SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";本文的任何平台结论都不来自 *_DEPLOYMENT_TARGET 键,也没有任何应用的完整平台覆盖是从 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 开发者文档。2026 年 6 月小节包含四个子节(General、App life cycle、Drag and drop 和 Text views),其中 App life cycle 下收录了场景生命周期强制要求:”自 iOS 27 起,使用最新 SDK 构建的应用必须使用基于场景的生命周期,否则将无法启动。”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 指纹识别定义出处:”您的应用用于交付核心功能的某些 API……有可能被滥用于获取设备信号、试图识别设备或用户,也就是所谓的指纹识别。无论用户是否授予您的应用跟踪权限,指纹识别都是不被允许的。”2026 年 7 月 26 日检索:canOpenURLLSApplicationQueriesSchemes 未出现在该页面上,这正是本文”Apple 从未把该方法与指纹识别联系起来”这一说法的依据。该页面描述的是申报要求,并把类别清单交由 NSPrivacyAccessedAPIType 文档说明。本文对这次弃用的隐私解读属于作者推断,而非 Apple 陈述的理由。 

  11. Apple,UIApplication,UIKit 类参考。声明为 @MainActor class UIApplication,自 iOS 2.0 起可用,不带弃用元数据。这是 open(_:options:completionHandler:) 所继承、而 canOpenURL(_:)nonisolated 主动退出的主 actor 隔离的出处。 

  12. Apple,open(_:options:completionHandler:),UIKit 实例方法参考。自 iOS 10.0 起可用,不带弃用元数据,同时提供完成回调与 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 由另一个应用处理,iOS 会启动那个应用并把 URL 传给它。(启动该应用会把它带到前台。)如果没有应用能够处理指定的 scheme,完成回调被调用时 success 参数为 false“),以及那条至今仍在推荐被弃用方法的指示:”要判断是否安装了能够处理该 URL 的应用,请在调用本方法之前先调用 canOpenURL(_:) 方法。请务必阅读该方法的说明,其中有关于注册您想使用的 scheme 的重要注记。”查阅日期 2026 年 7 月 26 日。 

  13. Apple,Allowing apps and websites to link to your content,Xcode 文档。以下内容出处:服务器端关联要求(”当有人安装您的应用时,系统会检查存放在您 Web 服务器上的一个文件,以验证您的网站允许您的应用代表它打开 URL。只有您能把这个文件放到您的服务器上,从而确保网站与应用之间关联的安全”)、浏览器兜底(”如果对方没有安装您的应用,系统会在其默认浏览器中打开该 URL,交由您的网站处理”)、应用打开自身 universal link 不会跳回自身的注记(”如果您的应用使用上述任一方式打开指向您网站的 universal link,该链接不会在您的应用中打开”),以及本文描述的同域 Safari 行为。该页面把 SwiftUI 的 EnvironmentValues.openURL 和 UIKit 的 open(_:options:completionHandler:) 列在会路由 universal links 的调用之中。 

  14. Apple,Link,SwiftUI 结构体参考。声明为 @MainActor @preconcurrency struct Link<Label> where Label : View,自 iOS 14.0、macOS 11.0 和 watchOS 7.0 起可用,不带弃用元数据。本文引用的默认行为出处:”当用户轻点或点击 Link 时,默认行为取决于 URL 的内容。例如,SwiftUI 会尽可能在关联的应用中打开 Universal Link,否则在用户的默认浏览器中打开。” 

  15. Apple,OpenURLAction.callAsFunction(_:completion:),SwiftUI 实例方法参考。声明为 @MainActor @preconcurrency func callAsFunction(_ url: URL, completion: @escaping (Bool) -> Void)。本文引用的完成语义出处:”方法在判断出能否打开该 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(Array - iOS)指定您希望应用能够配合 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,而 lsapplicationcategorytypelsbackgroundonly 等同级 LS* 键页面返回 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”:三者出现次数均为零。引用此项是为了支撑”不存在提交规则”这一缺席性判断,而非任何肯定性主张。 

相关文章

On Demand Resources 已弃用:Background Assets 的代价

苹果用 13 个词宣告 ODR 弃用。替代方案却分成三条路,把最低部署目标抬到 iOS 26,还把过去由系统代劳的磁盘管理原样扔回给您。

6 分钟阅读

iOS 27 启动屏幕新规:四个键,否则被拒

使用 iOS 27 SDK 构建的应用必须声明启动屏幕,否则 App Store 会拒绝。本文讲清这四个键,以及如何审查使用自动生成 plist 的 target。

4 分钟阅读