You are reading the documentation for an older version of Realm. You can view the latest documentation instead.

React Native 版本的 Realm 能够让您以一种安全、耐用以及迅捷的方式来高效地编写应用的数据模型层。如下例所示:

// 定义您的模型以及它们的属性
class Car {}
Car.schema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: 'int',
  }
};
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name:    {type: 'string'},
    cars:    {type: 'list', objectType: 'Car'},
    picture: {type: 'data', optional: true}, // optional property
  }
};

// 获取支持我们所定义对象的默认 Realm 数据库实例
let realm = new Realm({schema: [Car, Person]});

// 创建 Realm 对象并写入到本地存储当中
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 1000,
  });
  myCar.miles += 20; // 更新某个属性值
});

// 检索 Realm 数据库中所有高行驶里程的车辆
let cars = realm.objects('Car').filtered('miles > 1000');

// 这会返回一个 Results 对象,这个对象包含我们所创建的那一辆车
cars.length // => 1

// 添加另一辆汽车
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Ford',
    model: 'Focus',
    miles: 2000,
  });

// 检索结果会即时更新
cars.length // => 2

从这里开始

按照下方的安装说明,通过 npm 来使用 React Native 版本的 Realm,或者查看 Github 上的源代码

准备工作

  • 确保您的环境已被配置好能够运行 React Native 应用。您可以参考 React Native 安装说明 来配置开发环境;
  • 使用 Realm 的应用可以运行在 iOS 和 Android 上;
  • 需要使用 React Native 0.20.0 及其以上版本;
  • 确保 React Native 包管理器 (rnpm) 是全局安装的,并且可以实时更新:

    npm install -g rnpm

安装

  • 创建一个新的 React Native 项目:

    react-native init <project-name>
  • 将当前目录切换到新项目当中 (cd <project-name>),然后添加 realm 依赖包:

    npm install --save realm
  • 接下来使用 rnpm 将您的项目链接到 realm 本地模组当中。

    rnpm link realm

您现在就完成了配置工作了。如果想查看 Realm 的运行效果的话,将以下声明添加到 index.ios.js 或者 index.android.js 当中的 class <project-name> 当中:

const Realm = require('realm');

class <project-name> extends Component {
 render() {
   let realm = new Realm({
     schema: [{name: 'Dog', properties: {name: 'string'}}]
   });

   realm.write(() => {
     realm.create('Dog', {name: 'Rex'});
   });

   return (
     <View style={styles.container}>
       <Text style={styles.welcome}>
         Count of Dogs in Realm: {realm.objects('Dog').length}
       </Text>
     </View>
   );
 }
}

接下来,您就可以在真机或者模拟器上运行您的应用了!

API 手册

您可以查询我们完整的 API 手册,里面包含了所有类和方法之类的信息。

示例

您可以通过克隆 GitHUb 上的这个项目来作为示例:

git clone https://github.com/realm/realm-js.git

接着在这个克隆下来的目录当中:

git submodule update --init --recursive

对于 Android 而言,您需要安装有 NDK,并设置 ANDROID_NDK 环境变量:

export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e

React Native 示例位于 examples 目录当中。在第一次运行每个项目之前,您需要运行 npm install

获得帮助

如果您在使用崩溃检测 SDK (诸如 Crashlytics 或者 HockeyApp) 的话,请确保开启了日志收集功能。Realm 会在抛出异常以及发生致命错误的时候会记录下元数据信息(不是用户数据),这些信息可以在出现问题的时候有效地帮助您进行解决。

数据模型(Model)

Realm 数据模型由架构 (schema) 信息定义,而这个信息是在初始化过程中传递到 Realm 当中的。一个对象的架构包含了对象的 name 信息,以及一系列包含 nametype 信息的属性,此外对于对象和列表属性来说还包含有 objectType 信息。您同样可以将每个属性指定为 optional 类型,或者必须包含 default 值。

var Realm = require('realm');

