Realm 1.0 How can I use thread - swift

I would like to use the realm in my project, but I have a very complex filter and sort. I have to order the list by name,but the name is in other class.
class CustomObject: Object
{
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
}
class ObjectLang: Object
{
dynamic var objectId = 0
dynamic var name = ""
}
When I have more than 130 rows, it is very slow in main thread and it blocks the UI. I tried do it in a background thread, but when I want to update the UI, it was crashed by Realm. So what is the perfect solution? How could I use it? Could you give me an example or tutorial? I have read the guide line.

If program is crashed when updating UI on background thread, you should update UI on main thread when realm task finished.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
let realm = try! Realm()
//do What you need
dispatch_async(dispatch_get_main_queue(), {
//updateUI()
})
})

You can't use Realm accessors across threads. You will need to retrieve the objects on the thread on which you want to use them. To make that happen, I'd recommend for each of your object classes which need to be passed between threads to designate a property as primary key. This property might be objectId in your case.
class CustomObject: Object {
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
override class func primaryKey() -> String {
return "objectId"
}
}
This primary key can then be used to identify your objects and pass them over to the main thread to retrieve them there again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) {
let realm = try! Realm()
var objects = realm.objects(CustomObject)
// objects = objects.filter(…)
let sortedObjects: [CustomObject] = objects.sort { /* … */ }
let ids = sortedObjects.map { $0.objectId }
dispatch_async(dispatch_get_main_queue()) {
let realm = try! Realm()
let objects = ids.map {
realm.objectForPrimaryKey(CustomObject.self, key: $0)
}
updateUIWithObjects(objects)
}
}

Related

Update whole class in Realm Swift in 1 time wont work

Imagine this code:
class StoredVersions: Object{
#objc dynamic var minimumAppVersion = 0.0
#objc dynamic var sets = 0.0
}
class LoadViewController: UIViewController {
let realm = try! Realm()
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
var newestVersions = StoredVersions()
if let resultsStoredVersion = self.realm.objects(StoredVersions.self).first{
print("found stored versions: \(resultsStoredVersion)")
self.storedVersions = resultsStoredVersion
}else{
try! self.realm.write {
print("no stored versions")
self.realm.add(self.storedVersions)
}
}
db.collection("data").document("version").getDocument(completion: { (data, someError) in
if let versions = data.flatMap({StoredVersions(value: $0.data()) }) {
try! self.realm.write {
self.storedVersions = versions
}
}
})
}
storedVersions is updated but when I restart the application, storedVersions is back to its initial state. I do see the print "found stored versions".
If I write just 1 variable at a time, it works. That looks like this:
try! self.realm.write {
self.storedVersions.sets = versions.sets
}
How can I update a whole class without having to put in variables one at a time?
When you do this:
if let versions = data.flatMap({StoredVersions(value: $0.data()) }) {
try! self.realm.write {
self.storedVersions = versions
}
}
You're creating a new, unmanaged StoredVersions object. You need to call realm.add(_:) to add it to the Realm, otherwise the object only exists in memory.
If you want to update the existing StoredVersions object rather than creating a new one, you should instead use Realm.add(_:update:), specifying true for the update argument. Note that this requires your type have a primary key property declared so that Realm knows which existing object to update.

Realm Async Thread Swift 3

