WWDC Quick Look 💓 By SwiftGGTeam
Supercharge device connectivity with Wi-Fi Aware

Supercharge device connectivity with Wi-Fi Aware

观看原视频

Highlight

Wi-Fi Aware 是一个由 Wi-Fi Alliance 维护的全球标准,让 iPhone 和 iPad 在保留现有 Wi-Fi 上网的同时,建立点对点、加密、低延迟的设备直连。


核心内容

过去做一个能在两台设备之间直接传文件、控制配件、或者实时投屏的 app,开发者得自己拼一套底座:先解决发现,再处理配对、密钥交换、链路加密,还要操心是不是占用了主 Wi-Fi 让用户掉网。结果是各家方案互不兼容,跨厂商基本不可能,电池和带宽也压不住。

WWDC25 推出的 Wi-Fi Aware 框架改写了这个流程。它基于 Wi-Fi Alliance 维护的全球标准,跨平台、可互操作,任何厂商的设备都能接入。设备保留正常 Wi-Fi 连接的同时,运行在 Wi-Fi 层的点对点直连,整条链路自动认证加密,支持高吞吐、低延迟、多设备并发。开发者只需处理两个高层流程:Pairing(一次性建立信任)和 Connecting(已配对设备按需连接)。系统自己负责密钥交换和链路加密,你不需要管协议细节。

故事还有第二层:配对入口被拆给两个框架。DeviceDiscoveryUI 适合 app 之间或 app 到设备的通用配对,能同时支持 Apple 和第三方设备;AccessorySetupKit 是配件厂商专用,能在一次流程里同时把蓝牙和 Wi-Fi Aware 都配好。配对完成后,你拿到一个 WAPairedDevice,把它喂给 Network framework 的 NetworkListenerNetworkBrowser,连接就建立起来了。


详细内容

服务声明:以 Info.plist 为唯一真源

Wi-Fi Aware 的核心概念是服务(Service)。服务名必须唯一,长度不超过 15 个字符,只允许字母、数字、短横线,由「唯一名 + 协议(tcp 或 udp)」组成,例如 _file-service._tcp。建议把服务名注册到 IANA 防止冲突。

服务有两个角色:Publisher(提供服务、监听连接)和 Subscriber(消费服务、浏览设备)。一个 app 可以同时扮演两个角色。Info.plistWiFiAwareServices 字典声明了 app 能用哪些服务,配置 PublishableSubscribable 决定角色。app 只能使用 Info.plist 中声明过的服务。

声明完之后,在代码里通过下列方式拿到能力检查和服务句柄(06:57):

import WiFiAware

// 检查设备是否支持 Wi-Fi Aware
guard WACapabilities.supportedFeatures.contains(.wifiAware) else { return }

// Info.plist 中声明的 Publishable 服务
extension WAPublishableService {
    public static var fileService: WAPublishableService {
        allServices["_file-service._tcp"]!
    }
}

// Info.plist 中声明的 Subscribable 服务
extension WASubscribableService {
    public static var fileService: WASubscribableService {
        allServices["_file-service._tcp"]!
    }
    public static var droneService: WASubscribableService {
        allServices["_drone-service._udp"]!
    }
}

关键点:

  • WACapabilities.supportedFeatures.contains(.wifiAware):能力门,老设备直接 return,避免后续 API 触发不可用错误。
  • WAPublishableService.allServices[...]:用 Info.plist 中的服务名取出 publishable 句柄,强制解包是因为这一步取不到说明 plist 没声明,这是开发期错误。
  • WASubscribableService.allServices[...]:同理,用静态属性把服务包起来便于在调用点引用。
  • _file-service._tcp 同时声明在 publishable 和 subscribable,对应「app 既能当服务端又能当客户端」的对称用例;_drone-service._udp 仅 subscribable,对应「app 控制一个外部配件」。

配对入口:两条路径

DeviceDiscoveryUI 用 SwiftUI 视图直接调起系统配对界面(10:33):

import DeviceDiscoveryUI
import WiFiAware
import SwiftUI