const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};
const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    birthday: 'date',
    cars:     {type: 'list', objectType: 'Car'},
    picture:  {type: 'data', optional: true}, // optional property
  }
};

// 初始化带有 Car 和 Person 模型的 Realm 实例
let realm = new Realm({schema: [CarSchema, PersonSchema]});

如果您偏好于从既有类来继承对象的话,那么您只需要在对象构造器当中定义此架构即可,然后在创建 Realm 实例的时候将这个构造器传递进去:

class Person {
  get ageSeconds() {
    return Math.floor((Date.now() - this.birthday.getTime()));
  }
  get age() {
    return ageSeconds() / 31557600000;
  }
}

Person.schema = PersonSchema;

// 注意到,这里我们传递进去的是 `Person` 构造器
let realm = new Realm({schema: [CarSchema, Person]});

一旦您定义好对象模型,接下来您就可以在 Realm 中创建和获取对象了:

realm.write(() => {
  let car = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 750,
  });

  // 您可以访问并设置您模型中定义的所有属性
  console.log('汽车的类型是 ' + car.make + ' ' + car.model);
  car.miles = 1500;
});

基础属性类型

Realm 支持以下基础类型:boolintfloatdoublestringdatadate

  • bool 属性将会映射为 JavaScript 的 Boolean 对象;
  • intfloat 以及 double 属性将会映射为 JavaScript 的 Number 对象。其中,intdouble 是以 64 位进行存储的,而 float 则是以 32 位进行存储的;
  • string 属性将会映射为 String
  • data 属性将会映射为 ArrayBuffer
  • date 属性将会映射为 Date

在以简略方式指定基础类型的时候,您只需要指定其类型就可以了,而不必指定一个单入口的字典:

const CarSchema = {
  name: 'Car',
  properties: {
    // 以下这两种属性类型定义是等价的
    make:   {type: 'string'},
    model: 'string',
  }
}

对象属性

对于对象类型来说,您只需要使用对象架构中的 name 来进行类型定义就可以了:

const PersonSchema = {
  name: 'Person',
  properties: {
    // 以下这两种属性定义方式是等价的
    car: {type: 'Car'},
    van: 'Car',
  }
};

在使用对象属性的时候,您需要确保所有已使用的引用类型都用来创建 Realm 实例了:

// CarSchema 是必需的,因为 PersonSchema 包含了 `Car` 类型属性
let realm = new Realm({schema: [CarSchema, PersonSchema]});

当访问对象属性的时候,您可以使用常规的属性语法来访问嵌套属性:

realm.write(() => {
  var nameString = person.car.name;
  person.car.miles = 1100;

  // 通过合法的 JSON 来设置属性,从而创建一个新的 Car 实例
  person.van = {make: 'Ford', model: 'Transit'};

  // 将这两个属性设置为相同的 Car 实例
  person.car = person.van;
});

列表属性

对于列表属性 (list) 来说,您必须要将属性类型指定为 list,并且还要设置 objectType

const PersonSchema = {
  name: 'Person',
  properties: {
    cars: {type: 'list', objectType: 'Car'},
  }
}

当访问列表属性的时候,会返回一个 List 对象。List 拥有和普通 JavaScript 数组相似的方法。它们之间最大的区别在于,所有对 List 进行的操作改变都会自动存储到底层 Realm 数据库当中。此外,List 的所有权在于创建它们的底层对象——也就是说,您只能通过从某个拥有者对象 (owning object) 访问相关属性来获取 List 实例,您不能够手动创建它们。

let carList = person.cars;

// 向 list 中添加新的汽车
realm.write(() => {
  carList.push({make: 'Honda', model: 'Accord', miles: 100});
  carList.push({make: 'Toyota', model: 'Prius', miles: 200});
});

let secondCar = carList[1].model;  // 使用数组索引来访问

可空属性值

可以通过在属性定义中设定 optional 指示器,将属性声明为可空或者不可空:

