Dienstag, 14. Juli 2015

Some utility functions to work with Realm in iOS

I started using Realm today. So far a very good first impression! My initial ~1000 lines of core data shrank to 350 lines, which are also generally easier to understand. It also took me only about 3 hours to port the core data code which I wrote in about 3 days. I hope it continues like this.

Since I was repeating a lot of code, I wrote these helper functions for simple operations, so I can now load and save using mostly 1-liners. I use GCD to execute the read and writes in the background, so the db access doesn't block the UI. I also use a model layer, which is entirely decoupled from the database (*).

Here are the functions:

Update for Swift 2:
func saveObj<T: Object>(obj: T, update: Bool = false, handler: Bool -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        do {
            let realm = try Realm()
            realm.write {
                realm.add(obj, update: update)
            }
            dispatch_async(dispatch_get_main_queue(), {
                handler(true)
            })
        } catch _ {
            print("Error: creating Realm")
            dispatch_async(dispatch_get_main_queue(), {
                handler(false)
            })
        }
    })
}

//usage:
saveObj(myRealmObj, update: true, handler: myHandler)


func saveObjs<T: Object>(objs: [T], update: Bool = false, handler: Bool -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        do {
            let realm = try Realm()
            realm.write {
                for obj in objs {
                    realm.add(obj, update: update)
                }
            }
            dispatch_async(dispatch_get_main_queue(), {
                handler(true)
            })
        } catch _ {
            print("Error: creating Realm")
            dispatch_async(dispatch_get_main_queue(), {
                handler(false)
            })
        }
    })
}

//usage:
saveObjs(myRealmObjs, update: true, handler: myHandler)


// mapper is a function that maps a database object of type T to a model object of type U
// filterMaybe filter string as in Realm's docs e.g. "name = 'foo'"
// handler function which gets the model object U, which was generated by mapper, as parameter
func load<T: Object, U>(mapper: T -> U, filter filterMaybe: String? = nil, handler: [U] -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        do {
            let realm = try Realm()
            
            var results = realm.objects(T)
            if let filter = filterMaybe {
                results = results.filter(filter)
            }
            
            let objs: [T] = results.toArray()
            let models = objs.map{mapper($0)}
            
            dispatch_async(dispatch_get_main_queue(), {
                handler(models)
            })
            
        } catch _ {
            print("Error: creating Realm")
            dispatch_async(dispatch_get_main_queue(), {
                handler([])
            })
        }
    })
}

//usage:
let mapper = {realmObj in
    return MyModelObj(name: realmObj.name, color: realmObj.color)
}
load(mapper) {myModelObjs in
    // do something with models
}


// pred string as in Realm's docs e.g. "name = 'foo'"
func remove<T: Object>(pred: String, handler: Bool -> (), objType: T.Type) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        do {
            let realm = try Realm()
            let results: Results<T> = realm.objects(T).filter(pred)
            realm.write {
                realm.delete(results)
            }
            dispatch_async(dispatch_get_main_queue(), {
                handler(true)
            })
        } catch _ {
            print("Error: creating Realm")
            dispatch_async(dispatch_get_main_queue(), {
                handler(true)
            })
        }
    })
}


extension Results {
    //loads the results into an array    
    func toArray() -> [T] {
        return self.map{$0}
    }
}

extension RealmSwift.List {    
    func toArray() -> [T] {
        return self.map{$0}
    }
}




Swift 1.2:
func saveObj<T: Object>(obj: T, update: Bool = false, handler: Bool -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        let realm = Realm()
        realm.write {
            realm.add(obj, update: update)
        }
        dispatch_async(dispatch_get_main_queue(), {
            handler(true) // I don't know exactly yet what to return here, since Realm's operations don't return anything. For now indicates method executed successfully.
        })
    })
}

//usage:
saveObj(myRealmObj, update: true, handler: myHandler)


func saveObjs<T: Object>(objs: [T], update: Bool = false, handler: Bool -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        let realm = Realm()
        realm.write {
            for obj in objs {
                realm.add(obj, update: update)
            }
        }
        dispatch_async(dispatch_get_main_queue(), {
            handler(true)
        })
    })
}

//usage:
saveObjs(myRealmObjs, update: true, handler: myHandler)
    

// mapper is a function that maps a database object of type T to a model object of type U
// filterMaybe filter string as in Realm's docs e.g. "name = 'foo'"
// handler function which gets the model object U, which was generated by mapper, as parameter
func load<T: Object, U>(mapper: T -> U, filter filterMaybe: String? = nil, handler: [U] -> ()) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        var results = Realm().objects(T)
        if let filter = filterMaybe {
            results = results.filter(filter)
        }
            
        let objs: [T] = Realm().objects(T).toArray()
        let models = objs.map{mapper($0)}
            
        dispatch_async(dispatch_get_main_queue(), {
            handler(models)
        })
    })
}

//usage:
let mapper = {realmObj in
    return MyModelObj(name: realmObj.name, color: realmObj.color)
}
load(mapper) {myModelObjs in
    // do something with models
}    
    
 
// pred string as in Realm's docs e.g. "name = 'foo'"
func remove<T: Object>(pred: String, handler: Bool -> (), objType: T.Type) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        let realm = Realm()
        let results: Results<T> = realm.objects(T).filter(pred)
        realm.write {
            realm.delete(results)
        }

        dispatch_async(dispatch_get_main_queue(), {
            handler(true)
        })
    })
}

//usage:
remove("name = 'foo'", handler: myHandler, objType: MyRealmObject.self)


extension Results {
    //loads the results into an array    
    func toArray() -> [T] {
        return map(self){$0}
    }
}



I hope this is useful to somebody, and if there's something to improve please add a comment!

(*) This has pros and cons, (roughly), pros: decoupling from db, cleaner models, fixes issue with Realm of not being able to read in the background and use the result objects in the main thread. Cons: performance, since the query result has to be loaded into memory and mapped, but for regular, limited/segmented datasets and considering that loading and parsing is done in the background this is not an issue.

Kommentare:

  1. Realm.objects does not seem to be valid anymore, can you help

    AntwortenLöschen
  2. I updated the post for Swift 2, thanks for your notice!

    AntwortenLöschen