// Listener (Publisher) 设备:调起广告 UI
DevicePairingView(.wifiAware(.connecting(to: .fileService, from: .selected([])))) {
    // 系统 UI 拉起前展示给用户的视图
} fallback: {
    // 出错时的视图
}

// Browser (Subscriber) 设备:调起选择器 UI
DevicePicker(.wifiAware(.connecting(to: .selected([]), from: .fileService))) { endpoint in
    // 处理已配对的网络端点
} label: {
    // 系统 UI 拉起前展示给用户的视图
} fallback: {
    // 出错时的视图
}

关键点:

  • DevicePairingView(.wifiAware(.connecting(to:from:))):Publisher 端,to 指定要广告的服务,from: .selected([]) 表示对所有发起方开放。
  • DevicePicker:Subscriber 端,回调里直接拿到一个网络 endpoint,可以传给 NetworkConnection
  • 两个视图都接受 fallback,在设备不支持或运行时出错时承担降级 UI。

AccessorySetupKit 是配件厂商专用,能在一次流程里把蓝牙和 Wi-Fi Aware 都配好(12:29):

import AccessorySetupKit

// 配置发现描述符(Subscriber)
let descriptor = ASDiscoveryDescriptor()
descriptor.wifiAwareServiceName = "_drone-service._udp"
descriptor.wifiAwareModelNameMatch = .init(string: "Example Model")
descriptor.wifiAwareVendorNameMatch = .init(string: "Example Inc", compareOptions: .literal)
let item = ASPickerDisplayItem(name: "My Drone",
                               productImage: UIImage(named: "DroneProductImage")!,
                               descriptor: descriptor)

// 创建并激活 session
let session = ASAccessorySession()
session.activate(on: sessionQueue) { event in
    // 设备添加成功时回调,event: .accessoryAdded
    // 用 ASAccessoryWiFiAwarePairedDeviceID 反查 WAPairedDevice
}
// 弹出选择器
session.showPicker(for: [item]) { error in
    // 错误处理
}

关键点:

  • ASDiscoveryDescriptor 通过 wifiAwareServiceName / ModelNameMatch / VendorNameMatch 三组过滤条件锁定要找的硬件。
  • ASPickerDisplayItem 把图标、名称、描述符打包成一项,可以同时传多项给 showPicker
  • 配对成功后 event 携带 ASAccessoryWiFiAwarePairedDeviceID,用它去 WAPairedDevice 反查后续连接要用的实体。

访问已配对设备

配对完成后,已配对列表通过异步序列暴露(13:51):

import Foundation
import WiFiAware

var device: WAPairedDevice // 用 WAPairedDevice.allDevices 获取

// 访问 WAPairedDevice 的属性
let pairingName = device.pairingInfo?.pairingName
let vendorName = device.pairingInfo?.vendorName
let modelName = device.pairingInfo?.modelName

// 创建过滤器
let filter = #Predicate<WAPairedDevice> {
    $0.pairingInfo?.vendorName.starts(with: "Example Inc") ?? false
}

// 拿到当前所有匹配的已配对设备
// 每次有设备增删改时都会得到一个新的快照
for try await devices in WAPairedDevice.allDevices(matching: filter) {
    // 处理新的快照
}

关键点:

  • WAPairedDevice.allDevices(matching:) 返回 AsyncSequence,每次配对列表变化都会推送一个全量快照,便于驱动 UI。
  • #Predicate<WAPairedDevice> 是 Swift Predicate 宏,把过滤条件编译期确定下来。
  • 用户可能在系统设置里删掉配对,app 必须响应这个序列变化更新自己的状态。

建立连接:交给 Network framework

服务声明、配对完成后,连接基于 Network framework 标准 API(16:54):

import WiFiAware
import Network

// Listener (Publisher) 端:构造 NetworkListener
let listener = try NetworkListener(for:
        .wifiAware(.connecting(to: .fileService, from: .matching(deviceFilter))),
    using: .parameters {
        TLS()
    })
    .onStateUpdate { listener, state in
        // 状态更新
    }

