Docs 菜单

Docs 主页开发应用程序Atlas Device SDKs

事件库 - Swift SDK

在此页面上

  • 概述
  • 开始之前
  • 启用事件记录
  • 默认事件配置
  • 将参数传递给事件配置
  • 更新事件元数据
  • 记录事件
  • 与 事件 Realm 交互
  • 记录读取或写入事件
  • 记录自定义事件
  • 事件对象序列化
  • JSON 对象序列化
  • 自定义事件序列化
  • 事件 Realm 文件大小
  • 事件库参考
  • 事件(Events)
  • 事件有效负载

事件库使您能够在用户使用支持 Sync 的移动应用程序时追踪用户的活动。事件库可以记录读取和写事务(write transaction)。开发者还可以配置自定义事件来记录按钮按下、用户界面中显示的数据或其他重要详细信息。

使用事件库时,可以指定要记录的事件。 这意味着要打开两个 Realm:

  • 用户域,用户在客户端应用程序中进行读取和写入操作

  • 事件域,事件库在其中记录限定范围的事件和自定义事件

两个 Realm 中的数据都会同步到您的 App Services App。 客户端用户从不直接与事件域或其数据交互;同步用户的事件域甚至可以与用户域不同。

由于事件库会生成大量数据:

  • 客户端设备必须有足够的容量来存储数据

  • 预计事件领域的 Device Sync 使用率高于用户领域中的读取和写入

  • 应用的后端 Atlas 集群必须有足够的存储容量来处理事件库生成的数据

事件库将数据存储在链接的 Atlas 数据源的 AuditEvent集合中。在 App Services App 中启用开发模式,让 Atlas 创建集合并从上传的事件推断模式

重要

需要基于分区的同步

事件库不支持使用 Flexible Sync 记录AuditEvents 。此功能需要基于分区的 Sync App Services App 来记录AuditEvent数据。

要启用事件记录,请在 Realm.Configuration 上设置Event.Configuration属性

您可以通过以下两种方式初始化EventConfiguration

  • 当不需要指定详细信息时,请使用默认初始化的配置

  • 传递其他参数以自定义事件配置

如果不需要指定特定参数,则可以使用默认初始化的EventConfiguration

var config = user.configuration(partitionValue: "Some partition value")
config.eventConfiguration = EventConfiguration()

您可以传递可选参数来自定义EventConfiguration

  • metadata:要附加到每个事件的元数据字段的字符串字典

  • syncUser:用于同步事件域的用户。 如果为零,则默认为Realm.Configuration中的用户。

  • partitionPrefix:要附加到事件分区值的字符串前缀

  • logger:用于事件的自定义记录器。 如果为零,则默认为用户 Realm 中的记录器。

  • errorHandler:如果上传事件数据时发生同步错误,则调用的自定义错误处理程序。 如果为nil ,SDK 会记录该事件,然后调用abort() 。 生产应用程序应始终定义事件errorHandler ,除非出现错误时中止是理想的行为。

let eventSyncUser = try await app.login(credentials: Credentials.anonymous)
var config = user.configuration(partitionValue: "Some partition value")
config.eventConfiguration = EventConfiguration(metadata: ["username": "Jason Bourne"], syncUser: eventSyncUser, partitionPrefix: "event-")

即使开始记录事件后,您也可以更新元数据。使用updateMetadata()函数将事件配置中提供的元数据替换为新值。

如果在事件作用域处于活动状态时更新元数据,则在下一个事件作用域开始之前,事件库不会使用新的元数据。

var config = user.configuration(partitionValue: "Some partition value")
config.eventConfiguration = EventConfiguration(metadata: ["username": "Jason Bourne"], syncUser: user, partitionPrefix: "event-")
let realm = try! Realm(configuration: config)
let events = realm.events!
let updateUsernameScope = events.beginScope(activity: "Update username")
// Call some function that updates the user's username
updateUsername()
updateUsernameScope.commit()
// Update the metadata you supplied with the initial EventConfiguration
events.updateMetadata(["username": "John Michael Kane"])

