WWDC Quick Look 💓 By SwiftGGTeam
What's new in Universal Links

What's new in Universal Links

观看原视频

Highlight

Universal Links 允许用户点击一个 HTTPS 链接时直接打开你的 App 而非浏览器。它的核心机制是:你的 Web 服务器根目录或 .well-known 目录下放置一个 apple-app-site-association(AASA)文件,声明哪些 URL 路径应该由哪个 App 处理。

核心内容

Universal Links 的目标很直接:同一个 HTTPS 链接,既能代表网页内容,也能把用户带进 App 里的对应页面。系统要求 App 带上 Associated Domains entitlement,网站提供 apple-app-site-association 文件。App 声明自己信任哪些域名,网站声明哪些 App 可以处理哪些路径,两边合起来形成一组安全关联。

2020 年的问题出在规模上。一个餐饮 App 可能要支持上百个国家、多种语言和多种商品。旧的 AASA 路径规则靠 *? 做匹配,语言、地区、商品一组合,规则数量会快速膨胀。演讲里的例子只考虑两位语言代码、两位地区代码和四种商品,模式数量就达到 180 万,占用超过 27 MB。

Apple 在这场 session 里补了三块能力。第一,Universal Links 扩展到 watchOS,并获得 SwiftUI 支持。第二,AASA 的 components 匹配新增大小写、Unicode、默认值和 substitution variables(替换变量),用更少规则表达复杂路径。第三,Associated Domains 数据从设备直连网站改成由 Apple CDN 缓存和分发,安装后的首次可用性更稳定。

这也改变了调试方式。开发和企业内部分发不一定能让测试服务器暴露在公网。macOS Big Sur 和 iOS 14 引入 alternate modes(替代模式):developer mode 面向开发调试,managed mode 面向 MDM 管理设备。开发者可以在 entitlement 里为特定域名追加 mode 查询项,让系统绕过 CDN,直接连接受控服务器。

详细内容

00:21)Universal Links 是 HTTP 或 HTTPS URL,代表网页和 App 内同一份内容。采用它需要两个配置:App 里的 Associated Domains entitlement,以及网站上的 AASA JSON 文件。Apple 明确建议还在使用 custom URL schemes 的项目尽快迁移,因为 custom URL schemes 不再是推荐方式。

App entitlement:
  Associated Domains -> applinks:example.com

Web server:
  /.well-known/apple-app-site-association
  or /apple-app-site-association

Association:
  entitlement names the web domain
  AASA names the app application identifier

关键点:

  • applinks:example.com 是 App 侧声明,表示这个 App 要处理该域名下的 Universal Links。
  • apple-app-site-association 是网站侧声明,描述哪些路径交给哪些 App。
  • entitlement 和 AASA 必须互相指向,系统才会把链接交给 App。
  • 逐字稿提到 AASA 可以放在网站根目录或 .well-known 目录。

01:27)watchOS 是 2020 年新增的平台。WatchKit 的入口和 UIKit、AppKit 不同,Associated Domains entitlement 也要加在 WatchKit extension 上,而不是包含它的 WatchKit app 上。

UIKit incoming link:
  application:continueUserActivity:restorationHandler:

UIKit outgoing link:
  UIApplication.openURL

WatchKit incoming link:
  WKExtensionDelegate.handle(_ userActivity:)

WatchKit outgoing link:
  WKExtension.openSystemURL

关键点:

  • WKExtensionDelegate.handle(_ userActivity:) 负责在 watchOS 上接收传入的 Universal Link。
  • WKExtension.openSystemURL 用来从 watchOS App 打开另一个应用的 Universal Link。
  • 如果目标应用没有安装,watchOS 会提示用户转到配对的 iPhone 继续。
  • 使用 UIScene 的 iOS App 要实现 scene 相关的 delegate 方法,而不是只实现 UIApplicationDelegate 方法。

让 AASA 匹配规则变短

05:14)旧规则支持 *?* 贪婪匹配零个或多个字符,? 匹配一个字符,?* 表示至少一个字符。iOS 13.5 和 macOS Catalina 10.15.5 开始,components 字典可以加 caseSensitive: false,让同一条规则覆盖大小写变化。

{
  "components": [
    {
      "/": "/sourdough?*",
      "caseSensitive": false
    }
  ]
}

关键点:

  • "/" 表示这条 component 匹配 URL path。
  • "/sourdough?*" 要求路径以 sourdough 开头,并且后面至少有一个字符。
  • "caseSensitive": false 关闭大小写敏感匹配。
  • 这项能力适合修掉用户手动输入或外部渠道生成链接时的大小写差异。

06:11)URL 本身是 ASCII。包含中文、法文重音字符等 Unicode 内容时,浏览器看到的通常是 percent encoding。iOS 14 和 macOS Big Sur 新增 percentEncoded: false,让 AASA 规则按 Unicode code points 匹配。

{
  "defaults": {
    "caseSensitive": false,
    "percentEncoded": false
  },
  "components": [
    {
      "/": "/zh_CN/蚂蚁上树"
    },
    {
      "/": "/fr_CA/pâté"
    }
  ]
}

关键点:

  • "percentEncoded": false 让规则直接写 Unicode 字符。
  • "defaults" 可以放在 components 同级,给同一个 components 数组里的规则提供默认值。
  • "defaults" 也可以放在 details 同级,对这个域名下的 Universal Links 生效。
  • 这能减少重复键,让每条 path 规则只保留真正不同的部分。

用替换变量压缩组合爆炸

09:43)substitution variables 是这场 session 的核心更新。它把一组字符串命名成变量,再在 path pattern 里引用。演讲内置了 $(lang)$(region)$(alpha)$(digit) 等变量,也允许开发者在 substitutionVariables 里定义自己的业务变量。

