mcpField
R
rivetkit-client-swiftui

rivetkit-client-swiftui

github.com/rivet-dev开发447.3k 次安装1 天前更新
代码仓库

关于 rivetkit-client-swiftui

面向 SwiftUI 应用开发者的技能,介绍如何通过 `RivetKitSwiftUI` 连接 Rivet Actors。内容涵盖 `@Actor` 属性包装器、动作调用、事件流、连接状态及客户端配置。

主要功能

  1. 01通过 `@Actor` 包装器连接 Actor
  2. 02异步或即发即弃地调用动作
  3. 03订阅类型化事件和事件流
  4. 04查看连接状态和错误
  5. 05在视图树中配置客户端

适用场景

  1. 01构建由 Rivet Actors 支持的 SwiftUI 视图
  2. 02从应用界面调用 Actor 动作
  3. 03展示 Actor 连接状态并处理错误

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-swiftui

安装后在 Claude Code 中输入/rivetkit-client-swiftui确认已加载。

附带文件index.json

详细介绍

RivetKit SwiftUI 客户端

构建通过 RivetKitSwiftUI 连接到 Rivet Actor 的 SwiftUI 应用时,请使用此技能。

版本

RivetKit 版本:2.3.7

错误处理策略

  • 默认优先采用快速失败行为。
  • 除非绝对必要,否则避免使用宽泛的 do/catch。
  • 如果使用 catch 块,请显式处理错误,至少将其记录下来。

安装

添加 Swift 包依赖并导入 RivetKitSwiftUI:

// Package.swift
dependencies: [
    .package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0")
]

targets: [
    .target(
        name: "MyApp",
        dependencies: [
            .product(name: "RivetKitSwiftUI", package: "rivetkit-swift")
        ]
    )
]

RivetKitSwiftUI 会重新导出 RivetKitClient 和 SwiftUI,因此只需导入一次即可同时使用两者。

最简客户端

import RivetKitSwiftUI
import SwiftUI

@main
struct HelloWorldApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:[email protected]")
        }
    }
}
import RivetKitSwiftUI
import SwiftUI

struct ContentView: View {
    @Actor("counter", key: ["my-counter"]) private var counter
    @State private var count = 0

    var body: some View {
        VStack(spacing: 16) {
            Text("\(count)")
                .font(.system(size: 64, weight: .bold, design: .rounded))

            Button("Increment") {
                counter.send("increment", 1)
            }
            .disabled(!counter.isConnected)
        }
        .task {
            count = (try? await counter.action("getCount")) ?? 0
        }
        .onActorEvent(counter, "newCount") { (newCount: Int) in
            count = newCount
        }
    }
}

Actor 选项

@Actor 属性包装器始终采用获取或创建语义,并接受:

  • name(必需)
  • key,类型为 String 或 [String](必需)
  • params(可选的连接参数)
  • createWithInput(可选的创建输入)
  • createInRegion(可选的创建区域提示)
  • enabled(切换连接生命周期)
import RivetKitSwiftUI
import SwiftUI

struct ConnParams: Encodable {
    let authToken: String
}

struct ChatView: View {
    @Actor(
        "chatRoom",
        key: ["general"],
        params: ConnParams(authToken: "jwt-token"),
        enabled: true
    ) private var chat

    var body: some View {
        Text("Chat: \(chat.connStatus.rawValue)")
    }
}

操作

import RivetKitSwiftUI
import SwiftUI

struct CounterView: View {
    @Actor("counter", key: ["my-counter"]) private var counter
    @State private var count = 0
    @State private var name = ""

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Text("Name: \(name)")

            Button("Fetch") {
                Task {
                    count = try await counter.action("getCount")
                    name = try await counter.action("rename", "new-name")
                }
            }

            Button("Increment") {
                counter.send("increment", 1)
            }
        }
    }
}

订阅事件

import RivetKitSwiftUI
import SwiftUI

struct GameView: View {
    @Actor("game", key: ["game-1"]) private var game
    @State private var count = 0
    @State private var isGameOver = false

    var body: some View {
        VStack {
            Text("Count: \(count)")
            if isGameOver {
                Text("Game Over!")
            }
        }
        .onActorEvent(game, "newCount") { (newCount: Int) in
            count = newCount
        }
        .onActorEvent(game, "gameOver") {
            isGameOver = true
        }
    }
}

异步事件流

import RivetKitSwiftUI
import SwiftUI

struct ChatView: View {
    @Actor("chatRoom", key: ["general"]) private var chat
    @State private var messages: [String] = []

    var body: some View {
        List(messages, id: \.self) { message in
            Text(message)
        }
        .task {
            for await message in chat.events("message", as: String.self) {
                messages.append(message)
            }
        }
    }
}

