Realm for Swift

Realm Studio

使用 Realm Studio,让开发更有效率。
您可以轻松地打开任何 Realm 数据库文件或者 Realm 对象服务器部署对象,并对其进行分析。Realm Studio 允许您轻松地打开并编辑本地 Realm 数据库和可同步 Realm 数据库,此外还可以管理 Realm 平台。可运行在 Mac、Windows 以及 Linux 平台。

下载链接

Realm 开发工具

项目中添加RealmSwift

Carthage

  1. Cartfile添加github "realm/realm-cocoa"
  2. 终端中cd到工程文件;
  3. 运行 carthage update --platform iOS
  4. Carthage/Build/iOS/中将Realm.frameworkRealmSwift.framework添加到Xcode 工程的 “General” 设置选项卡的 “Linked Frameworks and Libraries” 部分内;
  5. 路径配置如下图。Carthage传送门
配置Carthage路径

CocoaPods

  1. 执行 pod repo update,从而让 CocoaPods 更新至目前最新可用的 Realm 版本;
  2. 在您的 Podfile 中,将 use_frameworks! 和 pod 'RealmSwift' 添加到主应用目标和测试目标中;
  3. 在命令行中执行 pod install;
  4. 使用由 CocoaPods 生成的 .xcworkspace 文件来编写工程。

动态库

下载地址

前往 Xcode 工程的 “General” 设置选项卡中,在 ios/、osx/、tvos/ 或者 watchos/ 目录中选择适合您项目的 Swift 版本目录,将 Realm.framework 和 RealmSwift.framework 拖曳到 “Embedded Binaries” 部分内。请确保勾选了 Copy items if needed(除非项目中有多个平台都需要使用 Realm ),然后单击 Finish 按钮;
在单元测试目标的 “Build Settings” 中,将 RealmSwift.framework 的父目录添加到 “Framework Search Paths” 部分中;
如果在 iOS、watchOS 或者 tvOS 工程中使用 Realm,请在应用目标的 “Build Phases” 中创建一条新的 “Run Script Phase”,然后将下面这段代码粘贴到脚本文本框内:

Copy to clipboardbash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"
因为要绕过App Store 出现的提交 bug,因此这一步在打包通用二进制文件时是必须的。

使用RealmSwift

数据模型

1.创建数据模型

在使用Realm中存储的数据模型都要是Object类的子类。

import Foundation
import RealmSwift

class Dog: Object {
   @objc dynamic var id: Int = 0
   @objc dynamic var name: String?
    
}

class Cat: Object {
    @objc dynamic var name: String?
}

@objc是为了兼容Swift4

2.支持的属性类型

Realm支持的类型:Bool、Int、Int8、Int16、Int32、Int64、Double、Float、String、Date 以及 Data。
其中String、Date 以及 Data 属性都是可空的,Object 属性必须可空。

在上面Dog中你会发现数值类型是不能写成Int?类型的,在Realm中可选数值类型要如下设置:

  // 可选 int 属性,默认为 nil
  // RealmOption 属性应该始终用 `let` 进行声明,
  // 因为直接对其进行赋值并不会起任何作用
  let age = RealmOptional<Int>()
    
let realm = try! Realm()
try! realm.write() {
    var person = realm.create(Person.self, value: ["Jane", 27])
    // // 读取或者修改 `RealmOptional` 可以通过 `value` 属性实现
    person.age.value = 28
}

RealmOptional 支持 Int、Float、Double、Bool,以及所有大小的 Int 版本(包括 Int8、Int16、Int32、Int64)。

3.设置主键

override static func primaryKey() -> String? {
    return "id"
}

实现这个方法就能直接设置主键

4.索引属性

override static func indexedProperties() -> [String] {
   return ["title"]
}    

实现indexedProperties进行设置索引属性。

Realm 支持为字符串、整型、布尔值以及 Date 属性建立索引。

5.属性备忘单

类型 非可空值形式 可空值形式
Bool @objc dynamic var value = false let value = RealmOptional<Bool>()
Int @objc dynamic var value = 0 let value = RealmOptional<Int>()
Float @objc dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double @objc dynamic var value: Double = 0. let value = RealmOptional<Double>()
String @objc dynamic var value = "" @objc dynamic var value: String? = nil
Data @objc dynamic var value = Data() @objc dynamic var value: Data? = nil
Date @objc dynamic var value = Date() @objc dynamic var value: Date? = ni
Object 不存在:必须是可空值 @objc dynamic var value: Class?
List let value = List<Type>() 不存在:必须是非可空值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 不存在:必须是非可空值

创建Realm