{
  "applinks": {
    "substitutionVariables": {
      "food": ["sourdough", "baguette", "noodles", "dumplings"]
    },
    "details": [
      {
        "components": [
          {
            "/": "/$(lang)_$(region)/$(food)"
          }
        ]
      }
    ]
  }
}

关键点:

  • "food" 是业务变量名,值是允许匹配的商品列表。
  • $(lang)$(region) 是系统内置变量,分别来自 Foundation Locale 支持的语言和地区代码。
  • $(food) 引用开发者自己定义的变量。
  • 变量值可以包含 ?* 通配符,但不能继续引用其他变量。

12:49)变量不等于放弃精确控制。演讲用加拿大市场举例:先把 CA 地区排除,再为加拿大定义单独菜单。地区例外、语言翻译和 Unicode 商品名可以共存。

{
  "applinks": {
    "substitutionVariables": {
      "food": ["sourdough", "baguette", "noodles", "dumplings"],
      "Canadian food": ["poutine", "tourtière"]
    },
    "details": [
      {
        "defaults": {
          "percentEncoded": false
        },
        "components": [
          {
            "/": "/$(lang)_CA/$(food)",
            "exclude": true
          },
          {
            "/": "/$(lang)_CA/$(Canadian food)"
          },
          {
            "/": "/$(lang)_$(region)/$(food)"
          }
        ]
      }
    ]
  }
}

关键点:

  • "exclude": true 表示匹配该 pattern 的 URL 不应该由这个 App 作为 Universal Link 打开。
  • 排除规则放在加拿大专用规则前面,用来挡住普通菜单在加拿大继续生效。
  • "Canadian food" 是另一个业务变量,可以和 "food" 有重叠值。
  • tourtière 这类带重音字符的值依赖 percentEncoded: false 才能直接写在规则里。

CDN 和 alternate modes

16:31)iOS 14 和 macOS Big Sur 开始,设备安装 App 后不再直接向每个网站下载 AASA。系统会连接 Apple 为 Associated Domains 准备的 CDN。CDN 可以同时从多个网站拉取 AASA,缓存结果,再通过一条连接把数据发给设备。

Before:
  device -> each associated web server -> AASA files

Starting with iOS 14 and macOS Big Sur:
  device -> Apple Associated Domains CDN -> cached AASA data

关键点:

  • CDN 能降低开发者服务器的请求量。
  • 设备可以通过单个 HTTP/2 连接拿到多个域名的 Associated Domains 数据。
  • 用户刚安装 App 后,Universal Links 更容易在首次启动阶段就可用。
  • 公网不可达的测试服务器需要 alternate modes 处理。

18:37)alternate modes 有两种:developer mode 用于开发和测试,managed mode 用于通过 MDM profile 安装的 App。developer mode 要用户主动开启。iOS、watchOS 和 tvOS 在 Developer Settings 里打开 Associated Domains Development;macOS 通过命令行开启。

swcutil developer-mode -e true

关键点:

  • developer-mode 是逐字稿给出的 macOS 命令。
  • 开启后需要管理员密码或 Touch ID。
  • developer mode 是 per-user 操作。
  • 它只对开发签名的 App 生效,App Store、TestFlight、notarized Mac App 不能使用这个模式。

21:23)App 通过 Associated Domains entitlement 选择某个域名是否使用 alternate mode。查询项名是 mode,值指定模式。使用 developer 或 managed mode 时,AASA 必须放在 .well-known 目录,不能只放在域名根目录。

applinks:www.example.com
applinks:dev.example.com?mode=developer
applinks:managed.example.com?mode=managed

关键点:

  • 第一行是普通 Universal Links 域名,会走 CDN。
  • ?mode=developer 用于开发测试域名。
  • ?mode=managed 用于 MDM 管理设备上的企业部署。
  • 这些域名只是示例,实际域名由团队自己的环境决定。

核心启发

做一个国际化路由收敛器

做什么:把产品页、内容页和活动页的 URL 规则整理成一份 AASA 变量表。

为什么值得做:substitution variables 可以把语言、地区和业务枚举从大量 path pattern 里抽出来,避免规则数量随组合成倍增长。

怎么开始:先列出当前 URL 中的语言、地区和商品维度,把系统内置 $(lang)$(region) 和自定义变量结合起来,再用 exclude 处理无法服务的地区。

给 watchOS App 加深度链接入口

做什么:让手表 App 能接收餐厅、订单、票券或内容详情页的 Universal Link。

为什么值得做:watchOS 现在支持 Universal Links,用户从 Messages 或 Mail 里的链接进入时,可以停留在手表上的轻量流程。

怎么开始:把 Associated Domains entitlement 加到 WatchKit extension,实现 WKExtensionDelegate.handle(_ userActivity:),从 NSUserActivity 里解析 URL,再跳到对应界面。

建一套 AASA 发布前检查

做什么:在上线前检查 AASA 的路径规则、大小写、Unicode 和 .well-known 部署位置。

为什么值得做:iOS 14 和 macOS Big Sur 会通过 Apple CDN 获取 Associated Domains 数据,公网可达性和文件位置会直接影响新安装用户的首次体验。

怎么开始:用真实域名准备一份最小 AASA,覆盖 caseSensitivepercentEncodeddefaults 和 substitution variables;开发环境用 ?mode=developer 走直连。

为企业 App 准备 managed mode 域名

做什么:把内部业务系统的 Universal Links 和 MDM 安装流程一起设计。

为什么值得做:managed mode 面向通过 MDM profile 安装的 App,适合只在公司网络或受控环境里访问的域名。

怎么开始:为企业域名增加 ?mode=managed entitlement 条目,把 AASA 放到 .well-known 目录,并配合设备管理团队确认 profile 安装路径。

关联 Session

评论

GitHub Issues · utterances