// Browser (Subscriber) 端:构造 NetworkBrowser
let browser = NetworkBrowser(for:
        .wifiAware(.connecting(to: .matching(deviceFilter), from: .fileService))
    )
    .onStateUpdate { browser, state in
        // 状态更新
    }

关键点:

  • NetworkListener(for: .wifiAware(...)):把 Wi-Fi Aware 服务包装成普通 Network framework 的 listener,TLS 等参数也按 Network framework 标准写。
  • from: .matching(deviceFilter):Listener 用过滤器限制谁能来连,Browser 用过滤器限制找哪些设备。
  • .onStateUpdate:观察底层无线资源的就绪、运行、失败、终止状态,是诊断「连不上」的第一手位置。

性能调优

默认配置是 .bulk + .bestEffort,适合传文件类工作负载。视频或控制类要求低延迟时改成 .realtime + .interactiveVideo21:11):

// Listener (Publisher) 端
let listener = try NetworkListener(for:
        .wifiAware(.connecting(to: .fileService, from: .matching(deviceFilter))),
    using: .parameters {
        TLS()
    }
    .wifiAware { $0.performanceMode = .realtime }
    .serviceClass(.interactiveVideo))

// Browser (Subscriber) 端
let connection = NetworkConnection(to: endpoint, using: .parameters {
        TLS()
    }
    .wifiAware { $0.performanceMode = .realtime }
    .serviceClass(.interactiveVideo))

// 两端都可读取性能报告
let performanceReport = try await connection.currentPath?.wifiAware?.performance

关键点:

  • performanceMode = .realtime:低延迟模式,电量代价更高,建议只在交互式场景启用。
  • serviceClass(.interactiveVideo):QoS 等级,让链路把这类流量优先调度。备选还有 .voice / .background / .bestEffort
  • connection.currentPath?.wifiAware?.performance:拿到吞吐、延迟、信号强度等运行期指标,可以用来在 UI 上提示用户「靠近一点」。

核心启发

1. 给 app 加一个「附近设备直连」的能力开关

为什么值得做:很多 iPad / iPhone 协作场景(白板、文件互传、实时投屏)以前要走云中转或本地 Wi-Fi 服务发现,受路由器和网络隔离影响很大。Wi-Fi Aware 在 Wi-Fi 层做点对点,避开 NAT 和企业网限制。

怎么开始:在 Info.plist 里声明一个 publishable + subscribable 的对称服务名,先用 DeviceDiscoveryUI 跑通最小配对 demo,再把现有的 URLSession 替换成基于 endpoint 的 NetworkConnection

2. 配件 app 用 AccessorySetupKit 一次性拿下蓝牙 + Wi-Fi Aware

为什么值得做:硬件配件常用蓝牙做发现/控制、Wi-Fi 做大流量数据。以前要分两轮配对,体验割裂。AccessorySetupKit 把两条链路打包,用户只走一次 PIN 码确认。

怎么开始:填写 ASDiscoveryDescriptor 时同时设置蓝牙和 wifiAwareServiceName / VendorNameMatch / ModelNameMatch,让用户在第一次设置时就拿到完整能力。

3. 把视频/控制类场景切到 .realtime + .interactiveVideo

为什么值得做:默认 .bulk + .bestEffort 在多设备共存时会被排到后面,延迟看着小但抖动大。无人机控制、远端摄像头预览、AR 实时同步这类场景对抖动敏感。

怎么开始:在 NetworkListener / NetworkConnection.parameters 闭包里加 .wifiAware { $0.performanceMode = .realtime }.serviceClass(.interactiveVideo),并在 UI 上读 performanceReport 给用户实时反馈链路质量。

4. 用 WAPairedDevice.allDevices 驱动配对设备的列表 UI

为什么值得做:用户随时可能在系统设置里删除配对设备。allDevicesAsyncSequence,每次变更推送全量快照,比自己存一份本地缓存更可靠。

怎么开始:在管理设备的 SwiftUI 视图里用 .task 启动一次 for try await devices in WAPairedDevice.allDevices(matching: filter),把 devices 写到 @State,UI 自动响应。


关联 Session

评论

GitHub Issues · utterances