//MARK: - 创建数据库
    /// 创建数据库
    ///
    /// - Parameters:
    ///   - dataBaseName: 数据库名字
    ///   - isReadOnly: 是否是只读
    /// - Returns: Realm实例
    @discardableResult
    public func creatDB(_ dataBaseName: String, isReadOnly: Bool = false) -> Realm? {
        let realm = openDB(dataBaseName, isReadOnly: isReadOnly)
        return realm
    }
    
    /// 打开数据库
    ///
    /// - Parameter name: 数据库名字
    /// - isReadOnly: 是否是只读
    /// - Returns: Realm实例
    @discardableResult
    private func openDB(_ dataBaseName: String, isReadOnly: Bool = false) -> Realm? {
        guard let dbPath = getCreatDatabasePath(dataBaseName) else {
            return nil
        }
        var config = Realm.Configuration()
        config.fileURL = dbPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        do {
            let realm = try Realm.init(configuration: config)
            return realm
        }catch let error {
            mPrint("打开或者创建数据库失败:\n\(error.localizedDescription)")
            return nil
        }
    }

在本地生成realm.realm文件时还会有:

  • realm.lock - 资源锁定文件;
  • realm.management - 存放进程锁文件的目录;
  • realm.note - 用于通知的命名管道。
    这些文件只是Realm维护的文件删除或者怎么着都不会出现什么问题。

在报告 Realm 问题的时候,请将这些辅助文件 (auxiliary Realm) 连同主要的 .realm 文件一同提交,因为它们很可能会包含某些对调试问题有用的信息。

打开创建的文件利用RealmStudioda打开会发现,在工程中所有继承Object的类直接在Realm中创建了表(暂时未找到具体的原因)。

打开工程中的Realm文件

    /// 打开预植的数据库
    ///
    /// - Parameters:
    ///   - dataBaseName: 数据库名字
    ///   - isReadOnly: 是否是只读
    /// - Returns: Realm实例
    @discardableResult
    public func openReferenceDB(_ dataBaseName: String, isReadOnly: Bool = true) -> Realm? {
        guard let dbPath = getReferenceDatabasePaeh(dataBaseName) else {
            return nil
        }
        var config = Realm.Configuration()
        config.fileURL = dbPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        do {
            let realm = try Realm.init(configuration: config)
            return realm
        }catch let error {
            mPrint("打开或者创建数据库失败:\n\(error.localizedDescription)")
            return nil
        }
    }

设置Realm的defaultConfiguration

    /// 设置通过Realm()获取数据库的配置
    ///
    /// - Parameters:
    ///   - realmName: 数据库的名字
    ///   - isReadOnly: 是否是这是只读
    public func setDefaltRealmConfiguration(_ realmName: String,isReference: Bool = false, isReadOnly: Bool = false) -> Bool{
        var realmPath: URL?
        if isReference {
            realmPath = getReferenceDatabasePaeh(realmName)
        }else {
            realmPath = getCreatDatabasePath(realmName)
        }
        if realmPath == nil {
            return false
        }
        var config = Realm.Configuration()
        config.fileURL = realmPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        return true
    }

获取当前默认的数据库

    /// 获取当前默认的数据
    ///
    /// - Returns: 返回默认的Realm的数据库实例
    @discardableResult
    public func getDefaultRealm() -> Realm? {
        do {
            return try Realm()
        }catch let error {
            mPrint("获取默认的Realm的数据库失败:\n\(error.localizedDescription)")
            return nil
        }
    }

如果没有配置defaultConfiguration则会获取默认的数据库。

/Library/Developer/CoreSimulator/Devices
/26B4D5CC-1EF4-4897-8F02-BCFBE06F7C40/data
/Containers/Data/Application/7CDCBAF4-A7A2-45E4-9B8A-725E873975AD/Documents/default.realm

配置后会获取到设置路径的数据库。

/Library/Developer/CoreSimulator/Devices
/26B4D5CC-1EF4-4897-8F02-BCFBE06F7C40/data
/Containers/Data/Application/E050DEE4-71FB-4866-A10C-CBADA288D35C/Library/Caches/DB/2237DB/2237DB.realm

Realm的实例不用全局数据共享,在配置默认数据库后你无论在什么地方获取的Realm()都是同一个数据库。

    //MARK: - 增
    /// 创建表 || 更新表
    ///
    /// - Parameters:
    ///   - type: 表向对应的对象
    ///   - value: 值
    ///   - update: 是否是更新, 如果是"true", Realm会查找对象并更新它, 否则添加对象
    ///   - result: 最后添加对象是成功, 如果成功将对象返回
    public func creatObject(_ type: RealmSwift.Object.Type, value: Any? = nil, update: Bool = false, result: ((RealmSwift.Object?, Error?) -> Void)? = nil){
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                let object = (value == nil) ? realm?.create(type) : realm?.create(type, value: value!, update: update)
                result?(object, nil)
            }
        } catch let error {
            mPrint("获取默认的Realm的数据库失败:\n\(error.localizedDescription)")
            result?(nil, error)
        }
    }
    
    /// 添加数据 || 根据主键更新数据
    ///
    /// - Parameters:
    ///   - object: 要添加的数据
    ///   - update: 是否更新, 如果是true
    ///   - result: 添加数据的状态
    public func addObject(_ object: RealmSwift.Object, update: Bool = false, result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.add(object, update: update)
                result?(nil)
            }
        } catch let error {
            mPrint("添加数据失败:\n \(error.localizedDescription)")
            result?(error)
        }
    }