const PersonSchema = {
  name: 'Person',
  properties: {
    name:     {type: 'string'},               // 不可空属性
    birthday: {type: 'date', optional: true}, // 可空属性

    // 对象属性永远可空
    car:      {type: 'Car'},
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  // 可空属性在创建的时候可以被设为 null 或者 undefined
  let charlie = realm.create('Person', {
    name: 'Charlie',
    birthday: new Date(1995, 11, 25),
    car: null,
  });

  // 可空属性可以被设置为 `null`、`undefined`
  // 或者一个新的非空值
  charlie.birthday = undefined;
  charlie.car = {make: 'Honda', model: 'Accord', miles: 10000};
});

如上所示,对象属性永远是可空的,并不需要将其设定为可空。列表属性不能被声明为可空,并且也不能设置为 null。您可以使用一个空数组来设置或者初始化列表,以将其清空。

默认属性值

默认属性值 (default property value) 可以通过在属性定义中设定 default 指示器来进行指定。要使用默认值的话,在对象创建过程中不要指定该属性即可。

const CarSchema = {
  name: 'Car',
  properties: {
    make:  {type: 'string'},
    model: {type: 'string'},
    drive: {type: 'string', default: 'fwd'},
    miles: {type: 'int',    default: 0}
  }
};

realm.write(() => {
  // 由于 `miles` 没有指定,因此它会被设定为默认值 `0`
  // 由于 `drive` 已经指定,因此它会覆盖其默认值
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

索引属性

您可以在属性定义中添加一个 indexed 指示器,这样会使得该属性可以被索引 (indexed)。这个功能对 intstringbool 属性类型有效:

var BookSchema = {
  name: 'Book',
  properties: {
    name: { type: 'string', indexed: true },
    price: 'float'
  }
};

对属性进行索引会极大地加快查询的速度,因为在比较属性相等的过程中插入操作会耗费极大的开销。

主键

您可以在对象模型中,指定一个类型为 string 或者 intprimaryKey 属性。声明主键将允许对象在检索、更新时更有效率,并且对于每个值来说将保证其唯一性。一旦某个带有主键的对象被添加到 Realm 数据库之后,那么其主键将不能更改。

const BookSchema = {
  name: 'Book',
  primaryKey: 'id',
  properties: {
    id:    'int',    // 主键
    title: 'string',
    price: 'float'
  }
};

主键将自动被索引。

对象存储

对对象的所有更改(添加,修改和删除)都必须通过写入事务 (transaction) 完成。

因为写入事务将会产生不可忽略的性能消耗,因此您应当检视您的代码,以确保减少写入事务的次数。

创建对象

如上所示,您可以使用 create 方法来创建对象:

let realm = new Realm({schema: [CarSchema]);

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

嵌套对象

如果某个对象拥有对象属性,那么这些属性的值可以通过为每个子属性指定 JSON 值来依次进行创建:

let realm = new Realm({schema: [PersonSchema, CarSchema]);

realm.write(() => {
  realm.create('Person', {
    name: 'Joe',
    // 嵌套对象将依次创建
    car: {make: 'Honda', model: 'Accord', drive: 'awd'},
  });
});

更新对象

内容直接更新

您可以在写入事务中通过设置某个对象的属性从而完成对象的更新操作。

realm.write(() => {
  car.miles = 1100;
});

通过主键更新

如果您的数据模型中设置了主键的话,那么您可以基于它们的主键值让 Realm 自行更新或者添加对象。这可以通过向 create 方法中将 true 作为第三个参数进行传递来实现:

realm.write(() => {
  // 创建一个书本对象
  realm.create('Book', {id: 1, title: 'Recipes', price: 35});

  // 基于 id 来更新这本书本的价格
  realm.create('Book', {id: 1, price: 55}, true);
});

在上面这个例子中,由于对象已经存在了值为 1id,然后我们在第三个参数中传递了 true 参数,这时候价格属性将会被更新,而不是试图创建一个新的对象。由于 name 属性被忽略了,因此这个对象将会为此属性保留其原始值。注意到,当创建或者更新带有主键属性的对象时,主键必须要指明出来。

删除对象

通过在写入事务中调用 delete 方法,可以对对象进行删除。

realm.write(() => {
  // 创建一个书本对象
  let book = realm.create('Book', {id: 1, title: 'Recipes', price: 35});

  // 删除该书本
  realm.delete(book);

  // 通过传递 `Results`、`List` 
  // 或者 JavaScript `Array` 来删除多个书本
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); // 删除所有书本
});

查询

查询允许您从 Realm 数据库中获取某个单独类型的对象,您同样还可以对这些结果进行匹配和排序。所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。这允许您在处理大数据集合的时候以一个更高性能的方式进行处理。

当执行查询操作后,会返回一个 Results 对象。Results 只是数据的投影,不能够被修改。

从 Realm 中检索对象的最基本方法是使用 Realm 中的 objects 方法,这会返回该给定类型的所有对象:

let dogs = realm.objects('Dog'); // 检索 Realm 中所有的 Dog 对象

条件查询

您可以调用 filtered 方法,通过检索字符串来对 Results 进行匹配。

例如,下面的例子将会检索所有棕黄色,并且以“B”开头命名的狗狗:

let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');

目前,只有 NSPredicate 语法的某些部分能够在查询语言当中进行使用。基础的比较运算符 ==!=>>=< 以及 <= 支持数字类型。==BEGINSWITHENDSWITH 以及 CONTAINS 支持字符串属性。字符串之间的比较可以通过添加 [c] 运算符来让其能够区分大小写:比如 ==[c]BEGINSWITH[c] 之类。检索链接或者子对象的属性可以通过在查询过程中指定 keypath 来完成,例如 car.color == 'blue'

排序

Results 允许您指定一个排序标准,从而可以根据一个或多个属性进行排序。比如说,下列代码将上面例子中返回的汽车根据行驶里程升序进行排序:

let hondas = realm.objects('Car').filtered('make = "Honda"');

// 根据行驶里程进行排序
let sortedHondas = hondas.sorted('miles');

注意到,只有当检索操作排序过的时候,Results 的次序只能保证不变。出于性能考虑,插入后的顺序不能保证稳定。

自动更新

Results 是底层数据的动态表现,其会进行自动更新,这意味着检索到的结果不能进行重复检索。对检索所检索到的对象进行修改,会立即影响到检索到的结果。

let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1

这对所有的 Results 实例都有影响,不管其是通过 objectsfiltered 还是 sorted 方法所返回的。

Results 的这个特性不仅让 Realm 保证速度和效率,它同时还让代码更加简洁、更为灵活。比如说,如果您的视图控制器是基于查询结果而现实的,您可以将 Results 存储为一个属性,这样就无需在每次访问前都要刷新数据以确保数据最新了。

您也可以查看变化事件一节以确认 Realm 数据何时被更新,比如说由此来决定应用 UI 何时需要被更新,这样就无必重新检索 Results 了。

查询结果限制

大多数其他数据库技术都提供了从检索中对结果进行“分页”的能力(例如 SQLite 中的 “LIMIT” 关键字)。这通常是很有必要的,可以避免一次性从硬盘中读取太多的数据,或者将太多查询结果加载到内存当中。

由于 Realm 中的检索是惰性的,因此这行这种分页行为是没有必要的。因为 Realm 只会在检索到的结果被明确访问时,才会从其中加载对象。

如果由于 UI 相关或者其他代码实现相关的原因导致您需要从检索中获取一个特定的对象子集,这和获取 Results 对象一样简单,只需要读出您所需要的对象即可。

let cars = realm.objects('Car');

// 获取前 5 个 Car 对象
let firstCars = cars.slice(0, 5);

Realm 数据库

其他的 Realm 数据库

有的时候,在不同位置存储多个 Realm 数据库是十分有用的。 例如,如果您需要将您应用的某些数据打包到一个 Realm 文件中,作为主要 Realm 数据库的扩展。 您可以在初始化 Realm 数据库的时候指定 path 参数来实现这个功能。所有的路径以您应用程序中可写文档目录的路径为根路径:

// 用其他路径打开 Realm 文件
let realmAtAnotherPath = new Realm({
  path: 'anotherRealm.realm',
  schema: [CarSchema]
});

默认的 Realm 数据库

您可能很早就已经注意到,在之前的例子中路径参数总是被忽略掉。在这种情况下使用的就是默认 Realm 数据库路径。您可以使用 Realm.defaultPath 全局属性来访问以及修改默认 Realm 数据库路径。

架构版本

当打开 Realm 数据库的时候,最后一个可供设置的选项就是 schemaVersion 属性了。如果其被忽略的话,那么 schemaVersion 属性将默认为 0。您需要在初始化现有的某个内含对象的 Realm 数据库架构和其之前架构发生变化的时候,指定这个 schemaVersion 属性。如果架构发生了更新,而 schemaVersion 没有更新,那么就会抛出一个异常。

const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string'
  }
};

// schemaVersion 默认为 0
let realm = new Realm({schema: [PersonSchema]});

const UpdatedPersonSchema = {
  // 架构名称相同,因此之前 Ralm 当中的
  // `Person` 对象将会被更新
  name: 'Person',
  properties: {
    name: 'string',
    dog:  'Dog'     // 新属性
  }
};

// 这样会抛出异常,因为架构发生了改变
// 而 `schemaVersion` 没有变化
let realm = new Realm({schema: [UpdatedPersonSchema]});

// 这样不会有任何问题,会将 Realm 数据库更新为最新的架构
let realm = new Realm({schema: [UpdatedPersonSchema], schemaVersion: 1});

如果您希望获取当前 Realm 数据库的架构版本的话,您可以使用 Realm.schemaVersion 方法来实现。

let currentVersion = Realm.schemaVersion(Realm.defaultPath);

数据迁移(Migration)

当您使用任意一个数据库时,您随时都可能打算修改您的数据模型。 例如,假设我们有如下 Person 模型:

var PersonSchema = {
  name: 'Person',
  properties: {
    firstName: 'string',
    lastName: 'string',
    age: 'int'
  }
}

假如我们想要更新数据模型,给它添加一个 name 属性, 而不是将“姓”和“名”分离开来。 为此我们只需要改变一下架构即可,范例如下:

var PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string',
    age: 'int'
  }
}

