Swift开发Swift进阶指南Realm

Realm 杂谈 Swift 版

2016-06-05  本文已影响959人  Robert_Zhang

Realm 在五月终于发布了1.0正式版。不知道是不是见证了一个历史性的时刻,毕竟 Realm 是号称用来取代 SQLite 而诞生的。话不多说,下面就来揭开她的面纱,说一说我是怎么和她愉快的玩耍。

故事的背景要说到,最近开始的一个新项目。项目选定使用 Swift 开发,所以需要在前期的时候制定相关的技术方案。数据存储是老生常谈的模块,IOS 开发无非 SQLIte 和 Core Data 可选。Realm 在之前就有听说过,但一直没有静下心来研究。这次经过一番研究后,觉得甚是妙哉。再考虑到她对 Android 平台也有不俗的表现,以后业务逻辑方便迁移。故最终选定 Realm。

先附上 Realm Swift 中文文档地址:https://realm.io/cn/docs/swift/latest/ 内容相当详细。当然这都是基础用法,更高级的用法还是参考官方例子吧。以下是我封装的 RealmHelper 类:

class RealmHelper {

    /// realm 数据库的名称
 private let username = "MY-DB"
 
    static let sharedInstance = try! Realm()
    
//--MARK: 初始化 Realm
    /// 初始化进过加密的 Realm, 加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)
    static func initEncryptionRealm() {
        // 说明: 以下内容是可以合并操作的,但为了能最大限度的展示各个操作内容,故分开设置 Realm
        
        // 产生随机密钥
        let key = NSMutableData(length: 64)!
        SecRandomCopyBytes(kSecRandomDefault, key.length,
                           UnsafeMutablePointer<UInt8>(key.mutableBytes))
        
        // 获取加密 Realm 文件的配置文件
        var config = Realm.Configuration(encryptionKey: key)
        
        // 使用默认的目录,但是使用用户名来替换默认的文件名
        config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
            .URLByAppendingPathComponent("\(username).realm")
        
        // 获取我们的 Realm 文件的父级目录
        let folderPath = config.fileURL!.URLByDeletingLastPathComponent!.path!
        
        /**
         *  设置可以在后台应用刷新中使用 Realm
         *  注意:以下的操作其实是关闭了 Realm 文件的 NSFileProtection 属性加密功能,将文件保护属性降级为一个不太严格的、允许即使在设备锁定时都可以访问文件的属性
         */
        // 解除这个目录的保护
        try! NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone],
                                                          ofItemAtPath: folderPath)
        
        // 将这个配置应用到默认的 Realm 数据库当中
        Realm.Configuration.defaultConfiguration = config
        
    }
    
    /// 初始化默认的 Realm
    static func initRealm() {
        var config = Realm.Configuration()
        
        // 使用默认的目录,但是使用用户名来替换默认的文件名
        config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
            .URLByAppendingPathComponent("\(username).realm")
        
        // 获取我们的 Realm 文件的父级目录
        let folderPath = config.fileURL!.URLByDeletingLastPathComponent!.path!
        
        // 解除这个目录的保护
        try! NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone],
                                                          ofItemAtPath: folderPath)
        
        // 将这个配置应用到默认的 Realm 数据库当中
        Realm.Configuration.defaultConfiguration = config
    }