Using Xcode-8.2.1, Swift-3.0.2, RealmSwift-2.2.0, iOS-Simulator-10:
Trying to write a View-Model with a Realm-Object, I fail at creating a returnArray in another thread. The issue is that the access to the previously created realm-object fails (most likely due to the background-thread access ??).
Can anybody tell me what is wrong with the following code (see below):
Important: It is given that the "createDataEntries()-method" is called before the "getEntries-completionHandler" (as can be seen with correct SimPholders realmobject-entry)! Therefore the "category" is set as "Love" (see code)
import Foundation
import RealmSwift
class MVVMCBalancesModel: BalancesModel
{
fileprivate var entries = [BalancesDataEntry]()
let realm = try! Realm()
init() {
self.createDataEntries()
}
fileprivate func createDataEntries() {
let myBalance = BalancesDataEntry()
myBalance.index = 0
myBalance.category = "Love" // !!!!!!! Here the category is filled
try! self.realm.write {
self.realm.deleteAll()
self.realm.add(myBalance)
}
}
func getEntries(_ completionHandler: #escaping (_ entries: [BalancesDataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
var returnArray: [BalancesDataEntry] = [BalancesDataEntry]()
let realmy = try! Realm()
let cnt = realmy.objects(BalancesDataEntry.self).count
for idx in 0 ..< cnt {
let obj = realmy.objects(BalancesDataEntry.self).filter("index = \(idx)").first!
returnArray.append(obj)
}
completionHandler(returnArray) // !!!!!!! BREAKPOINT (see screenshot below)
}
}
}
Running the above code and setting a breakpoint at the completionHandler(returnArray) produces the following:
Why is the "category" of the returnArray an empty String ???
Properties of objects retrieved from a Realm are lazily retrieved from the underlying storage. Accessing the properties from Swift will return the appropriate values. Likewise, if you run po returnArray from Xcode's LLDB console you should see the object's complete state. The instance variables, shown in the debugger popover, are only used when the object is unmanaged (prior to being added to the Realm).

Object has been deleted or invalidated realm

I have this class inherit from Object:
class Location: Object {
dynamic var id: String = ""
dynamic var name: String = ""
override class func primaryKey() -> String {
return "id"
}
}
This class is used as an instance inside my manager like this:
class LocationServiceAPI {
fileprivate var _location: Location?
var location: Location? {
get {
if _location == nil {
let realm = try! Realm()
_location = realm.objects(Location.self).first
}
return _location
}
set {
let realm = try! Realm()
if let newValue = newValue {
// delete previous locations
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
// store new location
try! realm.write {
realm.add(newValue, update: true)
_location = newValue
}
} else {
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
}
}
}
}
So whenever I get a location I delete the old one (new and old locations could be identical) and replace it with the new one, then I used the newValue as new value for the property _location but whenever I try to access the location it gives me 'Object has been deleted or invalidated'.
I am really confused since location will hold the value passed from the setter but not the realm!!
Note: If I stop the deleting then It will work fine.
The Object has been deleted or invalidated error will occur if an object has been deleted from a Realm, but you subsequently try and access a stored property of an instance of that object that your code was hanging onto since before the deletion.
You'll need to examine your logic paths and make sure there's no way you're deleting the location object, and not subsequently updating the _location property. There's no mention of deleting the object in the sample code you've provided, but your if let newValue = newValue line of code would mean that _location wouldn't actually get cleared if you passed in nil.
Finally, it's possible to manually check if an object has been deleted from a Realm by calling _location.invalidated, so if this happens a lot, it might be a good idea to include some extra checks in your code as well.
Without knowing really anything about your app and your design choices, it looks like you're trying to avoid reading/writing to the DB too often by caching the location property. Unless you're working with tons of LocationServiceAPI objects it shouldn't be a real performance penalty to actually read/write directly in the DB, like this :
class LocationServiceAPI {
var location: Location? {
get {
let realm = try! Realm()
return realm.objects(Location.self).first
}
set {
let realm = try! Realm()
if let newValue = newValue {
// store new location
try! realm.write {
realm.add(newValue, update: true)
}
} else {
// delete the record from Realm
...
}
}
}
}
Also, I would in general avoid keeping Realm objects along for longer periods, I don't say it's not possible but in general it leads to issues like you've experienced (especially if do multi-threading). In most cases I'd rather fetch the object from DB, use it, change it and save it back in the DB asap. If keeping references to specific records in the DB is necessary I'd rather keep the id and re-fetch it when I need it.

How to improve performance for large datasets with Realm?