在这个时候如果您在数据模型更新之前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm 就会抛出错误。

进行迁移

通过更新 schemaVersion 以及定义一个可选的 migration 函数,您可以定义一个迁移操作以及与之关联的架构版本。 迁移函数将会提供提供相应的逻辑操作,以让数据模型从之前的架构转换到新的架构中来。 当打开一个 Realm 的时候,迁移函数将会被执行,在迁移需要的时候,将给定的架构版本应用到更新 Realm 数据库的操作中。

如果没有提供任何的迁移函数的话,那么接下来更新到新的 schemaVersion 的时候,所有的新属性会自行添加,所有的旧属性会从数据库移除。 如果您需要在提升版本号的时候更新旧有的属性,或者计算新的属性值的话,那么您可以在迁移函数中执行之类的操作。 例如,假设我们想要把上面所声明 Person 数据模型进行迁移。您可以使用旧有的 firstNamelastName 属性来计算新架构中的 name 属性:

var realm = new Realm({
  schema: [PersonSchema],
  schemaVersion: 1,
  migration: function(oldRealm, newRealm) {
    // 只有在 schemaVersion 提升为 1 的时候才应用此变化
    if (oldRealm.schemaVersion < 1) {
      var oldObjects = oldRealm.objects('Person');
      var newObjects = newRealm.objects('Person');

      // 遍历所有对象,然后设置新架构中的 `name` 属性
      for (var i = 0; i < oldObjects.length; i++) {
        newObjects[i].name = oldObjecs[i].firstName + ' ' + oldObjects[i].lastName;
      }
    }
  }
});

