Skip to content

100 个公开 API · Swift Concurrency 示例

适用版本:0.1 Commercial Beta 示例基准:Swift 5.9+ / XHIM iOS 与 macOS Facade;同一 api-id 的 Android、 Windows、HarmonyOS 名称见原生平台 API 名称映射

本页为 public-api-surface.tsv 中全部公开能力提供 Swift Concurrency 最小 示例,适合已经使用 async/await 的 Repository 和 SDK 维护者。

如果你是第一次接入 iOS / macOS,不需要从本页开始,也不需要在页面里到处写 try。请直接使用 iOS 回调式 APImacOS 回调式 APIonSuccess / onFailure 写法;两套 API 共用同一个 Client。

1. 示例约定

下面片段默认已经存在:

swift
import Foundation
import XHIM

let server = "https://im.example.com"
let appID = "your-app-id"
let userID = "alice"
let conversationID = "conversation-id"
let peerUserID = "bob"

// 生产环境由购买方自己的业务登录层提供,不把管理员密钥放进 App。
let credentialProvider: XHIMCredentialProvider = {
    try await MyAccountAPI.fetchXHIMAccessToken()
}

clientMessageIDmutationID 建议使用 UUID,并在同一次业务重试中复用。 cursorcontinuationexpectedRevisionserverMessageIDmediaRef 等值来自上一次 SDK 查询结果,不要自行伪造。复杂输入模型的字段说明见 模型、枚举与错误

2. 原生平台基础流程

2.1 创建并登录

swift
let client = try await XHIMClient.connect(
    server: server,
    userID: userID,
    appID: appID,
    authentication: .business(credentialProvider)
)
kotlin
val client = XHIMClient.connect(
    context = applicationContext,
    server = server,
    userId = userId,
    appId = appId,
    authentication = XHIMAuthentication.Business(
        XHIMCredentialProvider {
            accountApi.fetchXHIMAccessToken()
        },
    )
)
csharp
var client = await XHIMClient.ConnectAsync(
    server,
    userId,
    appId,
    XHIMAuthentication.Business(
        cancellationToken => accountApi.FetchXHIMAccessTokenAsync(
            cancellationToken)));
ts
const client = await XHIMClient.connect(
  getContext(this),
  server,
  userId,
  {
    appId,
    authentication: XHIMAuthentication.business(
      async (): Promise<string> => accountApi.fetchXHIMAccessToken()
    )
  }
)

2.2 先订阅,再查询首屏

swift
let eventTask = Task {
    for await event in client.events {
        await MainActor.run { viewModel.consume(event) }
    }
}
let firstPage = try await client.conversations(limit: 50)
kotlin
val eventJob = viewModelScope.launch {
    client.events.collect { event -> viewModel.consume(event) }
}
val firstPage = client.conversations(limit = 50)
csharp
await foreach (var evt in client.Events(cancellationToken))
{
    await dispatcher.InvokeAsync(() => viewModel.Consume(evt));
}
var firstPage = await client.ListConversationsAsync(
    null, 50, cancellationToken);
ts
const off = client.onEvent((event: XHIMEvent) => {
  this.viewModel.consume(event)
})
const firstPage = await client.conversations(undefined, 50)

2.3 发送文本

swift
let receipt = try await client.sendText(
    conversationID: conversationID,
    clientMessageID: UUID().uuidString.lowercased(),
    text: "你好,XHIM"
)
kotlin
val receipt = client.sendText(
    conversationId = conversationId,
    clientMessageId = UUID.randomUUID().toString(),
    text = "你好,XHIM"
)
csharp
var receipt = await client.SendTextAsync(
    conversationId,
    "你好,XHIM",
    Guid.NewGuid().ToString("N"),
    cancellationToken);
ts
const receipt = await client.sendText(
  conversationId,
  '你好,XHIM',
  util.generateRandomUUID()
)

3. 生命周期、连接与诊断

lifecycle.connect

swift
let client = try await XHIMClient.connect(
    server: server,
    userID: userID,
    appID: appID,
    authentication: .business(credentialProvider)
)

lifecycle.create

底层手动装配入口,普通产品优先用 connect