//MARK: - 删
    /// 删除数据
    ///
    /// - Parameters:
    ///   - object: 要删除的对象
    ///   - result: 删除的状态
    public func deleteObject(_ object: RealmSwift.Object, result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.delete(object)
                result?(nil)
            }
        } catch let error {
            mPrint("添加数据失败:\n \(error.localizedDescription)")
            result?(error)
        }
    }
    
    /// 删除当前数据库中所有的数据
    ///
    /// - Parameter result: 删除的状态
    public func deleteAllObject(result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.deleteAll()
                result?(nil)
            }
        } catch let error {
            mPrint("添加数据失败:\n \(error.localizedDescription)")
            result?(error)
        }
    }
    /// 删除当前打开的数据库
    ///
    /// - Parameter dataBaseName: 数据库的名字
    /// - Returns: 删除的状态
    @discardableResult
    public func deleteCreatDBFile() -> Bool {
       return  autoreleasepool { () -> Bool in
            let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
            let realmURLs = [
                realmURL,
                realmURL.appendingPathExtension("lock"),
                realmURL.appendingPathExtension("note"),
                realmURL.appendingPathExtension("management")
            ]
            for URL in realmURLs {
                do {
                    try FileManager.default.removeItem(at: URL)
                    return true
                } catch {
                    // 错误处理
                    return false
                }
            }
            return false
        }
    }

//MARK: - 改
    /// 根据主键进行更新
    ///
    /// - Parameters:
    ///   - object: 要更新的对象
    ///   - update: 是否根据主键更新, 如果是"false"则是添加数据
    ///   - result: 更新数据的结果
    public func updateObject(_ object: RealmSwift.Object, update: Bool = true, result: ((Error?) -> Void)? = nil) {
        addObject(object, update: update, result: result)
    }
    
    /// 根据主键进行更新
    ///
    /// - Parameters:
    ///   - type: 要更新的对象类型
    ///   - value: 要更新的值, 例如: ["id": 1, "price": 9000.0]
    ///   - update: 是否根据主键进行更新, 如果为"false"则为创建表
    ///   - result: 更新的结果
    public func updateObject(_ type: RealmSwift.Object.Type, value: Any? = nil, update: Bool = true, result: ((RealmSwift.Object?, Error?) -> Void)? = nil) {
        creatObject(type, value: value, update: update, result: result)
    }
    
    /// 直接更新对象
    ///
    /// - Parameters:
    ///   - property: 要更改的属性
    ///   - value: 更改的值
    /// - Returns: 更改的结果
    @discardableResult
    public func updateObject( property: inout Any, value: Any) -> Bool {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                 property = value
            }
            return true
        } catch let error {
            mPrint("直接更新对象属性错误: \(error.localizedDescription)")
            return false
        }
    }
    
    /// 更改表中所有的字段的值
    ///
    /// - Parameters:
    ///   - type: 表的对象类型
    ///   - key: 要更改的字段名
    ///   - value: 更改的值
    /// - Returns: 返回更改结果
    public func updateObjects(type: RealmSwift.Object.Type, key: String, value: Any) -> Bool {
        let objects = getObjects(type: type)
        do {
            try getDefaultRealm()?.write {
                objects?.setValue(value, forKeyPath: key)
            }
            return true
        } catch let error {
            mPrint("更改一个表中的所有数据错误: \(error.localizedDescription)")
            return false
        }
    }
    
    /// 根据主键进行对某个对象中的数据进行更新
    ///
    /// - Parameters:
    ///   - type: 表类型
    ///   - primaryKey: 主键
    ///   - key: 要更改属性
    ///   - value: 更改的值
    /// - Returns: 更改的状态
    public func updateObject(type: RealmSwift.Object.Type, primaryKey: Any, key: String, value: Any) -> Bool {
        let object = getObjectWithPrimaryKey(type: type, primaryKey: primaryKey)
        do {
            try getDefaultRealm()?.write {
                object?.setValue(value, forKeyPath: key)
            }
            return true
        } catch let error {
            mPrint("更新数据出错: \(error.localizedDescription)")
            return false
        }
    }