var fullName = realm.objects('Person')[0].name;

一旦迁移成功结束,Realm 文件和其中的所有对象都可被您的应用正常访问。

线性迁移(Linear Migration)

对于上面所述的迁移模型来说,很可能会导致您在多个版本之间迁移的时候会遇到问题。如果用户在应用更新的过程中跳过了好几个版本,并且某个属性可能在跳过的这几个版本中发生了多次改变,那么问题很可能会发生。在这种情况下,您或许需要编辑旧有的迁移代码,让其能够从旧有的架构正确地将数据更新到现有的架构。

您可以通过连续运行多个迁移操作来避免这个问题的发生,这样可以确保数据库升级经过了每一个不同的版本,并且相关的迁移代码都能够被运行。通过这个模式,旧有的迁移代码就不必进行修改,因为您需要保留所有的旧有架构以及迁移代码块以备不时之需。有一个例子可以对其很好地说明:

var schemas = [
  { schema: schema1, schemaVersion: 1, migration: migrationFunction1 },
  { schema: schema2, schemaVersion: 2, migration: migrationFunction2 },
  ...
]

// 第一个架构将会被更新到现有的架构版本
// 因为第一个架构位于数组顶部
var nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
  var migratedRealm = new Realm(schemas[nextSchemaIndex++]);
  migratedRealm.close();
}