swift
let storageURL = FileManager.default.urls(
    for: .applicationSupportDirectory,
    in: .userDomainMask
)[0].appending(path: "XHIM/\(appID)/\(userID)", directoryHint: .isDirectory)
let client = try XHIMClient(configuration: XHIMClientConfiguration(
    appID: appID,
    storageURL: storageURL
))

lifecycle.start

swift
try await client.start()

lifecycle.login

swift
let token = try await credentialProvider()
try await client.login(accountHint: userID, accessToken: token)

lifecycle.update_credential

swift
try await client.updateCredential(
    accessToken: try await credentialProvider()
)

lifecycle.logout

swift
try await client.logout()

lifecycle.state

swift
let state = try client.state()
let canWrite = state == .ready

lifecycle.notify_network_available

swift
// NWPathMonitor 从不可用变为可用时调用;重连计时器仍是兜底。
try client.notifyNetworkAvailable()

lifecycle.diagnostics

swift
let diagnostics = try client.diagnostics()
supportLogger.record(diagnostics)

lifecycle.device_session_policy

swift
let policy = try client.deviceSessionPolicySnapshot()
print(policy.multiLoginPolicy, policy.maxSessionsPerUser)

lifecycle.compatibility

swift
let compatibility = try client.compatibilitySnapshot()
telemetry.record(compatibility)

lifecycle.shutdown

swift
eventTask.cancel()
await client.shutdown()

4. 事件订阅

events.subscribe

swift
let eventTask = Task {
    for await event in client.events {
        await MainActor.run { viewModel.consume(event) }
    }
}
// 页面或账号容器结束时:
eventTask.cancel()

逐事件载荷、触发条件和刷新动作见 回调详细说明

5. 消息

message.send_text

swift
let clientMessageID = UUID().uuidString.lowercased()
let receipt = try await client.sendText(
    conversationID: conversationID,
    clientMessageID: clientMessageID,
    text: "第一条消息"
)

message.send_custom

swift
let outgoing = XHIMOutgoingMessage(
    contentType: "com.example.order-card",
    contentVersion: 1,
    payload: try JSONEncoder().encode(orderCard),
    fallbackText: "[订单] \(orderCard.title)"
)
let receipt = try await client.sendMessage(
    conversationID: conversationID,
    clientMessageID: UUID().uuidString.lowercased(),
    message: outgoing
)

message.edit_text

swift
let edited = try await client.editText(
    conversationID: conversationID,
    serverMessageID: serverMessageID,
    mutationID: UUID().uuidString.lowercased(),
    expectedRevision: message.mutationRevision,
    text: "修改后的内容"
)

message.recall

swift
let recalled = try await client.recall(
    conversationID: conversationID,
    serverMessageID: serverMessageID,
    mutationID: UUID().uuidString.lowercased(),
    expectedRevision: message.mutationRevision
)

message.retry

swift
let receipt = try await client.retryMessage(
    clientMessageID: failedMessage.clientMessageID
)

message.cancel

swift
let receipt = try await client.cancelMessage(
    clientMessageID: pendingMessage.clientMessageID
)

message.get

swift
let message = try await client.message(clientMessageID: clientMessageID)

message.list

swift
let page = try await client.messages(
    conversationID: conversationID,
    cursor: nil,
    limit: 50
)
let nextPage = try await client.messages(
    conversationID: conversationID,
    cursor: page.nextCursor,
    limit: 50
)

message.history

swift
let history = try await client.getMessageHistory(
    conversationID: conversationID,
    continuation: nil,
    limit: 50
)
let older = try await client.getMessageHistory(
    conversationID: conversationID,
    continuation: history.continuation,
    limit: 50
)
swift
let query = XHIMMessageSearchQuery(
    text: "合同",
    conversationID: conversationID
)
let page = try await client.searchMessages(query, limit: 50)

message.delete_for_self

swift
let result = try await client.deleteMessageForSelf(
    conversationID: conversationID,
    serverMessageID: serverMessageID,
    mutationID: UUID().uuidString.lowercased(),
    expectedRevision: 0
)

6. 会话、已读与草稿

conversation.clear

swift
let result = try await client.clearConversation(
    conversationID: conversationID,
    throughServerSequence: lastVisibleServerSequence,
    mutationID: UUID().uuidString.lowercased()
)

