View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

返回 WWDC25

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 将结构化并发代码与 Network 框架搭配使用

    Network 框架是在 Apple 平台上建立底层网络连接的最佳方式,而在 iOS、iPadOS、和 macOS 26 上,它非常适合与你的结构化并发代码搭配使用。我们将探索如何建立连接、发送和接收数据与分帧处理的信息、监听传入连接以及浏览网络上的服务。在此期间,我们还将介绍一些关键的最佳做法。

    章节

    • 0:00 - 开场介绍
    • 0:45 - 建立连接
    • 7:22 - 发送和接收
    • 14:22 - 监听传入连接
    • 16:05 - 查找其他设备

    资源

    • Building a custom peer-to-peer protocol
    • Network
    • NetworkBrowser
    • NetworkConnection
    • NetworkListener
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 借助 Wi-Fi Aware 增强设备连接性能
    • 采用 Swift 并发

    WWDC18

    • Introducing Network.framework: A modern alternative to Sockets
  • 搜索此视频…

    大家好 我是 Scott 很高兴能与大家分享一些改进 它们能让网络代码编写 变得更加有趣和轻松 如果你从未编写过任何网络代码 这个讲座将非常适合你 如果你已经有相关经验 我也将分享一些新的 API 我将首先介绍如何创建连接 然后 我将深入讲解如何使用这些 连接发送和接收数据

    接着 我将讨论如何侦听传入连接 最后 我将介绍如何查找并连接 网络中的其他设备 我们从建立连接开始 Network 框架使你能够 在 App 中创建 安全、可组合的现代连接 不再需要套接字、 sockaddr 和难记的 ioctl 和阻塞操作 Network 框架支持“按名称连接” 这意味着它会完成解析名称的工作 让你无需再费力 解析名称后 它使用称为 Happy Eyeballs 的算法 确保高效地选择 最佳解析地址来动态连接

    内置通过 TLS 实现的安全性 因此你不必在 App 中额外集成 一个通常具有完全不同的 API 的库 只是为了保护用户的隐私 它还支持网络接口转换和代理 可实现 Wi-Fi 助理 和多路径协议等功能 无需你执行任何操作

    它支持 QUIC 等现代传输协议 甚至允许你将自己的协议 与内置协议混搭 让你能够专注于业务逻辑 和用户体验 而不是受困于调试问题 比如为什么消息无法 从 A 点传输到 B 点 如果你同时支持 原生 App 和 Web App Network 框架还对 WebSocket 提供强大支持 允许一台服务器 为各种客户端应用程序提供服务

    Network 框架从底层 就被设计为可组合的结构 这意味着当你使用 Network 框架时 即使网络协议随着时间的推移 不断发展 以提升性能和隐私保护 你仍将获得一组熟悉的 API 如果你使用 TCP 和 BSD Socket API 写过网络代码 就会知道切换到 QUIC 之类的协议 需要进行大规模的重写 有了 Network 框架 切换到 QUIC 不过是一顿饭的功夫 在 iOS 和 macOS 26 中 Network 框架紧密结合了 Swift 对异步操作 和结构化并发的强大支持 现在 你的网络代码 将与其他 Swift 代码无缝融合 使 App 更易于构建和维护 对于刚接触网络编程的朋友 我在这个示例中介绍的一些概念 你可能会不太熟悉 但别担心 最终你都会理解的

    假设你正在编写 App 代码 并且你想与地址为 www.example.com 的服务器在端口 1029 建立通信 你想使用 TLS 并且只想 在不受约束的网络上建立连接 端点是你的连接目标 协议栈是你想要连接的方式 参数有助于优化到达目标的方式

    将它们组合起来 你就得到了一个 NetworkConnection 让我们通过一个例子来了解 它的实际工作方式

    我使用所需的端点和协议栈 初始化 NetworkConnection 从而建立连接 在这个例子中 我指定协议栈为 TLS 请注意 TCP 和 IP 是自动推断的 只有当我想自定义某些东西时 才需要指定它们 但几乎所有情况下 默认设置都适用

    当我想修改默认设置时 我仍然以声明的方式进行 作为示例 我将关闭这个连接的 IP 分片

    首先 我需要在 TLS 下 指定 TCP 和 IP 协议 然后我可以自定义 为 IP 设置的选项以关闭分片

    如果有人启用了低数据模式 以尽量减少网络使用 我希望修改连接的行为 使它禁止 使用那些受限的网络接口

    我更新了代码 以便可以修改连接的参数 因为我将不再使用 自动创建的默认参数

    协议栈保持不变 但我添加了自定参数 这样我就可以指定 对于这个特定连接 我希望 Network 框架只考虑使用 非低数据模式的网络接口 太棒了! 它采用的声明式风格 正是使 SwiftUI 广受欢迎的原因 现在我的网络代码 与用户界面代码的感觉很相似

    我们总希望网络始终可用 但实际上 网络状况会随时变化 与套接字不同 NetworkConnection 会为你响应这些不断变化的状态

    当连接启动时 它将在任何协议握手进行时 转换为准备状态 握手完成后 它会进入就绪状态 如果没有连通性 连接将 从准备状态转入等待状态

    当 Network 框架检测到 网络状况发生变化时 它将返回准备状态 同时尝试连接到远程端点 当连接状态变为就绪时 它就可以发送和接收数据了

    当连接处于就绪状态时 如果遇到错误或失去连通性 它将转换为失败状态 并显示错误 让你知道发生了什么

    如果退出或取消与连接相关的任务 它将变为已取消状态 这个机制的好处是 如果你不想了解任何连接状态 就完全不需要关注 你可以调用 send 和 receive NetworkConnection 会等待 状态变为就绪 以完成这些操作 但如果你想知道连接处于什么状态 比如为了更新用户界面 你可以安装一个处理程序 当连接状态变化时调用它 好的 现在你知道如何建立连接 可以开始使用它在网络上 发送和接收数据

    send 和 receive 都是异步函数 如果连接尚未启动 两者都会启动连接

    send 接受一个数据对象并暂停任务 直到 Network 框架处理完数据为止

    TLS 和 TCP 都是流协议 因此接收数据时 你必须指定所需的字节数 在这个示例中 我精确指定了 要读取的字节数 receive 会返回包含内容 和元数据的元组 但在这个示例中我指定 我只想要内容

    如果连接遇到错误 这两个函数都会抛出错误 例如 如果由于开启了飞行模式 而导致网络中断 你可以使用相关错误来 解释传输中断的原因 有时你不知道要接收多少字节的数据 在这个示例中 我正在从网络加载一张图片 我在连接上收到的一个 32 位整数 告诉我还有多少字节 的图像数据需要接收 我用这个值反复调用 receive 函数 直到图像数据接收完毕

    在上一个例子中 我使用的 receive 版本 指定了确切的字节数 在这个例子中 我使用的 receive 版本允许我 指定接收的最小和最大字节数 这样做可以让代码 在字节从网络传入时解析图像 而不必等待全部数据传输完毕 像 TLS 这样的字节流协议很好用 但很多时候你需要 将发送和接收的字节封装成帧 这样就可以处理消息而不是字节 在上一个示例中 我通过从图像内容之前的字节流中 读取 32 位值来了解图像大小 这个值使用长度将图像封装成帧 以便将它与相邻图像区分开来 我必须这样做 因为流协议不保留消息边界 这意味着 传递给单个 send 操作的字节数 不一定等于 从连接另一端的 receive 操作 传回的字节数 例如 如果你调用 send 发送三个数据块 另一端可能一次接收一个字节 也可能一次性接收所有字节 或者介于这两种情况之间 这会大大增加编写健壮的 联网应用程序的复杂性

    幸运的是 Network 框架可提供帮助 iOS 26 和 macOS 26 新增了内置的 类型、长度、值 (TLV) 帧封装器 它可以将消息封装成帧 使得在连接一端发送的内容 与在另一端接收的内容完全相同

    TLV 是一种简单的消息协议 它对类型进行编码 可用于描述消息中包含的数据 还对长度进行编码 也就是消息中数据的大小 接下来是消息的实际内容 常见的网络协议都使用 TLV 因此你的服务器可能 已经支持这种格式 我们来试一下 在这个示例中 我将发送和接收 游戏消息类型 GameMessage 是一个枚举 我将使用它作为消息的类型 消息的内容将是 游戏角色或游戏操作 添加 TLV 非常简单 只需把它添加到协议栈即可 因为 TLV 会将消息封装成帧 所以发送和接收的接口略有不同

    我将使用 JSONEncoder 对 GameCharacter 结构体 进行编码并发送它 请注意 我指定了消息的类型 以及编码的数据 现在让我们看看如何 使用 TLV 接收消息

    与前面使用字节流协议的示例不同 使用 TLV 时 我不必指定 要接收的字节数 因为 TLV 会为我解析消息 因为我想知道收到的信息类型 所以我收到了包含内容 和与消息相关的元数据的元组 对于 TLV 元数据包括类型 我使用类型来确定收到的内容类型 我使用这些信息解码数据 并打印收到的内容 这个功能很强大 尤其是在 与我无法控制的现有服务器 和协议进行互操作时 现在我已轻松地将字节装帧成帧 并且可以发送和接收消息了 这比直接使用字节流协议 有了很大的改进

    但是 如果我可以直接发送对象 会怎样? iOS 和 macOS 26 现在支持直接 发送和接收可编码类型 这有助于简化某些样板代码

    我可以将角色和操作结构体 折叠到 GameMessage 枚举中 Coder 是协议 可以添加到协议栈中 它会帮我将消息封装成帧 并允许我发送和接收可编码类型 我不需要自己进行 序列化和反序列化 在这段代码中 我来回发送游戏消息 因此 我将使用发送和接收的类型 以及希望 Coder 格式化数据的方式 来初始化 Coder Network 框架内置了 对 JSON 和属性列表格式的支持 在这个例子中 我将选择 JSON 现在 我可以发送游戏消息 而无需自己进行任何编码

    对连接调用 receive 将直接返回游戏消息 而无需进行任何中间解码 来将数据对象转换为 GameMessage 对象 现在 我可以发送和接收游戏消息 而不需要自己做任何额外工作 我可以专注于 App 的业务逻辑 和用户界面 而不会被一堆定制的网络代码所困扰

    现在你知道了如何创建与端点的连接 以及如何在连接上发送和接收数据 你了解了 TCP 和 TLS 等字节流协议 以及如何将帧封装协议添加到协议栈 以便处理消息而不是字节流 但是 侦听传入连接的应用程序 又该如何呢?

    传入连接由 NetworkListener 处理 和 NetworkConnection 一样 我通过声明协议栈来初始化它 与连接不同 侦听器没有 send 或 receive 方法

    这是因为侦听器会侦听新连接 然后将它们传递给调用者 NetworkListener 有一个 run 方法 它会将新连接传递给 传入的处理程序 我们来看看具体过程 我通过指定协议栈 以声明方式创建 NetworkListener 在这个示例中 我的传入连接 将能够发送和接收 由 TLS 加密的 GameMessage 对象

    对 NetworkListener 调用 run 将开始将新的 传入连接传递给 我传入 run 的处理程序

    NetworkListener 会为每个新连接 启动一个新的子任务 因此 我可以在闭包中执行异步操作 而不必担心会阻止侦听器 继续传递新的传入连接 当我获得新连接时 我使用 NetworkConnection 的 messages 属性 来处理来自客户端的传入消息 现在我创建了一个 NetworkConnection 它连接到一个已有端点 并且我编写了侦听新连接的代码 但现在我想创建一个 我事先不知道端点的 NetworkConnection NetworkBrowser 使我能够发现 创建连接时可用的端点 今年 iOS 26 新增了 Wi-Fi Aware 这是一种跨平台的点对点网络技术 可让你发现并连接 各种兼容设备 你可以使用 DeviceDiscoveryUI 框架 通过 Wi-Fi Aware 查找并配对 附近的设备 或者 你可以浏览 Bonjour 广播的服务 要进一步了解 Wi-Fi Aware 请观看“借助 Wi-Fi Aware 增强设备连接性能” 当你想查找网络上的设备时 无论是在附近使用 Wi-Fi Aware 还是通过 Bonjour 都可以使用 NetworkBrowser NetworkBrowser 接受浏览描述符 用于描述你要查找的内容 和 NetworkConnection 一样 它也接受参数 用于描述你想要如何查找 但与 NetworkConnection 和 NetworkListener 不同的是 NetworkBrowser 不接受协议栈 这是因为 NetworkBrowser 的唯一工作是返回 可用于建立连接的端点

    在这个例子中 我创建 NetworkBrowser 以使用 Wi-Fi Aware 服务 Tic-Tac-Toe 搜索附近的设备 对浏览器调用 run 将导致它启动 并开始向我传递给 run 的处理程序 提供一组端点

    在我的 App 中 我对使用哪个端点没有偏好 因此我选择浏览器返回的第一个端点 返回带端点的 .finish 将导致浏览器停止运行 并从 run 返回这个端点 然后 我可以使用这个端点 初始化 NetworkConnection 这与我在前面的例子中使用端点 初始化 NetworkConnection 的方式完全相同 不过 这个端点的巧妙之处在于 它是浏览器替我发现的 所以我不必事先知道它

    面对所有这些新协议 你可能想知道如何选择 在 App 中使用哪种协议 这是一个好问题 答案并不像你想象的那么复杂 如果你要与你无法控制的服务器 或其他设备通信 那么协议就已经确定了 例如 你可能通过 IPP over TCP 与打印机通信 如果你要与另一台设备上 你自己的 App 通信 那么使用 Coder over TLS 或 QUIC 不会出错

    注意 如果你要进行 HTTP 网络通信 并且当前使用 URLSession 则无需更改任何代码 如果你使用的是 Network 框架的 C API 或者使用 Swift 进行开发 并且更喜欢使用完成处理程序 那么也不需要更改任何代码 你仍将获得一流的按名称连接支持 可组合性、移动性和内置安全性 无论你以何种形式使用 URLSession 或 Network 框架

    我们来总结一下 iOS 和 macOS 26 中新增了 NetworkConnection、 NetworkListener、NetworkBrowser 你了解了如何搭配使用 NetworkConnection 和 TLV 帧封装 以及如何使用 Coder 协议 支持发送和接收可编码类型 NetworkListener 可用于 侦听传入连接 NetworkBrowser 可用于 浏览网络上的端点 所有这些工具使编写联网 App 代码 变得前所未有的轻松 这就是全部内容 这些新的 API 从底层就针对 Swift 的结构化并发进行构建 它们以声明方式创建 就像 在 SwiftUI 中布局用户界面一样 它们在任务中运行 当任务被取消时 它们也会自动取消 尝试使用这些新 API 以充分利用 Swift 中的结构化并发 让代码更简洁并减少臃肿的样板代码 它们都具备 Network 框架 为 App 提供的强大功能和灵活性 感谢观看

    • 4:04 - Make a connection with TLS

      // Make a connection
      
      import Network
      
      let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        TLS() 
      }
    • 4:41 - Make a connection with TLS and IP options

      // Make a connection
      
      import Network
      
      let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029) {
        TLS {
          TCP {
            IP()
              .fragmentationEnabled(false)
          }
        }
      }
    • 5:07 - Make a connection with customized parameters

      // Make a connection
      
      import Network
      
      let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029),
                                         using: .parameters {
        TLS {
          TCP {
            IP()
              .fragmentationEnabled(false)
          }
        }
      }
      .constrainedPathsProhibited(true))
    • 7:30 - Send and receive on a connection

      // Send and receive on a connection
      
      import Network
      
      public func sendAndReceiveWithTLS() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          TLS()
        }
      
        let outgoingData = Data("Hello, world!".utf8)
        try await connection.send(outgoingData)
      
        let incomingData = try await connection.receive(exactly: 98).content
        print("Received data: \(incomingData)")
      }
    • 8:29 - Send and receive on a connection

      // Send and receive on a connection
      
      import Network
      
      public func sendAndReceiveWithTLS() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          TLS()
        }
      
        let outgoingData = Data("Hello, world!".utf8)
        try await connection.send(outgoingData)
      
        let remaining32 = try await connection.receive(as: UInt32.self).content
        guard var remaining = Int(exactly: remaining32) else { /* ... throw an error ... */ }
        while remaining > 0 {
          let imageChunk = try await connection.receive(atLeast: 1, atMost: remaining).content
          remaining -= imageChunk.count
      
          // Parse the next portion of the image before continuing
        }
      }
    • 11:06 - Tic-Tac-Toe game messages

      // TicTacToe game messages
      
      import Network
      
      enum GameMessage: Int {
        case selectedCharacter = 0
        case move = 1
      }
      
      struct GameCharacter: Codable {
        let character: String
      }
      
      struct GameMove: Codable {
        let row: Int
        let column: Int
      }
    • 11:24 - Send TicTacToe game messages with TLV

      // Send TicTacToe game messages with TLV
      
      import Network
      
      public func sendWithTLV() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          TLV {
            TLS()
          }
        }
      
        let characterData = try JSONEncoder().encode(GameCharacter(character: "🐨"))
        try await connection.send(characterData, type: GameMessage.selectedCharacter.rawValue)
      }
    • 11:53 - Receive TicTacToe game messages with TLV

      import Network
      
      public func receiveWithTLV() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          TLV {
            TLS()
          }
        }
      
        let (incomingData, metadata) = try await connection.receive()
        switch GameMessage(rawValue: metadata.type) {
        case .selectedCharacter:
          let character = try JSONDecoder().decode(GameCharacter.self, from: incomingData)
          print("Character selected: \(character)")
        case .move:
          let move = try JSONDecoder().decode(GameMove.self, from: incomingData)
          print("Move: \(move)")
        case .none:
          print("Unknown message")
        }
      }
    • 12:50 - Tic-Tac-Toe game messages with Coder

      // TicTacToe game messages with Coder
      
      import Network
      
      enum GameMessage: Codable {
        case selectedCharacter(String)
        case move(row: Int, column: Int)
      }
    • 13:13 - Send TicTacToe game messages with Coder

      // Send TicTacToe game messages with Coder
      
      import Network
      
      public func sendWithCoder() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          Coder(GameMessage.self, using: .json) {
            TLS()
          }
        }
      
        let selectedCharacter: GameMessage = .selectedCharacter("🐨")
        try await connection.send(selectedCharacter)
      }
    • 13:53 - Receive TicTacToe game messages with Coder

      // Receive TicTacToe game messages with Coder
      
      import Network
      
      public func receiveWithCoder() async throws {
        let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
          Coder(GameMessage.self, using: .json) {
            TLS()
          }
        }
      
        let gameMessage = try await connection.receive().content
        switch gameMessage {
        case .selectedCharacter(let character):
          print("Character selected: \(character)")
        case .move(let row, let column):
          print("Move: (\(row), \(column))")
        }
      }
    • 15:16 - Listen for incoming connections with NetworkListener

      // Listen for incoming connections with NetworkListener
      
      import Network
      
      public func listenForIncomingConnections() async throws {
        try await NetworkListener {
          Coder(GameMessage.self, using: .json) {
            TLS()
          }
        }.run { connection in
          for try await (gameMessage, _) in connection.messages {
            // Handle the GameMessage
          }
        }
      }
    • 17:39 - Browse for nearby paired Wi-Fi Aware devices

      // Browse for nearby paired Wi-Fi Aware devices
      
      import Network
      import WiFiAware
      
      public func findNearbyDevice() async throws {
        let endpoint = try await NetworkBrowser(for: .wifiAware(.connecting(to: .allPairedDevices, from: .ticTacToeService))).run { endpoints in
          .finish(endpoints.first!)
        }
      
        // Make a connection to the endpoint
      }
    • 0:00 - 开场介绍
    • 了解 Network 框架的最新进展。了解如何建立连接、发送与接收数据、监听传入连接,以及在网络中发现端点。

    • 0:45 - 建立连接
    • Network 框架简化了 App 的网络通信流程。它提供诸如“按名称连接”和“Happy Eyeballs”等功能,用于高效解析地址。它内建 TLS 安全机制,并支持 QUIC 等现代传输协议。 借助 Network 框架,你可以通过声明式 API 来创建协议栈。这个框架可自动处理网络接口切换、代理配置和连接状态,确保连接稳定且响应迅速。 NetworkConnection 对象负责管理连接的整个生命周期,并在“准备中”、“就绪”、“等待”、“失败”或“已取消”等状态之间切换。你可以选择监控这些状态,以便及时更新界面。

    • 7:22 - 发送和接收
    • 在 Network 框架中,发送和接收数据是异步操作,必要时会自动发起连接。“send”函数会暂停任务,直到所提供的数据处理完成。使用 TLS 或 TCP 等流式协议接收数据时,必须指定接收的字节数。 在执行“send”或“receive”函数时可能会发生错误,相关的错误信息将说明中断的原因。 对于数据大小未知的场景 (例如载入图像),你可以多次调用“receive”函数,并指定最小和最大字节范围来接收数据。 为简化信息处理,框架引入了 Type-Length-Value (TLV) 帧封装器,可自动编码和解码信息,确保发送与接收内容完全一致。 在 iOS 和 macOS 26 中,现已支持直接发送和接收符合 Codable 协议的类型。

    • 14:22 - 监听传入连接
    • NetworkListener 让你的 App 能够接收传入连接。它通过协议栈进行初始化,并在调用“run”后为每个传入连接启动一个新的子任务,将连接交由处理程序进行异步信息处理。

    • 16:05 - 查找其他设备
    • 在 iOS 26 中,Network 框架引入了 NetworkBrowser,让你的 App 能够发现可用于网络连接的端点。NetworkBrowser 还可利用 Wi-Fi Aware (一种点对点网络技术) 或 Bonjour 来查找附近的设备或服务。 你可以使用浏览描述符创建 NetworkBrowser 来查找特定服务,当发现端点后,即可使用这个端点来初始化 NetworkConnection。 这些为 Swift 结构化并发而构建的新 API,使联网 App 的编写更加简洁清晰,不仅免去了样板代码,还保留了 Network 框架的强大功能与灵活性。

Developer Footer

  • 视频
  • WWDC25
  • 将结构化并发代码与 Network 框架搭配使用
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则