// 使用最新的架构打开 Realm 数据库
var realm = new Realm(schemas[schemas.length-1]);

变化事件(Change Events)

当写入事务完成的时候,就会发送变化事件。要对变化事件进行注册的话:

// 观察 Realm 变化事件
realm.addListener('change', () => {
  // 更新 UI
  ...
});

// 取消所有监听器的注册
realm.removeAllListeners();

React Native ListView

如果您偏好于使用 List 或者 Results 实例作为 ListView 的数据源的话,那么我们强烈建议您使用 realm/react-native 模组中所提供的 ListView 以及 ListView.DataSource

import { ListView } from 'realm/react-native';

这个组件的 API 和 React.ListView 完全相同,因此您可以参考 ListView 文档 以获取详细的使用方法。

加密(Encryption)

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

Realm 支持在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密。

var key = new Int8Array(64);  // 产生随机密钥
var realm = new Realm({schema: [CarObject], encryptionKey: key});

// 和往常一样使用 Realm 即可
var dogs = realm.objects('Car');

这样硬盘上的数据都能都采用AES-256来进行加密和解密,并用 SHA-2 HMAC 来进行验证。 每次您要获取一个 Realm 实例时,您都需要提供一次相同的密钥。

加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)。

疑难解答

崩溃报告

我们建议您在应用中使用崩溃报告。大多数 Realm 操作都可能会在运行时发生崩溃(就如同其他硬盘 IO 操作一样),因此从应用中收集这些崩溃报告可以帮助您(或者我们)发现出错的具体位置,从而进行错误处理以及修复问题。

绝大多数商用的崩溃报告都有收集日志的选项。我们强烈建议您开启此功能。Realm 在抛出异常或者处于无法恢复的状况时将会记录元数据信息(但不会记录用户数据),这些信息在出错的时候对调试有很大帮助。

报告 Realm 错误

如果您发现了 Realm 中的任何错误,请在 Github 上提交一个 issue,也可以给 help@realm.io 发邮件信息。尽可能给我们发送更多的信息,这可以帮助我们更好的理解并解决您提出的问题。

下面信息对我们来说将十分有用:

  1. 您的目的
  2. 您所期望的结果
  3. 实际的结果
  4. 产生此结果的步骤
  5. 突出此问题的代码示例 (如果有完整的工作项目的话,我们可以自行编译,以便更好理解)
  6. Realm 的版本
  7. 崩溃日志以及堆栈轨迹,参见上方的崩溃报告了解更多信息。

</div> </div>