定义事件配置后,您可以使用 Realm 上的新 events 属性调用事件记录功能。这将返回与该Event Realm 绑定的 实例。

let realm = try! Realm(configuration: config)
let events = realm.events!

在版本 10.36.0 中进行了更改:endScope() 已弃用,而 commit() 和 cancel() 已弃用,新的 Scope 对象

事件库记录作用域上下文中的读取和写入事件。 范围是事件库监视和记录 Realm 活动的时间段。 您可以设置不同的作用域来记录不同类型的事件。 例如,您可能为特定用户流(例如“登录”)设置了作用域,为不同屏幕设置了不同作用域,或者为特定的合规性相关活动设置了不同作用域。

使用beginScope(Activity: "some Activity")开始记录具有给定活动名称的新事件。这会返回一个Scope对象,您可以使用该对象稍后提交或取消该作用域。开始范围会将该范围内发生的活动记录为读取或写入事件。

  • 读取事件:运行查询并实例化对象。 当作用域结束时,事件领域会将这些活动记录为读取事件。

  • 写入事件:修改对象。 当作用域结束时,事件领域会记录对象的初始状态,以及在事件作用域期间更改的任何属性的新值。

使用beginScope记录事件会打开事件域(如果尚未打开)。SDK 在背景线程上打开事件Realm并向错误回调报告错误。

注意

重叠事件范围

如果多个事件作用域同时处于活动状态,则所有活动作用域都会记录生成的事件。

记录完某个范围的事件后,使用commit()保存该范围内发生的事件。 结束记录后,事件库会将事件保存到本地磁盘。 然后,如果设备有网络连接,SDK 则会将数据异步发送到服务器。

// Read event
let readEventScope = events.beginScope(activity: "read object")
let person = realm.objects(Person.self).first!
print("Found this person: \(person.name)")
readEventScope.commit()
let mutateEventScope = events.beginScope(activity: "mutate object")
// Write event
try! realm.write {
// Change name from "Anthony" to "Tony"
person.name = "Tony"
}
mutateEventScope.commit()

或者,您可以cancel()事件范围。 这会停止记录事件,并且不会将事件保存到磁盘或服务器。

let eventScope = events.beginScope(activity: "read object")
let person1 = realm.objects(Person.self).first!
print("Found this person: \(person1.name)")
eventScope.cancel()

您可以使用isActive布尔值检查给定范围当前是否正在进行中。 如果您已经开始了尚未提交或取消的作用域,则会返回true

let readPersonScope = events.beginScope(activity: "read object")
let person2 = realm.objects(Person.self).first!
print("Found this person: \(person2.name)")
if readPersonScope.isActive {
print("The readPersonScope is active")
} else {
print("The readPersonScope is no longer active")
}
readPersonScope.cancel()

完成记录后,您可以向commit()传递一个可选的完成区块。 SDK 在成功持久保存事件数据时调用此区块,而不是在事件域上传完成时。

let mutateScope = events.beginScope(activity: "mutate object with completion")
// Write event
try! realm.write {
// Add a userId
person.userId = "tony.stark@starkindustries.com"
}
mutateScope.commit(completion: { error in
if let error = error {
print("Error recording write event: \(error.localizedDescription)")
return
}
print("Successfully recorded a write event")
})

事件库允许您记录按钮单击或其他不涉及数据库读写的事件。使用recordEvent记录自定义事件。该函数采用以下参数:

  • activity:活动名称。 这是一个任意字符串,例如“用户注册”。

  • eventType:事件的类型。 这是一个任意字符串,例如“按下“提交”按钮。”

  • data:事件的可选数据有效负载。

  • completion:可选的完成处理程序。 将事件保存到事件 Realm 或发生错误后,事件库会调用此完成区块。 nil 错误表示成功。

自定义事件没有读取和写入事件那样的范围。 相反,记录自定义事件更类似于触发trigger。

events.recordEvent(activity: "event", eventType: "custom event")

事件库将每个事件对象转换为 JSON 对象。 大多数Realm 类型都有类似的 JSON 表示形式。 例如,Realm 字符串属性会变为 JSON 字符串。

