主题
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 回调式 API或 macOS 回调式 API的 onSuccess / 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()
}clientMessageID、mutationID 建议使用 UUID,并在同一次业务重试中复用。 cursor、continuation、expectedRevision、serverMessageID、mediaRef 等值来自上一次 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 == .readylifecycle.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
)message.search
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.conversationIDconversation.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.conversationPreviewextension.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>rejectXHIMError。
调用成功只代表该方法定义的事实已经完成。例如 sendText 成功表示消息被 本地 Outbox 持久接受,不等于对端已经收到;最终状态通过 messageStateChanged 后重查消息确认。错误处理只依赖 domain、 stableCode、retryable、userAction,不要匹配展示文案。
所有回调的精确说明见回调详细说明;全部参数和返回 模型见各分类 Reference。