# 按版本看 iOS 上的 PWA（iOS 11.3 到 iOS 26）

> 逐个版本梳理 iOS 给主屏幕 Web 应用带来了什么：从 11.3 的 service worker 到 iOS 26 默认以 Web 应用方式打开被添加的网站。

一个 Web 应用在 iPhone 上能做什么，同时取决于 iOS 版本与它附带的 manifest。各版本加入了 service worker、推送、角标以及调整后的存储配额等能力，manifest 成员则会影响其显示模式、图标与标识。本条目给出带日期的清单，以便有依据地选定支持下限，而不是凭感觉。

## 工作原理

下面每个版本都改变了站点在运行时能观察到的某件事，只有一处例外：iOS 17.4 一节记录的是一次在欧盟宣布、又在正式版发布前撤回的移除。实际后果是：支持下限是一个关于能力的陈述，而示例中的能力检测就是在不读取用户代理字符串的前提下守住这条下限的办法。

### service worker 之前：iPhone OS 1.1.3 与 2.1

主屏幕图标比这里其他一切都早。WebKit 的 Safari 26.0 功能文章记载，iPhone OS 1.1.3（2008 年 1 月）让用户可以把网站图标添加到主屏幕，点按图标会在 Safari 中打开该网站；iPhone OS 2.1（2008 年 8 月）让开发者可以用 `apple-mobile-web-app-capable` meta 标签请求类应用的独立模式。这两件事定下了此后每个版本都在调整的格局：添加动作由用户完成，而页面标记只能影响结果。

### iOS 11.3：service worker 与 Cache API