//MARK: - 查
    /// 查找一个表中的所有的数据
    ///
    /// - Parameter type: 对象类型
    /// - Returns: 查到的数据
    public func getObjects(type: RealmSwift.Object.Type) -> RealmSwift.Results<RealmSwift.Object>?{
         return getDefaultRealm()?.objects(type)
    }
    /// 根据主键查找某个对象
    ///
    /// - Parameters:
    ///   - type: 对象类型
    ///   - primaryKey: 主键
    /// - Returns: 查到的数据
    public func getObjectWithPrimaryKey(type: RealmSwift.Object.Type, primaryKey: Any) -> RealmSwift.Object? {
        return getDefaultRealm()?.object(ofType: type, forPrimaryKey: primaryKey)
    }
    
    /// 使用断言字符串查询
    ///
    /// - Parameters:
    ///   - type: 对象类型
    ///   - filter: 过滤条件
    /// - Returns: 查询到的数据
    /// - example:
    ///   - var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")
    public func getObject(type: RealmSwift.Object.Type, filter: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObjects(type: type)?.filter(filter)
    }
    
    /// 使用谓词进行查询
    ///
    /// - Parameters:
    ///   - type: 对象类型
    ///   - predicate: 谓词对象
    /// - Returns: 查询到的数据
    /// - example:
    ///   - let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
    ///   - tanDogs = realm.objects(Dog.self).filter(predicate)
    public func getObject(type: RealmSwift.Object.Type, predicate: NSPredicate) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObjects(type: type)?.filter(predicate)
    }

    /// 对查询的数据进行排序,请注意, 不支持 将多个属性用作排序基准,此外也无法链式排序(只有最后一个 sorted 调用会被使用)。 如果要对多个属性进行排序,请使用 sorted(by:) 方法,然后向其中输入多个 SortDescriptor 对象。
    ///
    /// - Parameters:
    ///   - type: 对象类型
    ///   - filter: 过滤条件
    ///   - sortedKey: 需要排序的字段
    /// - Returns: 最后的结果
    public func getObject(type: RealmSwift.Object.Type, filter: String, sortedKey: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObject(type: type, filter: filter)?.sorted(byKeyPath: sortedKey)
    }
    
    /// 对查询的数据进行排序, 请注意, 不支持 将多个属性用作排序基准,此外也无法链式排序(只有最后一个 sorted 调用会被使用)。 如果要对多个属性进行排序,请使用 sorted(by:) 方法,然后向其中输入多个 SortDescriptor 对象。
    ///
    /// - Parameters:
    ///   - type: 队形类型
    ///   - predicate: 谓词对象
    ///   - sortedKey: 排序的字段
    /// - Returns: 排序后的数据
    public func getObject(type: RealmSwift.Object.Type, predicate: NSPredicate, sortedKey: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObject(type: type, predicate: predicate)?.sorted(byKeyPath: sortedKey)
    }

集合

Realm 拥有许多能够表示一组对象的类型,称之为 “Realm 集合”:

  • Results 类,表示queries所返回的对象集合。
  • List 类,表示模型之间的对多关系。
  • LinkingObjects 类,表示模型之间的双向关系。
  • RealmCollection 协议,定义了所有 Realm 集合的常用接口。
  • AnyRealmCollection 类,这是一个无类型的类,可以将调用转发给具体的 Realm 集合,例如 Results、List 或者 LinkingObjects。

Realm 集合类型均实现了 RealmCollection 协议,这确保 它们的行为均保持一致。这个协议继承自 CollectionType,因此它的使用方式 与标准库内的集合相同。这个协议也同样声明了其他常用的 Realm 集合 API, 比如说检索、排序、聚合操作等等。List 还存在一些额外的修改操作, 这些操作没有在协议接口中定义,比如说添加或者删除对象。

使用 RealmCollection 协议, 您可以编写能够对任意 Realm 集合进行操作的泛型代码:

Copy to clipboardfunc operateOn<C: RealmCollection>(collection: C) {
    // collection 既可以是 RLMResults,也可以是 RLMArray
    print("operating on collection containing \(collection.count) objects")
}

由于 Swift 类型系统的限制,必须使用诸如 AnyRealmCollection 之类的无类型封装器,才能将这个集合存储在属性或者变量中:

Copy to clipboardclass ViewController {
//    let collection: RealmCollection
//                    ^
//                    error: protocol 'RealmCollection' can only be used
//                    as a generic constraint because it has Self or
//                    associated type requirements
//
//    init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
//        self.collection = collection
//    }

    let collection: AnyRealmCollection<MyModel>

    init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
        self.collection = AnyRealmCollection(collection)
    }
}

Realm的基础使用先写到这里,更详细的可以直接看文档(真的很详细)。

在使用RealmSwift增删改查又用RxSwift封装了一层。
项目地址

参考资料

  1. 英文文档
  2. 中文文档

谢谢

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容