//--- MARK: 操作 Realm
    /// 做写入操作
    static func doWriteHandler(clouse: ()->()) { // 这里用到了 Trailing 闭包
        try! sharedInstance.write {
            clouse()
        }
    }
    
    /// 添加一条数据
    static func addCanUpdate<T: Object>(object: T) {
        try! sharedInstance.write {
            sharedInstance.add(object, update: true)
        }
    }
    static func add<T: Object>(object: T) {
        try! sharedInstance.write {
            sharedInstance.add(object)
        }
    }
    /// 后台单独进程写入一组数据
    static func addListDataAsync<T: Object>(objects: [T]) {
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        // Import many items in a background thread
        dispatch_async(queue) {
            // 为什么添加下面的关键字,参见 Realm 文件删除的的注释
            autoreleasepool {
                // 在这个线程中获取 Realm 和表实例
                let realm = try! Realm()
                // 批量写入操作
                realm.beginWrite()
                // add 方法支持 update ,item 的对象必须有主键
                for item in objects {
                    realm.add(item, update: true)
                }
                // 提交写入事务以确保数据在其他线程可用
                try! realm.commitWrite()
            }
        }
    }
    
    static func addListData<T: Object>(objects: [T]) {
        autoreleasepool {
            // 在这个线程中获取 Realm 和表实例
            let realm = try! Realm()
            // 批量写入操作
            realm.beginWrite()
            // add 方法支持 update ,item 的对象必须有主键
            for item in objects {
                realm.add(item, update: true)
            }
            // 提交写入事务以确保数据在其他线程可用
            try! realm.commitWrite()
        }
    }
    
    /// 删除某个数据
    static func delete<T: Object>(object: T) {
        try! sharedInstance.write {
            sharedInstance.delete(object)
        }
    }
    
    /// 批量删除数据
    static func delete<T: Object>(objects: [T]) {
        try! sharedInstance.write {
            sharedInstance.delete(objects)
        }
    }
    /// 批量删除数据
    static func delete<T: Object>(objects: List<T>) {
        try! sharedInstance.write {
            sharedInstance.delete(objects)
        }
    }
    /// 批量删除数据
    static func delete<T: Object>(objects: Results<T>) {
        try! sharedInstance.write {
            sharedInstance.delete(objects)
        }
    }
    
    /// 批量删除数据
    static func delete<T: Object>(objects: LinkingObjects<T>) {
        try! sharedInstance.write {
            sharedInstance.delete(objects)
        }
    }
    
    
    /// 删除所有数据。注意,Realm 文件的大小不会被改变,因为它会保留空间以供日后快速存储数据
    static func deleteAll() {
        try! sharedInstance.write {
            sharedInstance.deleteAll()
        }
    }
    
    /// 根据条件查询数据
    static func selectByNSPredicate<T: Object>(_: T.Type , predicate: NSPredicate) -> Results<T>{
        return sharedInstance.objects(T).filter(predicate)
    }
    
//--- MARK: 删除 Realm
    /*
     参考官方文档,所有 fileURL 指向想要删除的 Realm 文件的 Realm 实例,都必须要在删除操作执行前被释放掉。
     故在操作 Realm实例的时候需要加上 autoleasepool 。如下:
        autoreleasepool {
            //所有 Realm 的使用操作
        }
     */
    /// Realm 文件删除操作
    static func deleteRealmFile() {
        let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
        let realmURLs = [
            realmURL,
            realmURL.URLByAppendingPathExtension("lock"),
            realmURL.URLByAppendingPathExtension("log_a"),
            realmURL.URLByAppendingPathExtension("log_b"),
            realmURL.URLByAppendingPathExtension("note")
        ]
        let manager = NSFileManager.defaultManager()
        for URL in realmURLs {
            do {
                try manager.removeItemAtURL(URL)
            } catch {
                // 处理错误
            }
        }
        
    }
}

注释写的很清楚,主要包括初始化 Realm 和其相关的操作。 所以就不赘述封装类,而是讲解一些 Realm 使用中我觉得有意思的地方。

有趣的 Realm

首先来一个例子:

class Dog: Object {
 let friends = List<Dog>
 let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}
class Person: Object {
 let dogs = List<Dog>()
 override static func primaryKey() -> String? {
    return "id"
  }
}

总结

Realm 是一个轻量级的关系型数据库。她和 SQLite 比起来还要轻。SQLite 需要通过 SQL 语句来操作数据库,虽然也可以通过一些第三方控件封装,使得上层不再关心 SQL 语句的编写。但对比起 Realm 天生已经将这繁琐的工作帮我们封装过,只给我提供较为直观的关系树(关系树是我根据打印出来的数据,自己理解的出来的一个词),让使用方法更符合上层逻辑的编写习惯。我相信如果用的得当,她会大大减轻我们对数据库开发的成本。

对比

Swift 版本的 Realm 更加智能化一些。我们在构建完对象之间的关系后,只需要将顶层的对象更新到 Realm 中,其子内容就会自动保存。就像上面的例子,Person 和 Dog 实例的关系构建完后,只需要保存 Person 实例就能完成所有的内容的持久化。然而,在 Android 中我们则需要分别持久化这些实例,既建立完关系后,需要分别取保存这些实例。(2017-1-20 新增)
这里需要更正一下,Android版本中,其实也只需要保存构建过关系树的顶层对象即可将所有数据持久化到Realm。例如,Person 和 Dog 的对象关系树构建成功后,只需要持久化 Person 对象即可。(2017-2-6 修改)

说明

本着分享加记录学习心得的严谨态度,最后还是要声明一下。以上内容皆为阅读官方文档和项目开发中对 Realm 的理解和感悟,不一定是正确的。如果有误导的地方,请联系我。我会在第一时间修正。当然,它也会帮助你从不同的角度理解 Realm,分享的乐趣就是让更多的人看到你的态度。

上一篇 下一篇

猜你喜欢

热点阅读