service worker 随 11.3 beta 到达 iOS，公布于 [Workers at Your Service](https://webkit.org/blog/8090/workers-at-your-service/)（2018 年 2 月）。文章明确说明了它们运行的位置：Safari、使用 `SFSafariViewController` 的应用，以及保存到主屏幕的 Web 应用。它同样明确说明了限制。除规范要求的源分区之外，WebKit 还按顶层文档的源再做一层分区，因此 `webkit.org` 页面内的 `example.com` 框架会得到自己的分区。该版本的 Cache API 配额是每个分区固定 50 MiB，一旦用满，需要空间的存储操作就会失败，直到页面自己清理条目。WebKit 还会移除数周未被使用的 service worker 注册，以及数周未被打开的缓存，因此离线优先的应用必须能承受随时失去其中任一项。

### iOS 11.4：Web 应用 manifest

W3C 于 2013 年开始标准化 Web 应用 manifest，浏览器支持自 2014 年 11 月开始，Safari 则在 2018 年 3 月随 iOS 11.4 采纳该格式，这些时间点来自 Safari 26.0 的文章。从那个版本起，manifest 与 `apple-mobile-web-app-capable` meta 标签一起成为决定被添加的图标产生 Web 应用还是书签的途径：该文章的说法是，过去 17 年里站点需要具备 meta 标签或 manifest 的 `display` 值才会以 Web 应用方式打开。这条规则一直持续到 iOS 26 把决定权交给用户。

### iOS 15.4：manifest 的读取时机与 manifest 图标

Safari 15.4 中有两项变化影响 manifest 的读取方式。浏览器改为在页面加载期间获取 manifest，而不是等到用户选择"添加到主屏幕"的那一刻，这也让该文件可以描述在 Safari 中浏览的页面。此外，manifest 中声明的图标开始可用：当 HTML head 中没有定义 `apple-touch-icon`，且 manifest 的图标条目省略 `purpose` 或设为 `"purpose": "any"` 时，Safari 与 iOS 会使用它们。`apple-touch-icon` 链接仍然优先，这是有意的设计，站点由此可以为 iOS 保留一个与其他移动平台不同的专用图标。

### iOS 16.4：推送、角标与 manifest 身份

Safari 16.4 是让已安装 Web 应用可以与应用相提并论的那个版本。它为添加到主屏幕的 Web 应用加入了 Web Push，建立在 Push API、Notifications API 与 service worker 之上，通知会出现在锁定屏幕、通知中心以及配对的 Apple Watch 上，并可通过专注模式配置。它为主屏幕 Web 应用加入了 Badging API，用户允许通知后即自动获得该权限。它加入了对 manifest `id` 成员的支持，使同一个 Web 应用的多份安装保持可区分。它还让第三方浏览器可以在共享菜单中提供"添加到主屏幕"。

### iOS 17：存储配额的重写

配额数字在 Safari 17.0 以及 iOS 17 上的 WebKit 应用中发生了变化，记载于 [Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)。浏览器应用，即可被设为默认浏览器的应用，获得的单源配额最高为磁盘总空间的 60 %，总体配额最高为 80 %；其他应用分别最高为 15 % 与 20 %。跨源框架位于自己的分区中，配额为主框架单源配额的 10 %。该版本还不再就网站想要更多空间向用户弹窗询问，并带来了对 Storage API 的完整支持，其中 `navigator.storage.persist()` 依据启发式规则授予，而这些规则包含站点是否以主屏幕 Web 应用打开。独立运行的 Web 应用获得的单源配额与总体配额，与它在浏览器应用中打开时相同，因此安装并不会换来额外空间。

### 欧盟的 iOS 17.4：宣布移除，随后撤回

2024 年 2 月，Apple 表示作为遵守《数字市场法》（DMA）的一部分，将为欧盟用户移除主屏幕 Web 应用。它在开发者支持页面上的原话是 "to comply with the DMA's requirements, we had to remove the Home Screen web apps feature in the EU"，并称欧盟用户将 "through a bookmark with minimal impact to their functionality" 访问网站。给出的理由是：要处理使用其他浏览器引擎的 Web 应用所带来的安全与隐私问题，就得构建 "an entirely new integration architecture"，而 iOS 当时并没有这样的架构。Open Web Advocacy 最早在 iOS 17.4 的第一个 beta 中注意到这一变化，并记录了 Apple 于 2024-02-16 的确认（[It's Official, Apple Kills Web Apps in the EU](https://open-web-advocacy.org/blog/its-official-apple-kills-web-apps-in-the-eu/)，open-web-advocacy.org）。

这次移除没有进入正式版。同一页面原文上方加入的更新写明，Apple "will continue to offer the existing Home Screen web apps capability in the EU"，这项支持 "means Home Screen web apps continue to be built directly on WebKit and its security architecture"，受 beta 中移除影响的开发者与用户 "can expect the return of the existing functionality for Home Screen web apps with the availability of iOS 17.4 in early March"（[Changes for apps in the European Union，2024-03-04 存档](https://web.archive.org/web/20240304183538/https://developer.apple.com/support/dma-and-apps-in-the-eu/)，web.archive.org）。Open Web Advocacy 把撤回日期记为 2024-03-01（[Apple backs off killing web apps](https://open-web-advocacy.org/blog/apple-backs-off-killing-web-apps/)，open-web-advocacy.org），iOS 17.4 于 2024-03-05 发布（[About the security content of iOS 17.4 and iPadOS 17.4](https://support.apple.com/en-us/120893)，support.apple.com）。对支持下限而言，这段经过不增加新的一行：欧盟保留了原有能力，而且按 Apple 的说法，它仍建立在 WebKit 之上。

### iOS 18.4 与 iOS 26：先是声明式推送，再是没有条件的可安装性

Safari 18.4 让 Declarative Web Push 在 iOS 与 iPadOS 18.4 上对添加到主屏幕的 Web 应用可用，这是该平台上第一条完全不需要 service worker 的通知路径。随后 Safari 26.0 改变了"添加到主屏幕"的含义：默认情况下每个被添加的网站都以 Web 应用打开，想要浏览器书签的用户在添加时关闭 Open as Web App 即可，文章直白地写明，Safari 中如今对可安装性"零要求"。manifest 仍然有价值，因为其中声明的图标依旧会被使用，而 Safari 26.0 另外接受 SVG 文件与 data URL 作为图标。

## 示例

两段代码都是针对能力而非版本写的，这也是它们在下一个版本上同样正确的原因。

### 大规模预缓存前先确认空间

存储上限在 iOS 17 发生了移动，把假设写死的站点至少会在一个受支持的版本上出错。改为询问，并为无法回答的引擎保留一条路径。

```js
async function roomFor(bytes) {
  if (!('storage' in navigator) || typeof navigator.storage.estimate !== 'function') {
    return true; // 这里没有 Storage API：直接尝试写入并处理配额错误。
  }
  const { quota = 0, usage = 0 } = await navigator.storage.estimate();
  return quota - usage > bytes;
}
```

回退分支返回 `true` 是诚实的选择：写入仍有可能成功，而调用方在两条分支里都必须处理 `QuotaExceededError`。把一个无法回答的问题当作拒绝，会在本来能应付的引擎上关掉离线支持。

### 声明一个所有受支持版本都能用的图标

本条目覆盖的版本区间内图标解析方式各不相同，因此两种形式都声明，让各版本自行取用。两者同时存在时 `apple-touch-icon` 链接胜出，这使它成为可预测的下限。

```html
<link rel="apple-touch-icon" href="/icons/apple-touch-180.png" />
<link rel="manifest" href="/manifest.webmanifest" />
```

manifest 依然值得保留：自 iOS 15.4 起，没有 `apple-touch-icon` 时会使用它的图标；自 iOS 16.4 起，它的 `id` 让多份安装彼此区分；自 iOS 26 起，它的图标条目可以是 SVG。在 iOS 15.4 及之后的版本中，当 `apple-touch-icon` 不存在，且 manifest 图标省略 `purpose` 或其值包含 `any` 时，系统会使用该 manifest 图标。

## 实测行为

过去调试主屏幕 Web 应用的 service worker，是一场与想要检查的那个事件的赛跑。Safari 26.0 取消了这场赛跑，而这条菜单路径值得记下来。

:::observed
Safari 26.0（英文界面）：从"开发"菜单打开 Inspect Apps and Devices 会列出每个应用与主屏幕 Web 应用，条目旁的三点菜单中有一项 "Automatically Inspect New Service Workers"，相邻还有 "Automatically Pause New Service Workers"。记载于 [WebKit Features in Safari 26.0](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)（webkit.org），文中也给出了需要它的原因：`push` 事件可能在"开发"菜单列出该 worker 之前就已处理完毕。
:::

对推送相关的工作而言，真正要紧的是暂停选项，因为它能在足够早的时刻停下执行，让断点落在处理程序内部，而不是它已经返回之后。

## 另请参阅

- [iOS 与 Safari 上的 PWA](/zh/reference/platforms/ios-safari/)
- [iOS 与 iPadOS 上的 Web Push（16.4+）](/zh/reference/notifications/ios-safari-push/)
- [StorageManager.persist() 与 persisted()](/zh/reference/storage/persistence/)
- [iOS 上主屏 Web 应用的存储隔离](/zh/reference/storage/home-screen-storage-isolation/)
- [Workers at Your Service](https://webkit.org/blog/8090/workers-at-your-service/)（webkit.org）
- [Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)（webkit.org）
- [Apple backs off killing web apps](https://open-web-advocacy.org/blog/apple-backs-off-killing-web-apps/)（open-web-advocacy.org）
- [WebKit Features in Safari 16.4](https://webkit.org/blog/13966/webkit-features-in-safari-16-4/)（webkit.org）