conversation.hide

swift
let result = try await client.hideConversation(
    conversationID: conversationID,
    mutationID: UUID().uuidString.lowercased()
)

conversation.list

swift
let page = try await client.conversations(cursor: nil, limit: 50)

conversation.direct

swift
let direct = try await client.directConversation(with: peerUserID)
let conversationID = direct.conversationID

conversation.mark_read

swift
let read = try await client.markConversationRead(
    conversationID: conversationID,
    throughServerSequence: lastVisibleServerSequence
)

conversation.mark_all_read

swift
let result = try await client.markAllConversationsRead(
    mutationID: UUID().uuidString.lowercased()
)

conversation.total_unread

swift
let unread = try await client.totalUnreadCount()

conversation.peer_reads

swift
let page = try await client.conversationPeerReads(
    conversationID: conversationID,
    limit: 200
)

conversation.set_preference

swift
let preference = try await client.setConversationPreference(
    conversationID: conversationID,
    isPinned: true,
    notificationsMuted: false,
    mutationID: UUID().uuidString.lowercased(),
    expectedRevision: conversation.preferenceRevision
)

conversation.set_draft

swift
let draft = try await client.setLocalDraft(
    conversationID: conversationID,
    message: XHIMOutgoingMessage(
        contentType: "text/plain",
        contentVersion: 1,
        payload: Data("未发送内容".utf8),
        fallbackText: "未发送内容"
    )
)

conversation.get_draft

swift
let draft = try await client.localDraft(conversationID: conversationID)

conversation.clear_draft

swift
let cleared = try await client.clearLocalDraft(
    conversationID: conversationID
)

7. 用户、在线状态与 Push

user.current_profile

swift
let me = try await client.currentUserProfile()

user.batch_profiles

swift
let profiles = try await client.userProfiles(
    userIDs: ["alice", "bob", "carol"]
)

user.update_profile

swift
let update = XHIMUserProfileUpdate(
    displayName: "Alice",
    avatarURL: "https://cdn.example.com/avatar/alice.png"
)
let me = try await client.updateCurrentUserProfile(update)

user.publish_presence

swift
let publication = try await client.publishPresence(
    .online,
    ttlMilliseconds: 60_000
)

user.publish_typing

swift
let publication = try await client.publishTyping(
    conversationID: conversationID,
    isTyping: true,
    ttlMilliseconds: 5_000
)

push.register

swift
let registration = try await client.registerPushDevice(XHIMPushDevice(
    platform: .apns,
    deviceID: deviceID,
    token: apnsToken,
    environment: "production"
))

push.disable

swift
let result = try await client.disablePushDevice(deviceID: deviceID)

8. 登录设备

session.list

swift
let page = try await client.listDeviceSessions()
let otherSessions = page.sessions.filter { !$0.isCurrent }

session.revoke

swift
let result = try await client.revokeDeviceSession(
    sessionID: session.sessionID,
    mutationID: UUID().uuidString.lowercased()
)

9. 好友与黑名单

relationship.send_friend_request

swift
let request = try await client.sendFriendRequest(
    toUserID: peerUserID,
    introduction: "你好,我是 Alice",
    mutationID: UUID().uuidString.lowercased()
)

relationship.resolve_friend_request

swift
let result = try await client.resolveFriendRequest(
    requestID: request.requestID,
    decision: .accept,
    mutationID: UUID().uuidString.lowercased()
)

relationship.delete_friendship

swift
let result = try await client.deleteFriendship(
    peerUserID: peerUserID,
    mutationID: UUID().uuidString.lowercased()
)

relationship.set_friend_remark

swift
let result = try await client.setFriendRemark(
    peerUserID: peerUserID,
    remark: "王经理",
    expectedRevision: friendship.remarkRevision,
    mutationID: UUID().uuidString.lowercased()
)

relationship.list_friend_requests

swift
let page = try await client.friendRequests(cursor: nil, limit: 50)

relationship.list_friendships

swift
let page = try await client.friendships(cursor: nil, limit: 50)

relationship.set_block

swift
let block = try await client.setBlock(
    blockedUserID: peerUserID,
    isActive: true,
    mutationID: UUID().uuidString.lowercased()
)

relationship.list_blocks

