Swift - Realm数据库的使用详解(附样例)
作者:hangge | 2015-10-08 15:13
(本文代码已升级至Swift4)
1,什么是Realm
Realm 于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代 Core Data 和 SQLite。
2,关于Realm,你要知道下面几点:
(1)使用简单,大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,学习成本低。
(2)Realm 不是基于 Core Data,也不是基于 SQLite 封装构建的。它有自己的数据库存储引擎。
(3)Realm 具有良好的跨平台特性,可以在 iOS 和 Android 平台上共同使用。代码可以使用 Swift 、 Objective-C 以及 Java 语言来编写。
(4)Realm 还提供了一个轻量级的数据库查看工具(Realm Browser)。你也可以用它进行一些简单的编辑操作(比如插入和删除操作)
4,Realm的安装配置
5,将数据插入到数据库中
(2)判断数据库记录是否为空,空的话则插入数据库(这里以默认数据库为例)
7,使用Realm Browser查看数据库
11,查询结果的排序
12,使用List实现一对多关系
List 中可以包含简单类型的 Object,表面上和可变的 Array 非常类似。
注意:List 只能够包含 Object 类型,不能包含诸如String之类的基础类型。
如果打算给我们的 Person 数据模型添加一个“dogs”属性,以便能够和多个“dogs”建立关系,也就是表明一个 Person 可以有多个 Dog,那么我们可以声明一个List类型的属性:
反向关系(Inverse Relationship)
13,添加主键(Primary Keys)
14,添加索引属性(Indexed Properties)
重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引:
15,设置忽略属性(Ignored Properties)
16,修改更新数据
17,删除数据
18,Realm数据库配置
如果需要将应用的某些数据(比如配置信息,初始化信息等)打包到一个 Realm 文件中,作为主要 Realm 数据库的扩展,操作如下:
内存数据库在每次程序运行期间都不会保存数据。但是,这不会妨碍到 Realm 的其他功能,包括查询、关系以及线程安全。 假如您需要灵活的数据读写但又不想储存数据的话,那么内存数据库对您来说一定是一个不错的选择。
19,加密数据库
20,数据迁移(Migration)
(1)为何要迁移
比如原来有如下 Person 模型:
21,使用带有 REST API 功能的 Realm 数据库示例
我们将从 豆瓣FM的API 那里获取一组 JSON 格式的频道数据,然后将它以 Realm Objects 的形式储存到默认的 Realm 数据库里。
1,什么是Realm
Realm 于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代 Core Data 和 SQLite。
2,关于Realm,你要知道下面几点:
(1)使用简单,大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,学习成本低。
(2)Realm 不是基于 Core Data,也不是基于 SQLite 封装构建的。它有自己的数据库存储引擎。
(3)Realm 具有良好的跨平台特性,可以在 iOS 和 Android 平台上共同使用。代码可以使用 Swift 、 Objective-C 以及 Java 语言来编写。
(4)Realm 还提供了一个轻量级的数据库查看工具(Realm Browser)。你也可以用它进行一些简单的编辑操作(比如插入和删除操作)
3,支持的类型
(1)Realm 支持以下的属性类型:Bool、Int8、Int16、Int32、Int64、Double、Float、String、Date(精度到秒)以及Data.
(2)也可以使用 List<object> 和 Object 来建立诸如一对多、一对一之类的关系模型,此外 Object 的子类也支持此功能。
4,Realm的安装配置
(1)先去 Realm 的官网去下载最新框架:http://static.realm.io/downloads/swift/latest
(2)拖拽 RealmSwift.framework 和 Realm.framework 文件到”Embedded Binaries”选项中。选中 Copy items if needed 并点击 Finish
5,将数据插入到数据库中
下面代码判断默认数据库中是否有数据,如果没有的话将几个自定义对像插入到数据库中。
(1)这里以个人消费记录为例,我们先定义消费类别类,和具体消费记录类import Foundation import RealmSwift //消费类型 class ConsumeType:Object { //类型名 @objc dynamic var name = "" } //消费条目 class ConsumeItem:Object { //条目名 @objc dynamic var name = "" //金额 @objc dynamic var cost = 0.00 //时间 @objc dynamic var date = Date() //所属消费类别 @objc dynamic var type:ConsumeType? }
(2)判断数据库记录是否为空,空的话则插入数据库(这里以默认数据库为例)
import UIKit import RealmSwift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() //使用默认的数据库 let realm = try! Realm() //查询所有的消费记录 let items = realm.objects(ConsumeItem.self) //已经有记录的话就不插入了 if items.count>0 { return } //创建两个消费类型 let type1 = ConsumeType() type1.name = "购物" let type2 = ConsumeType() type2.name = "娱乐" //创建三个消费记录 let item1 = ConsumeItem(value: ["买一台电脑",5999.00,Date(),type1]) //可使用数组创建 let item2 = ConsumeItem() item2.name = "看一场电影" item2.cost = 30.00 item2.date = Date(timeIntervalSinceNow: -36000) item2.type = type2 let item3 = ConsumeItem() item3.name = "买一包泡面" item3.cost = 2.50 item3.date = Date(timeIntervalSinceNow: -72000) item3.type = type1 // 数据持久化操作(类型记录也会自动添加的) try! realm.write { realm.add(item1) realm.add(item2) realm.add(item3) } //打印出数据库地址 print(realm.configuration.fileURL ?? "") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
7,使用Realm Browser查看数据库
(1)默认数据库是应用的 Documents 文件夹下的一个名为“default.realm”。
//打印出数据库地址 print(realm.configuration.fileURL ?? "")
(2)使用 Realm Browser 工具可以很方便的对.realm数据库进行读取和编辑(在 App Store 中搜索 Realm Browser 即可下载)。
可以看到,上面的几个对象已经成功的插入到数据库中来。
8,从数据库中读取记录并显示到表格中来
(1)通过查询操作,Realm 将会返回包含 Object 集合的 Results 实例。Results 的表现和 Array 十分相似,并且包含在 Results 中的对象能够通过索引下标进行访问。 (2)所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。
(3)查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。
下面我们把库里的数据加载出来,并通过表格显示出来。
效果图如下:
代码如下:
9,查询前N条数据
Realm无法直接限制查询数量。所以我们如果想要查出部分数据(比如前5条记录),也是全部查出来后在结果集中捞取。
10,支持断言查询(Predicate),这样可以通过条件查询特定数据
同时可以使用链式查询数据。
import UIKit import RealmSwift class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! var dformatter = DateFormatter() //保存从数据库中查询出来的结果集 var consumeItems:Results<ConsumeItem>? override func viewDidLoad() { super.viewDidLoad() self.dformatter.dateFormat = "MM月dd日 HH:mm" self.tableView!.delegate = self self.tableView!.dataSource = self //创建一个重用的单元格 self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell") //使用默认的数据库 let realm = try! Realm() //查询所有的消费记录 consumeItems = realm.objects(ConsumeItem.self) } //在本例中,只有一个分区 func numberOfSections(in tableView: UITableView) -> Int { return 1; } //返回表格行数(也就是返回控件数) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.consumeItems!.count } //创建各单元显示内容(创建参数indexPath指定的单元) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //同一形式的单元格重复使用,在声明时已注册 let cell = UITableViewCell(style: .value1, reuseIdentifier: "MyCell") let item = self.consumeItems![indexPath.row] cell.textLabel?.text = item.name + " ¥" + String(format: "%.1f", item.cost) cell.detailTextLabel?.text = self.dformatter.string(from: item.date) return cell } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
9,查询前N条数据
Realm无法直接限制查询数量。所以我们如果想要查出部分数据(比如前5条记录),也是全部查出来后在结果集中捞取。
//查询并取出前5条数据 let dogs = try! Realm().objects(Dog.self) for i in 0..<5 { let dog = dogs[i] // ... }
Realm为何无法限制查询数量?
通常查询数据库数据时,我们可以在sql语句中添加一些限制语句(比如rownum,limit,top等)来限制返回的结果集的行数。
但我们使用Realm会发现,它没有这种分页功能,感觉不管查什么都是把所有的结果都捞出来。比如我们只要User表的前10条数据,那么做法是先查询出所有的User数据,再从结果集中取出前10条数据。
有人可能会担心,如果数据库中数据非常多,那每次都这么查不会影响性能吗?
其实大可放心,由于Realm都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。不像通常数据库,查询后,查询结果是从数据库拷贝一份出来放在内存中的。而Realm的查询结果应该说是数据库数据的引用,就算你查出来,如果不用也不会占用什么内存。
通常查询数据库数据时,我们可以在sql语句中添加一些限制语句(比如rownum,limit,top等)来限制返回的结果集的行数。
但我们使用Realm会发现,它没有这种分页功能,感觉不管查什么都是把所有的结果都捞出来。比如我们只要User表的前10条数据,那么做法是先查询出所有的User数据,再从结果集中取出前10条数据。
有人可能会担心,如果数据库中数据非常多,那每次都这么查不会影响性能吗?
其实大可放心,由于Realm都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。不像通常数据库,查询后,查询结果是从数据库拷贝一份出来放在内存中的。而Realm的查询结果应该说是数据库数据的引用,就算你查出来,如果不用也不会占用什么内存。
10,支持断言查询(Predicate),这样可以通过条件查询特定数据
同时可以使用链式查询数据。
//查询花费超过10元的消费记录(使用断言字符串查询) consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10") //查询花费超过10元的购物记录(使用 NSPredicate 查询) let predicate = NSPredicate(format: "type.name = '购物' AND cost > 10") consumeItems = realm.objects(ConsumeItem.self).filter(predicate) //使用链式查询 consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").filter("type.name = '购物'")
支持的断言:
- 比较操作数(comparison operand)可以是属性名称或者某个常量,但至少有一个操作数必须是属性名称;
- 比较操作符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 属性类型的比较,比如说 age == 45;
- 相等比较 ==以及!=,比如说Results<Employee>().filter("company == %@", company)
- 比较操作符 == and != 支持布尔属性;
- 对于 NSString 和 NSData 属性来说,我们支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操作符,比如说 name CONTAINS ‘Ja’;
- 字符串支持忽略大小写的比较方式,比如说 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小写将被忽略;
- Realm 支持以下复合操作符:“AND”、“OR” 以及 “NOT”。比如说 name BEGINSWITH ‘J’ AND age >= 32;
- 包含操作符 IN,比如说 name IN {‘Lisa’, ‘Spike’, ‘Hachi’};
- ==、!=支持与 nil 比较,比如说 Results<Company>().filter("ceo == nil")。注意到这只适用于有关系的对象,这里 ceo 是 Company 模型的一个属性。
- ANY 比较,比如说 ANY student.age < 21
- 注意,虽然我们不支持复合表达式类型(aggregate expression type),但是我们支持对对象的值使用 BETWEEN 操作符类型。比如说,Results<Person>.filter("age BETWEEN %@", [42, 43]])。
//查询花费超过10元的消费记录,并按升序排列 consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").sorted(byKeyPath: "cost")
12,使用List实现一对多关系
List 中可以包含简单类型的 Object,表面上和可变的 Array 非常类似。
注意:List 只能够包含 Object 类型,不能包含诸如String之类的基础类型。
如果打算给我们的 Person 数据模型添加一个“dogs”属性,以便能够和多个“dogs”建立关系,也就是表明一个 Person 可以有多个 Dog,那么我们可以声明一个List
class Person: Object { ... // 其余的属性声明 let dogs = List<Dog>() } // 这里我们就可以使用已存在的狗狗对象来完成初始化 let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]]) // 还可以使用多重嵌套 let aPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])可以和之前一样,对 List 属性进行访问和赋值:
let someDogs = realm.objects(Dog.self).filter("name contains '小白'") ZhangSan.dogs.append(objectsIn: someDogs) ZhangSan.dogs.append(dahuang)
通过反向关系(也被称为反向链接(backlink)),您可以通过一个特定的属性获取和给定对象有关系的所有对象。 Realm 提供了“链接对象 (linking objects)” 属性来表示这些反向关系。借助链接对象属性,您可以通过指定的属性来获取所有链接到指定对象的对象。
例如,一个 Dog 对象可以拥有一个名为 owners 的链接对象属性,这个属性中包含了某些 Person 对象,而这些 Person 对象在其 dogs 属性中包含了这一个确定的 Dog 对象。您可以将 owners 属性设置为 LinkingObjects 类型,然后指定其关系,说明其当中包含了 Person 对象。
class Dog: Object { @objc dynamic var name = "" @objc dynamic var age = 0 // Realm 并不会存储这个属性,因为这个属性只定义了 getter // 定义“owners”,和 Person.dogs 建立反向关系 let owners = LinkingObjects(fromType: Person.self, property: "dogs") }
13,添加主键(Primary Keys)
重写 Object.primaryKey() 可以设置模型的主键。
声明主键之后,对象将被允许查询,更新速度更加高效,并且要求每个对象保持唯一性。
一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。
class Person: Object { @objc dynamic var id = 0 @objc dynamic var name = "" override static func primaryKey() -> String? { return "id" } }
14,添加索引属性(Indexed Properties)
重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引:
class Book: Object { @objc dynamic var price = 0 @objc dynamic var title = "" override static func indexedProperties() -> [String] { return ["title"] } }
15,设置忽略属性(Ignored Properties)
重写 Object.ignoredProperties() 可以防止 Realm 存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操作,它们将由成员变量(var)提供支持,并且您能够轻易重写它们的 setter 和 getter。
class Person: Object { @objc dynamic var tmpID = 0 var name: String { // 计算属性将被自动忽略 return "\(firstName) \(lastName)" } @objc dynamic var firstName = "" @objc dynamic var lastName = "" override static func ignoredProperties() -> [String] { return ["tmpID"] } }
16,修改更新数据
(1)直接更新内容
如果您的数据模型中设置了主键的话,那么您可以使用 Realm().add(_:update:) 来更新对象(当对象不存在时也会自动插入新的对象。)
// 在一个事务中更新对象 try! realm.write { consumeItem.name = "去北京旅行" }(2)通过主键更新
如果您的数据模型中设置了主键的话,那么您可以使用 Realm().add(_:update:) 来更新对象(当对象不存在时也会自动插入新的对象。)
/****** 方式1 ***/ // 创建一个带有主键的“书籍”对象,作为事先存储的书籍 let cheeseBook = Book() cheeseBook.title = "奶酪食谱" cheeseBook.price = 9000 cheeseBook.id = 1 // 通过 id = 1 更新该书籍 try! realm.write { realm.add(cheeseBook, update: true) } /****** 方式2 ***/ // 假设带有主键值 `1` 的“书籍”对象已经存在 try! realm.write { realm.create(Book.self, value: ["id": 1, "price": 22], update: true) // 这本书的`title`属性不会被改变 }(3)键值编码
这个是在运行时才能决定哪个属性需要更新的时候,这个对于大量更新的对象极为有用。
let persons = realm.objects(Person.self) try! realm.write { // 更新第一个 persons.first?.setValue(true, forKeyPath: "isFirst") // 将每个人的 planet 属性设置为“地球” persons.setValue("地球", forKeyPath: "planet") }
17,删除数据
let cheeseBook = ... // 存储在 Realm 中的 Book 对象 // 在事务中删除一个对象 try! realm.write { realm.delete(cheeseBook) }也能够删除数据库中的所有数据
// 从 Realm 中删除所有数据 try! realm.write { realm.deleteAll() }
18,Realm数据库配置
(1)修改默认的的数据库
通过调用 Realm() 来初始化以及访问我们的 realm 变量。其指向的是应用的 Documents 文件夹下的一个名为“default.realm”的文件。
通过对默认配置进行更改,我们可以使用不同的数据库。比如给每个用户帐号创建一个特有的 Realm 文件,通过切换配置,就可以直接使用默认的 Realm 数据库来直接访问各自数据库:
func setDefaultRealmForUser(username: String) { var config = Realm.Configuration() // 使用默认的目录,但是使用用户名来替换默认的文件名 config.fileURL = config.fileURL!.deletingLastPathComponent() .appendingPathComponent("\(username).realm") // 将这个配置应用到默认的 Realm 数据库当中 Realm.Configuration.defaultConfiguration = config }(2)打包进项目里的数据库的使用
如果需要将应用的某些数据(比如配置信息,初始化信息等)打包到一个 Realm 文件中,作为主要 Realm 数据库的扩展,操作如下:
let config = Realm.Configuration( // 获取需要打包文件的 URL 路径 fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"), // 以只读模式打开文件,因为应用数据包并不可写 readOnly: true) // 通过配置打开 Realm 数据库 let realm = try! Realm(configuration: config) // 通过配置打开 Realm 数据库 let results = realm.objects(Dog.self).filter("age > 5")(3)内存数据库
内存数据库在每次程序运行期间都不会保存数据。但是,这不会妨碍到 Realm 的其他功能,包括查询、关系以及线程安全。 假如您需要灵活的数据读写但又不想储存数据的话,那么内存数据库对您来说一定是一个不错的选择。
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))
19,加密数据库
(1)加密后的 Realm文件不能跨平台使用(因为 NSFileProtection 只有 iOS 才可以使用)
(2)Realm 文件不能在没有密码保护的 iOS 设备中进行加密。为了避免这些问题(或者您想构建一个 OS X 的应用),可以使用 Realm 提供的加密方法。
(3)加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)。
/***** 在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密 ****/ // 产生随机密钥 var key = Data(count: 64) _ = key.withUnsafeMutableBytes { bytes in SecRandomCopyBytes(kSecRandomDefault, 64, bytes) } // 打开加密文件 let config = Realm.Configuration(encryptionKey: key) let realm:Realm do { realm = try Realm(configuration: config) } catch let error as NSError { // 如果密钥错误,`error` 会提示数据库不可访问 fatalError("Error opening realm: \(error)") } // 和往常一样使用 Realm 即可 let dogs = realm.objects(Book.self).filter("name contains 'Fido'")
20,数据迁移(Migration)
(1)为何要迁移
比如原来有如下 Person 模型:
class Person: Object { @objc dynamic var firstName = "" @objc dynamic var lastName = "" @objc dynamic var age = 0 }假如我们想要更新数据模型,给它添加一个 fullname 属性, 而不是将“姓”和“名”分离开来。
class Person: Object { @objc dynamic var fullName = "" @objc dynamic var age = 0 }在这个时候如果您在数据模型更新之前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm 就会抛出错误。
(2)如何进行数据迁移
假设我们想要把上面所声明 Person 数据模型进行迁移。如下所示是最简单的数据迁移的必需流程:
假设我们想要把上面所声明 Person 数据模型进行迁移。如下所示是最简单的数据迁移的必需流程:
// 在(application:didFinishLaunchingWithOptions:)中进行配置 let config = Realm.Configuration( // 设置新的架构版本。这个版本号必须高于之前所用的版本号 // (如果您之前从未设置过架构版本,那么这个版本号设置为 0) schemaVersion: 1, // 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用 migrationBlock: { migration, oldSchemaVersion in // 目前我们还未进行数据迁移,因此 oldSchemaVersion == 0 if (oldSchemaVersion < 1) { // 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 } }) // 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象 Realm.Configuration.defaultConfiguration = config // 现在我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移 let realm = try! Realm()虽然这个迁移操作是最精简的了,但是我们需要让这个闭包能够自行计算新的属性(这里指的是 fullName),这样才有意义。 在迁移闭包中,我们能够调用Migration().enumerateObjects(_:_:) 来枚举特定类型的每个 Object 对象,然后执行必要的迁移逻辑。注意,对枚举中每个已存在的 Object 实例来说,应该是通过访问 oldObject 对象进行访问,而更新之后的实例应该通过 newObject 进行访问:
// 在 application(application:didFinishLaunchingWithOptions:) 中进行配置 Realm.Configuration.defaultConfiguration = Realm.Configuration( schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in if (oldSchemaVersion < 1) { // enumerateObjects(ofType:_:) 方法遍历了存储在 Realm 文件中的每一个“Person”对象 migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // 将名字进行合并,存放在 fullName 域中 let firstName = oldObject!["firstName"] as! String let lastName = oldObject!["lastName"] as! String newObject!["fullName"] = "\(firstName) \(lastName)" } } })
21,使用带有 REST API 功能的 Realm 数据库示例
我们将从 豆瓣FM的API 那里获取一组 JSON 格式的频道数据,然后将它以 Realm Objects 的形式储存到默认的 Realm 数据库里。
(1)json数据格式如下:
(2)我们将直接把 Dictionary 插入到 Realm 中,然后让 Realm 自行快速地将其映射到 Object 上。
{ "channels": [ { "name_en": "Personal Radio", "seq_id": 0, "abbr_en": "My", "name": "私人兆赫", "channel_id": 0 }, { "name": "华语", "seq_id": 0, "abbr_en": "", "channel_id": "1", "name_en": "" }, { "name": "欧美", "seq_id": 1, "abbr_en": "", "channel_id": "2", "name_en": "" } ] }
(从 iOS9 起,新特性要求App访问网络请求,要采用 HTTPS 协议。直接请求HTTP数据会报错,解决办法可以参照的我另一篇文章:Swift - 网络请求报App Transport Security has blocked a cleartext错)
为了确保示例能够成功,我们需要一个所有属性完全匹配 JSON 键结构的 Object 结构体。如果 JSON 的键结构不匹配 Object 结构体属性结构的话,那么就会在插入时被忽略。
(3)可以看到数据已经成功插入到库中了
22,当前版本的限制
为了确保示例能够成功,我们需要一个所有属性完全匹配 JSON 键结构的 Object 结构体。如果 JSON 的键结构不匹配 Object 结构体属性结构的话,那么就会在插入时被忽略。
import UIKit import RealmSwift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 调用API let url = URL(string: "http://www.douban.com/j/app/radio/channels")! let response = try! Data(contentsOf: url) // 对 JSON 的回应数据进行反序列化操作 let json = try! JSONSerialization.jsonObject(with: response, options: .allowFragments) as! [String:Any] let channels = json["channels"] as! [[String:Any]] let realm = try! Realm() try! realm.write { // 为数组中的每个元素保存一个对象(以及其依赖对象) for channel in channels { if channel["seq_id"] as! Int == 0 {continue} //第一个频道数据有问题,丢弃掉 realm.create(DoubanChannel.self, value: channel, update: true) } } print(realm.configuration.fileURL ?? "") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } //豆瓣频道 class DoubanChannel:Object { //频道id @objc dynamic var channel_id = "" //频道名称 @objc dynamic var name = "" //频道英文名称 @objc dynamic var name_en = "" //排序 @objc dynamic var seq_id = 0 @objc dynamic var abbr_en = "" //设置主键 override static func primaryKey() -> String? { return "channel_id" } }
(3)可以看到数据已经成功插入到库中了
22,当前版本的限制
Realm 致力于平衡数据库读取的灵活性和性能。为了实现这个目标,在 Realm 中所存储的信息的各个方面都有基本的限制。例如:
(1)类名称的长度最大只能存储 57 个 UTF8 字符。
(2)属性名称的长度最大只能支持 63 个 UTF8 字符。
(3)NSData 以及 String 属性不能保存超过 16 MB 大小的数据。如果要存储大量的数据,可通过将其分解为16MB 大小的块,或者直接存储在文件系统中,然后将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属性,系统将在运行时抛出异常。
(4)对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。
全部评论(19)
模型不是继承realm的对象才可以吗?为啥您的是继承nsobject
站长回复:我继承的就是realm的对象啊(Object),不是nsobject
韩哥?realm的config配置能讲一下吗?如果app切换账号是不是就必须得删除已有缓存
站长回复:可以为每个用户帐号创建一个专门的 Realm 文件,根据账号自动切换使用。具体见文章的第18小节。
站长您好,我用的cocoapd集成的realm,集成之后我发现我的项目文件夹大小比原来增大了60M,想请问一下最终项目做完提交到appstore体积会很大吗,就是集成realm后增大的60M会在最终提交的app版本的当中吗?谢谢!
站长回复:这个不用担心,最终编译生成的安装包不会有那么大。
let realm = try! Realm()
try! realm.write {
// 为数组中的每个元素保存一个对象(以及其依赖对象)
for channel in channels {
if channel["seq_id"] as! Int == 0 {continue} //第一个频道数据有问题,丢弃三
realm.create(DoubanChannel.self, value: channel, update: true)
}
}
站长大人,用这个的时候出现一个错误
站长回复:我测试了下是没问题的啊,你那边报的是什么错误。
请问怎么使用Realm存储图片呢?有在StackOverflow上看到使用NSData,但并不是很理解也不知具体该如何使用。请问可以详细讲解一下吗?非常感谢!
站长回复:我下周会写篇相关文章,你可以关注下。
//查询并取出前5条数据
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
let dog = dogs[i]
// ...
}
我使用这种方法,会报
Type 'Results<MainPageModel>?' has no subscript members
错误
站长回复:还没碰到过这种问题,暂时帮不了你了。
Limiting Results 建议HANNGE多加上这个
// Loop through the first 5 Dog objects
// restricting the number of objects read from disk
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
let dog = dogs[i]
// ...
}
站长回复:这个建议不错,文章代码已更新。
realm没有像数据库那样可以限制查询结果数量,你如果只需要前2000条数据,就先全部查出来后再从结果集中取前2000条。
可以从2000条数据中,取得 top 50吗?在这篇文章内没有到查旬语句,请问版主,怎么取得TOP 50呢?谢谢!
站长回复:Realm没有类似于rownum,limit,top等语句来限制返回的结果集的行数。你如果只要前2000条,也是把所有记录查出来,然后从结果集中取出前2000条数据。
不过你不用当心性能或者资源占用问题,由于Realm都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。不像通常数据库,查询后,查询结果是从数据库拷贝一份出来放在内存中的。而Realm的查询结果应该说是数据库数据的引用,就算你查出来,如果不用也不会占用什么内存。
感谢!跟官方介绍差不多!
站长回复:不客气。很高兴对你有用。
这个Realm 能像sqlite3 保证插入数据的唯一性么,主键是不是就可以做到了?
站长回复:当然可以,设置了主键自然保证唯一性。
站长站长,查询到的数据 我想要通过id去判断本地是否存在 存在就不存 不存在就存。怎么做啊?
站长回复:你可以把id设置为主键,添加的时候realm会自动处理的。
你好,请问realm可以查询缓存数据大小的吗
站长回复:不太明白你说的缓存数据是指什么?
print(realm.path) 这个语句有问题 提示realm没有path这个属性/方法
站长回复:realm最新版本(1.0.2)的接口有调整,不用path属性获取路径了。你可以改用realm.configuration.fileURL来获取。文章代码已更新。
自从看了这个网站,基本上就告别Apple的doc了。哈哈哈,站长辛苦~加油
站长回复:过奖过奖。首先谢谢你的支持和鼓励。欢迎常来看看,我会持续更新,创作更多更好的文章分享给大家。
加入我的relam里面有三条数据分别为1,2,3, 当我把第一条删掉之后,relam的顺序就变成了3,2.为什么啊,relam的底层机制?
站长回复:由于性能的原因,Realm无法保证插入的数据的顺序保持一致。你如果要让每次查询的结果顺序都保持一致性,可以在查询的时候指定属性进行排序操作。
最低支持版本是8.0吗 7.0怎么搞。。。。
站长回复:8.0是指什么?Realm最新版本是1.0,Xcode我用的是最新的7.3
为啥我导入框架的时候就报错啊
站长回复:试了下没问题, 我也不知道你那边怎么会报错。
我查询的结果怎限制结果数量 列如查询结果返回结果数组做大长度2000个
站长回复:realm没有像数据库那样可以限制查询结果数量,你如果只需要前2000条数据,就先全部查出来后再从结果集中取前2000条。
写的真好。
站长回复:谢谢鼓励,欢迎常来看看。