> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# 实践指南

> Swift SDK 接入中常见任务的模式与配方。

## 处理 no-fill

订阅 `.noFill` 以了解何时没有返回广告（地域限制、频次封顶等）。payload 中的 `skipCode` 会说明原因。

```swift theme={null}
onEvent: { event in
    if case .noFill(let data) = event {
        print("no fill:", data.skipCode)
    }
}
```

## 在 UIKit 列表中调整广告尺寸

在 `UITableView` / `UICollectionView` 的 cell 中，请监听 `InlineAdUIView.onHeightChange`，并在它触发时刷新行高。广告流式加载时高度会变化，并最终稳定在终值。

```swift theme={null}
let adView = InlineAdUIView(ad: ad)
adView.onHeightChange = { [weak self] height in
    self?.updateRowHeight(height)
}
```

## 会话选项的实时更新

部分会话选项可以在不重建会话的情况下实时更新。变更会在下一次 `/preload` 时读取，因此会在下一条用户消息生效。

```swift theme={null}
session.updateOptions(MutablePublisherOptions(
    variantId: "new-variant"
))
```

可实时更新的字段：`variantId`、`regulatory`、`userEmail`、`advertisingId`、`vendorId`。非 nil 字段会覆盖原值；nil 字段保持不变。如需清空某个字段，请重建会话。

<Warning>
  `publisherToken`、`userId`、`conversationId`、`enabledPlacementCodes` 与 `character` **不可**实时更新。会话中途修改它们会让 `/init` 注册与后续请求不一致，或者让已累积的消息历史指向错误的人设。请改为重建会话。
</Warning>

## 会话中途实时更新 consent

当用户在你的 CMP 中更新了 consent，调用：

```swift theme={null}
session.updateOptions(MutablePublisherOptions(
    regulatory: Regulatory(gdpr: 1, gdprConsent: "<new-TCF-string>")
))
```

下一次 preload 就会采用新值——无需重建会话。

## 切换 character

进行中的 `character` 不能实时更新——已累积的消息历史归属于原角色，会话中途切换会让消息被定向到错误的角色。

要切换角色，请销毁当前会话并创建一个新的：

```swift theme={null}
session.destroy()

session = KontextAds.createSession(SessionOptions(
    publisherToken: "<your-publisher-token>",
    userId: "user-1234",
    conversationId: "conv-new",
    character: newCharacter
))
```

`publisherToken`、`userId`、`conversationId`、`enabledPlacementCodes` 同理——其中任何一个发生变化时，请重建会话。

## 加载历史消息（会话恢复）

从后端恢复对话时，请按顺序对每条历史消息调用一次 `addMessage(_:)`。Preload 经过 10ms 防抖，连续快速调用会合并成单次 preload（针对最新的用户消息）——不会出现"每恢复一条消息就发一次 preload"的情况。

```swift theme={null}
for historicalMessage in loadedFromBackend {
    session.addMessage(Message(
        id: historicalMessage.id,
        role: historicalMessage.role,
        content: historicalMessage.content,
        createdAt: historicalMessage.timestamp
    ))
}
```

## 传入每一条消息，并用 `trackOnly` 抑制广告

请**始终**把每条消息都喂给会话，即使你不希望广告出现（例如用户处于免费试用、所在地区不投放广告、或你只想每 N 条消息展示一次广告）。跳过 `addMessage(_:)` 调用会破坏服务端用于定向的对话上下文。完整模式见 [节奏控制](/concepts/pacing)。

```swift theme={null}
let shouldShowAd = userMessageCount.isMultiple(of: 5)

session.addMessage(
    Message(id: msg.id, role: .user, content: msg.content, createdAt: Date()),
    options: AddMessageOptions(trackOnly: !shouldShowAd)
)
```

`trackOnly: true` 时 preload 仍会发出（服务端保留完整分析数据），但该消息不会触发 `.filled` 事件，对应的 `session.createAd(...)` 也不会拿到任何广告。

## SwiftUI

SDK 只提供 UIKit 视图。在 SwiftUI 中使用时，请用 `UIViewRepresentable` 包装 `InlineAdUIView`：

```swift theme={null}
import SwiftUI
import KontextSwiftSDK

struct InlineAd: UIViewRepresentable {
    let ad: Ad
    let onHeightChange: (CGFloat) -> Void

    func makeUIView(context: Context) -> InlineAdUIView {
        let view = InlineAdUIView(ad: ad)
        view.onHeightChange = onHeightChange
        return view
    }

    func updateUIView(_ uiView: InlineAdUIView, context: Context) {}
}
```