My database has 500,000 records. The tables don't have a primary key because Realm doesn't support compound primary keys. I fetch data in background thread, then I want to display it in the UI on the main thread. But since Realm objects cannot be shared across threads I cannot use the record I fetched in the background. Instead I need to refetch the record on main thread? If I fetch a record out of the 500,000 records it will block the main thread. I don't know how to deal with it. I use Realm because it said it's enough quick. If I need refetch the record many times, is it really faster than SQLite? I don't want to create another property that combine other columns as primary key because the Realm database is already bigger than a SQLite file.
#objc class CKPhraseModel: CKBaseHMMModel{
dynamic var pinyin :String!
dynamic var phrase :String = ""
class func fetchObjects(apinyin :String) -> Results<CKPhraseModel> {
let realm = Realm.createDefaultRealm()
let fetchString = generateQueryString(apinyin)
let phrases = realm.objects(self).filter(fetchString).sorted("frequency", ascending: false)
return phrases
}
func save(needTransition :Bool = true) {
if let realm = realm {
try! realm.write(needTransition) {[unowned self] in
self.frequency += 1
}
}
else {
let realm = Realm.createDefaultRealm()
if let model = self.dynamicType.fetchObjects(pinyin).filter("phrase == %#", phrase).first {
try! realm.write(needTransition) {[unowned self] in
model.frequency += self.frequency
}
}
else {
try! realm.write(needTransition) {[unowned self] in
realm.add(self)
}
}
}
}
}
then I store fetched records in Array
let userInput = "input somthing"
let phraseList = CKPhraseModel().fetchObjects(userInput)
for (_,phraseModel) in phraseList.enumerate() {
candidates.append(phraseModel)
}
Then I want to display candidates information in UI when the user clicks one of these. I will call CKPhraseModel's save function to save changes. This step is on main thread.
Realm is fast if you use its lazy loading capability, which means that you create a filter that would return your candidates directly from the Realm, because then you'd need to only retrieve only the elements you index in the results.
In your case, you copy ALL elements out. That's kinda slow, which is why you end up freezing.

Inserting data into realm DB with progress?

I have request data which was about 7MB after it has downloaded the json string,means the json string is about 7MB.After it has downloaded,I would like to save the data into realm model object(table) with progress like
(1/7390) to (7390/7390) -> (data which is inserted/total data to be inserted)
I am using Alamofire as HTTPClient at my app.So,how to insert data with progress into my realm object model after it has downloaded from server?Any help cause I am a beginner.
I wont show the data model exactly,so,any example is appreciated.Let say my json string is.
{
{
name : Smith,
age : 23,
address : New York
},
{
name : Adam,
age : 22,
address : Maimi
},
{
name : Johnny,
age : 33,
address : Las Vegas
},
... about 7392 records
}
Supposing you have a label for do this.
Ok.
Supposing you using MVVM pattern too.
Ok.
ViewController has label and "observe"(*) the ViewModel property 'progress'
ViewModel has property 'progress'
class ViewModel: NSObject {
dynamic var progress: Int = 0
func save(object object: Object) {
do {
let realm = try Realm()
try realm.write({ () -> Void in
// Here your operations on DB
self.progress += 1
})
} catch let error as NSError {
ERLog(what: error)
}
}
}
In this way viewController is notified when "progress" change and you can update UI.
Your VC should have a method like this, called by viewDidLoad for instance:
private func setupObservers() {
RACObserve(self.viewModel, keyPath: "progress").subscribeNext { (next: AnyObject!) -> Void in
if let newProgress = next as? Int {
// Here update label
}
}
}
Where RACObserve is a global function:
import Foundation
import ReactiveCocoa
func RACObserve(target: NSObject!, keyPath: String) -> RACSignal {
return target.rac_valuesForKeyPath(keyPath, observer: target)
}
(*) You can use ReactiveCocoa for instance.
Katsumi from Realm here. First, Realm has no way to know the total amount of data. So calculating progress and notifying it should be done in your code.
A total is a count of results array. Store count as a local variable. Then define progress variable to store the number of persisted. progress should be incremented every save an object to the Realm. Then notify the progress.
let count = results.count // Store count of results
if count > 0{
if let users = results.array {
let progress = 0 // Number of persisted
let realm = try! Realm()
try realm.write {
for user in users{
let userList=UserList()
[...]
realm.add(userList,update: true)
progress += 1 // After save, increment progress
notify(progress, total: count)
}
}
}
}
So there are several ways to notify the progress. Here we use NSNotificationCenter for example:
func notify(progress: Int, total: Int) {
NSNotificationCenter
.defaultCenter()
.postNotificationName("ProgressNotification", object: self, userInfo: ["progress": progress, "total": total])
}