事件库 - Swift SDK
在此页面上
Overview
事件库使您能够在用户使用支持 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 交互
定义事件配置后,您可以使用 Realm 上的新 events 属性调用事件记录功能。这将返回与该 Realm 绑定的Event
实例。
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 对象序列化
事件库将每个事件对象转换为 JSON 对象。 大多数Realm 类型都有类似的 JSON 表示形式。 例如,Realm 字符串属性会变为 JSON 字符串。
事件库如下表示没有直接 JSON 类似物的类型:
Date | |
数据 | 完全从事件中排除。 |
UUID | 编码为 RFC-4122 兼容的string 。 |
ObjectID | 编码为ObjectId string表示形式。 |
Decimal128 | 编码为字符串,而不是数字。 JSON 数字在官方意义上是无限精度的,但实际上很少这样实现。 |
名单 | 编码为数组。 |
集 | 编码为数组。 |
Dictionary | 编码为对象。 |
嵌入式对象 | 编码为对象。 |
非嵌入式对象链接被编码为目标的主键。 在读取事件中,如果点击链接,则 this 会扩展到完整对象。 如果没有点击该链接,则它仍然是主键。
自定义事件序列化
您可以为事件对象自定义 JSON 序列化。 要自定义事件有效负载,请使用CustomEventRepresentable协议。
当对象符合CustomEventRepresentable
时,事件库通过以下方式序列化对象:
构造访问器对象
正在对该访问器对象调用
customEventRepresentation
序列化结果而不是原始对象
要符合CustomEventRepresentable
,您的对象必须实现定义自定义序列化的customEventRepresentation函数。
// To customize event serialization, your object must // conform to the `CustomEventRepresentable` protocol. class Person: Object, CustomEventRepresentable { true) var _id: ObjectId (primaryKey: var name: String var employeeId: Int 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)}" } }
事件 Realm 文件大小
如果设备长时间离线,则事件域可能会变得很大。
为了弥补这一问题,事件库会根据需要自动将事件数据分割为多个分区。当分区达到其最大大小时,事件库将关闭域并自动开始写入新分区。
事件库会检查用户是否有任何未同步的分区。 如果存在,事件库会打开一个文件,上传数据,然后关闭并删除该文件。 如此重复,直到用户没有未同步的分区。
事件库参考
事件(Events)
存储事件的AuditEvent
collection必须在服务器上为接收事件的应用定义模式。
模式必须包含以下字段:
id | _id | ObjectId. |
事件类型 | event | 可选字符串。 read 或write (针对作用域事件)或任意字符串(针对自定义事件)。 |
事件范围 | 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 } }] } }