swift
let page = try await client.blocks(cursor: nil, limit: 50)

10. 群组

group.create

swift
let change = try await client.createGroup(
    title: "项目讨论组",
    memberUserIDs: ["bob", "carol"],
    mutationID: UUID().uuidString.lowercased()
)

group.change_members

swift
let change = try await client.changeGroupMembers(
    conversationID: conversationID,
    addUserIDs: ["dave"],
    removeUserIDs: [],
    expectedRevision: group.revision,
    mutationID: UUID().uuidString.lowercased()
)

group.leave

swift
let result = try await client.leaveGroup(
    conversationID: conversationID,
    expectedRevision: group.revision,
    mutationID: UUID().uuidString.lowercased()
)

group.dismiss

swift
let result = try await client.dismissGroup(
    conversationID: conversationID,
    expectedRevision: group.revision,
    mutationID: UUID().uuidString.lowercased()
)

group.request_join

swift
let result = try await client.requestGroupJoin(
    conversationID: conversationID,
    introduction: "申请加入",
    mutationID: UUID().uuidString.lowercased()
)

group.resolve_join

swift
let result = try await client.resolveGroupJoin(
    requestID: joinRequest.requestID,
    decision: .accept,
    mutationID: UUID().uuidString.lowercased()
)

group.change_governance

swift
let result = try await client.changeGroupGovernance(
    conversationID: conversationID,
    expectedRevision: group.revision,
    mutationID: UUID().uuidString.lowercased(),
    change: governanceChange
)

group.list

swift
let page = try await client.groups(cursor: nil, limit: 50)

group.list_members

swift
let page = try await client.groupMembers(
    conversationID: conversationID,
    cursor: nil,
    limit: 100
)

group.list_join_requests

swift
let page = try await client.groupJoinRequests(cursor: nil, limit: 50)

11. 图片、视频、音频、文件与缓存

media.accept_upload

swift
let task = try await client.acceptMediaUpload(XHIMMediaUploadIntent(
    conversationID: conversationID,
    localFileURL: localFileURL,
    mimeType: "application/pdf",
    contentType: "xhim.media.file",
    fallbackText: "[文件] contract.pdf",
    displayName: "contract.pdf"
))

media.accept_download

swift
let task = try await client.acceptMediaDownload(XHIMMediaDownloadIntent(
    cacheKey: "message-\(message.clientMessageID)",
    mediaRef: mediaRef
))

media.get_task

swift
let snapshot = try await client.mediaTask(taskID: task.taskID)

media.cancel_task

swift
let cancelled = try await client.cancelMediaTask(taskID: task.taskID)

media.open_cache

swift
let reader = try await client.openMediaCacheReader(
    cacheKey: "message-\(message.clientMessageID)",
    mediaRef: mediaRef
)

media.send_image

swift
let task = try await client.sendImage(
    conversationID: conversationID,
    fileURL: imageURL,
    mimeType: "image/jpeg",
    width: 1920,
    height: 1080
)

media.send_video

swift
let task = try await client.sendVideo(
    conversationID: conversationID,
    fileURL: videoURL,
    mimeType: "video/mp4",
    durationMilliseconds: 12_500,
    width: 1920,
    height: 1080
)

media.send_audio

swift
let task = try await client.sendAudio(
    conversationID: conversationID,
    fileURL: audioURL,
    mimeType: "audio/m4a",
    durationMilliseconds: 8_200,
    waveform: waveformData
)

media.send_file

swift
let task = try await client.sendFile(
    conversationID: conversationID,
    fileURL: localFileURL,
    mimeType: "application/pdf",
    displayName: "contract.pdf"
)

media.factory_image

swift
let outgoing = try XHIMMediaMessageFactory.image(
    original: mediaRef,
    thumbnail: thumbnailRef,
    width: 1920,
    height: 1080
)

media.factory_audio

swift
let outgoing = try XHIMMediaMessageFactory.audio(
    media: mediaRef,
    durationMilliseconds: 8_200,
    waveform: waveformData
)

media.factory_video

swift
let outgoing = try XHIMMediaMessageFactory.video(
    media: mediaRef,
    cover: coverRef,
    durationMilliseconds: 12_500,
    width: 1920,
    height: 1080
)