事件库如下表示没有直接 JSON 类似物的类型:

Date
数据
完全从事件中排除。
UUID
编码为 RFC-4122 兼容的字符串。
ObjectID
编码为ObjectID字符串表示形式。
Decimal128
编码为字符串,而不是数字。 JSON 数字在官方意义上是无限精度的,但实际上很少这样实现。
名单
编码为数组。
编码为数组。
字典
编码为对象。
嵌入式对象
编码为对象。

非嵌入式对象链接被编码为目标的主键。 在读取事件中,如果点击链接,则 this 会扩展到完整对象。 如果没有点击该链接,则它仍然是主键。

您可以为事件对象自定义 JSON 序列化。要自定义事件有效负载,请使用CustomEventRepresentable协议。

当对象符合CustomEventRepresentable时,事件库通过以下方式序列化对象:

  • 构造访问器对象

  • 正在对该访问器对象调用customEventRepresentation

  • 序列化结果而不是原始对象

要符合CustomEventRepresentable ,您的对象必须实现定义自定义序列化的customEventRepresentation函数。

// To customize event serialization, your object must
// conform to the `CustomEventRepresentable` protocol.
class Person: Object, CustomEventRepresentable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name: String
@Persisted var employeeId: Int
@Persisted var userId: String?
convenience init(name: String, employeeId: Int) {
self.init()
self.name = name
self.employeeId = employeeId
}
// To conform to `CustomEventRepresentable`, your object
// must implement a `customEventRepresentation` func that
// defines your customized event serialization
func customEventRepresentation() -> String {
if employeeId == 0 {
return "invalid json"
}
return "{\"int\": \(employeeId)}"
}
}

如果设备长时间离线,则事件域可能会变得很大。

为了弥补这一问题,事件库会根据需要自动将事件数据分割为多个分区。当分区达到其最大大小时,事件库将关闭域并自动开始写入新分区。

事件库会检查用户是否有任何未同步的分区。 如果存在,事件库会打开一个文件,上传数据,然后关闭并删除该文件。 如此重复,直到用户没有未同步的分区。

存储事件的AuditEventcollection必须在服务器上为接收事件的应用定义模式。

模式必须包含以下字段:

id
_id
ObjectId。
事件类型
event
可选字符串。 readwrite (针对作用域事件)或任意字符串(针对自定义事件)。
事件范围
activity
可选字符串。 传递给beginScope()以开始记录事件的作用域名称,或者是任意字符串以开始记录事件。
时间戳
timestamp
事件作用域结束并提交数据的设备本地时间,或者事件命中服务器的时间。
数据
data
事件有效负载。 对于限定范围的事件,这是一个 JSON blob,对于自定义事件,这是一个任意字符串。
Metadata
metadata key (字符串)
可选元数据字典。 当此字典包含键和值时,键将成为事件对象中的字段名称,而值将存储在每个事件的该字段中。

每个事件的data属性中都包含一个有效负载,用于捕获正在读取或写入的对象的当前状态。

自定义事件的有效负载可以是开发者希望的任何值,包括 nil。

重要

由于有效负载会捕获正在读取或写入的对象的当前状态,因此会产生大量数据。 但是,这必须在客户端而不是服务器上完成,因为用户查看的确切数据可能永远不存在于服务器端。 实际上,这意味着设备必须有能力在离线时存储大量数据。 此外,事件领域的 Device Sync 使用率可能远高于用户在用户领域中读写时的使用率。

例子

在上面的记录事件示例中,这些是读取和写入事件的data有效负载:

读取事件有效负载
{
"type":"Person",
"value": [{
"_id":"62b38b3c10846041166f5deb",
"_partition":"",
"employeeId":2,
"name":"Anthony",
"userId":null
}]
}
写入事件有效负载
{
"Person": {
"modifications": [{
"newValue": {
"name":"Tony"
},
"oldValue":{
"_id":"62b38b3c10846041166f5deb",
"_partition":"",
"employeeId":2,
"name":"Anthony",
"userId":null
}
}]
}
}
← 设置客户端日志级别 — Swift SDK