连接状态

import RivetKitSwiftUI
import SwiftUI

struct StatusView: View {
    @Actor("counter", key: ["my-counter"]) private var counter
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Status: \(counter.connStatus.rawValue)")

            if counter.connStatus == .connected {
                Text("Connected!")
                    .foregroundStyle(.green)
            }

            Button("Fetch via Handle") {
                Task {
                    if let handle = counter.handle {
                        count = try await handle.action("getCount", as: Int.self)
                    }
                }
            }
            .disabled(!counter.isConnected)
        }
    }
}

错误处理

import RivetKitSwiftUI
import SwiftUI

struct UserView: View {
    @Actor("user", key: ["user-123"]) private var user
    @State private var errorMessage: String?
    @State private var username = ""

    var body: some View {
        VStack {
            TextField("Username", text: $username)

            Button("Update Username") {
                Task {
                    do {
                        let _: String = try await user.action("updateUsername", username)
                    } catch let error as ActorError {
                        errorMessage = "\(error.code): \(String(describing: error.metadata))"
                    }
                }
            }

            if let errorMessage {
                Text(errorMessage)
                    .foregroundStyle(.red)
            }
        }
        .onActorError(user) { error in
            errorMessage = "\(error.group).\(error.code): \(error.message)"
        }
    }
}

概念

键

键用于唯一标识 Actor 实例。使用复合键(数组)进行分层寻址:

import RivetKitSwiftUI
import SwiftUI

struct OrgChatView: View {
    @Actor("chatRoom", key: ["org-acme", "general"]) private var room

    var body: some View {
        Text("Room: \(room.connStatus.rawValue)")
    }
}

如果 userId 包含用户数据,请勿使用字符串插值(例如 "org:\(userId)")构建键。请改用数组,以防止键注入攻击。

环境配置

在视图树的根部调用一次 .rivetKit(endpoint:) 或 .rivetKit(client:):

// With endpoint string (recommended for most apps)
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:[email protected]")
        }
    }
}

// With custom client (for advanced configuration)
@main
struct MyApp: App {
    private let client = RivetKitClient(
        config: try! ClientConfig(endpoint: "https://api.rivet.dev", token: "pk_...")
    )

    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(client: client)
        }
    }
}

使用 .rivetKit(endpoint:) 时,客户端会创建一次,并按 endpoint 缓存。使用 .rivetKit(client:) 时,请将客户端作为 App 的属性存储(而不是放在 body 内),因为 SwiftUI 可能会多次调用 body。

环境变量

ClientConfig 会从环境变量读取可选值:

  • RIVET_NAMESPACE - 命名空间(也可放在 endpoint URL 中)
  • RIVET_TOKEN - 身份验证令牌(也可放在 endpoint URL 中)
  • RIVET_RUNNER - Runner 名称(默认为 "default")

endpoint 始终是必需的。没有默认 endpoint。

Endpoint 格式

Endpoint 支持 URL 身份验证语法:

https://namespace:[email protected]

你也可以传入不含身份验证信息的 endpoint,并单独提供 RIVET_NAMESPACE 和 RIVET_TOKEN。对于无服务器部署,请将 endpoint 设置为应用的 /api/rivet URL。详情请参阅Endpoints。

API 参考

属性包装器
  • @Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:) - 用于 Actor 连接的 SwiftUI 属性包装器
视图修饰符
  • .rivetKit(endpoint:) - 使用 endpoint URL 配置客户端(创建并缓存客户端)
  • .rivetKit(client:) - 使用自定义实例配置客户端
  • .onActorEvent(actor, event) { ... } - 订阅 Actor 事件(支持 0–5 个类型化参数)
  • .onActorError(actor) { error in ... } - 处理 Actor 错误
ActorObservable
  • actor.action(name, args..., as:) - 异步调用操作
  • actor.send(name, args...) - 发送并立即返回的操作
  • actor.events(name, as:) - 类型化事件的 AsyncStream
  • actor.connStatus - 当前连接状态
  • actor.isConnected - 是否已连接
  • actor.handle - 底层 ActorHandle(可选)
  • actor.connection - 底层 ActorConnection(可选)
  • actor.error - 最近发生的错误(可选)
类型
  • ActorConnStatus - 连接状态枚举(.idle、.connecting、.connected、.disconnected、.disposed)
  • ActorError - 类型化 Actor 错误,包含 group、code、message、metadata

需要的不只是客户端?

如果你还需要了解 Rivet Actor、注册表或服务端 RivetKit,请添加主技能:

npx skills add rivet-dev/skills

然后使用 rivetkit 技能获取后端指导。

评论

支持 Markdown