media.factory_file

swift
let outgoing = try XHIMMediaMessageFactory.file(
    media: mediaRef,
    displayName: "contract.pdf"
)

media.cache_read

swift
let firstChunk = try await reader.read(offset: 0, maximumBytes: 64 * 1_024)

media.cache_chunks

swift
for try await chunk in reader.chunks(chunkSize: 64 * 1_024) {
    player.consume(chunk)
}

media.cache_close

swift
await reader.close()

12. 离线只读

offline.open

swift
let offline = try await XHIMOfflineReader.open(
    databaseURL: databaseURL,
    accountID: userID,
    keyProvider: { try await keychain.loadXHIMDatabaseKey() }
)

offline.messages

swift
let page = try await offline.messages(
    conversationID: conversationID,
    cursor: nil,
    limit: 50
)

offline.search_messages

swift
let page = try await offline.searchMessages(
    XHIMMessageSearchQuery(text: "合同"),
    cursor: nil,
    limit: 50
)

offline.conversations

swift
let page = try await offline.conversations(cursor: nil, limit: 50)

offline.friend_requests

swift
let page = try await offline.friendRequests(cursor: nil, limit: 50)

offline.friendships

swift
let page = try await offline.friendships(cursor: nil, limit: 50)

offline.groups

swift
let page = try await offline.groups(cursor: nil, limit: 50)

offline.group_members

swift
let page = try await offline.groupMembers(
    conversationID: conversationID,
    cursor: nil,
    limit: 100
)

offline.blocks

swift
let page = try await offline.blocks(cursor: nil, limit: 50)

offline.group_join_requests

swift
let page = try await offline.groupJoinRequests(cursor: nil, limit: 50)

offline.close

swift
await offline.close()

13. 自定义消息插件与标准消息

extension.plugin_register

swift
let registry = XHIMMessagePluginRegistry()
try registry.register(OrderCardPlugin())

extension.plugin_unregister

swift
let removed = registry.unregister(contentType: "com.example.order-card")

extension.plugin_validate

swift
try registry.validate(outgoing)

extension.plugin_present

swift
let presentation = registry.presentation(for: message)
conversationCell.preview = presentation.conversationPreview

extension.standard_sticker

swift
let outgoing = try XHIMStandardMessageFactory.sticker(
    packID: "default",
    stickerID: "smile",
    emoji: "🙂"
)

extension.standard_location

swift
let outgoing = try XHIMStandardMessageFactory.location(
    latitude: 31.2304,
    longitude: 121.4737,
    name: "上海",
    address: "上海市"
)

extension.standard_contact_card

swift
let outgoing = try XHIMStandardMessageFactory.contactCard(
    userID: peerUserID,
    displayName: "Bob",
    avatarURL: "https://cdn.example.com/avatar/bob.png"
)

extension.standard_quote

swift
let outgoing = try XHIMStandardMessageFactory.quote(
    sourceServerMessageID: sourceServerMessageID,
    sourceSenderUserID: source.senderUserID,
    sourcePreview: source.fallbackText,
    text: "同意这个方案"
)

extension.standard_mention

swift
let outgoing = try XHIMStandardMessageFactory.mention(
    text: "@Bob 请看一下",
    mentionedUserIDs: [peerUserID],
    displayFallback: "@Bob 请看一下"
)

extension.standard_merged_forward

swift
let outgoing = try XHIMStandardMessageFactory.mergedForward(
    title: "项目讨论记录",
    items: forwardItems
)

14. 返回值、错误与关联回调

每个异步方法都通过语言原生机制返回结果或错误:

  • Swift:async throws
  • Kotlin:suspend 抛出 XHIMException
  • C#:Task<T> 抛出 XHIMException,支持 CancellationToken
  • ArkTS:Promise<T> reject XHIMError

调用成功只代表该方法定义的事实已经完成。例如 sendText 成功表示消息被 本地 Outbox 持久接受,不等于对端已经收到;最终状态通过 messageStateChanged 后重查消息确认。错误处理只依赖 domainstableCoderetryableuserAction,不要匹配展示文案。

所有回调的精确说明见回调详细说明;全部参数和返回 模型见各分类 Reference。

XHIM 客户端 SDK 与服务端文档