Swift Realm - Query Empty Set but Data is in RealmBrowser - ios

I am new to iOS developing. I try to receive the data which is stored in realm Database but sometimes I get an empty set although I can see the data in RealmBrowser. Often it works, but it is not reliable.
At first I clear the stored data to get sure I am not going to display old data.
After that I insert it to the RealmDatabase and in addition to that I query all stored data to display it in the tableView.
Here is my code (Swift 3):
let realm = try! Realm()
try! realm.write {
// clear all old data stored as fahrt.self
//realm.delete(realm.objects(fahrt.self));
realm.delete(realm.objects(fahrt.self));
let nsArray = try! JSONSerialization.jsonObject(with: data as Data, options: []) as! NSArray;
for eintrag in nsArray{
realm.create(fahrt.self, value: eintrag, update: false)
}
try! realm.commitWrite();
}
DispatchQueue.main.async {
let temp = ui as! DisplayViewController;
let realm = try! Realm()
let result = realm.objects(fahrt.self)
ui.showData(res: result);
// sometimes result is empty , don't know why
}
RealmModel:
class fahrt: Object {
dynamic var id: Int = 0
dynamic var date = ""
dynamic var from_lang: Double = 0.0
dynamic var from_lon: Double = 0.0
dynamic var to_lang: Double = 0.0
dynamic var to_lon: Double = 0.0
override static func primaryKey() -> String? {
return "id"
}
}

First of all, you don't need to include realm.commitWrite() inside a realm.write { } block. realm.commitWrite() is used in conjunction with realm.beginWrite(), but both of these are automatically called inside realm.write { }. :)
Secondly, when you perform a write transaction on a background thread, the changes are automatically exposed on the main thread on the next iteration of the run loop. However, in some cases, when immediately calling code on the main thread after a background write, it's possible this hasn't happened yet. To ensure Realm has the very latest copy of its data on the main thread, you should call realm.refresh() in that dispatch block before you perform the query.

Related

Is there any pagination like code for saving JSON data to Realmdatabase

I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.

Swift how to choose local database for offline dictionary?

I want to make an offline translator (dictionary) with swift 4.
I need advice on where to store words with translation.
The words with the translation are now in the dsl format and I search for this particular file, but on average it takes 2-3 seconds to search.
Is it possible to speed up the search? If i put words with translation in core data or realm database, its help or not ?
Realm Swift enables you to efficiently write your app’s model layer in a safe, persisted and fast way. Here’s what it looks like:
// Define your models like regular Swift classes
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var age = 0
}
class Person: Object {
#objc dynamic var name = ""
#objc dynamic var picture: Data? = nil // optionals supported
let dogs = List<Dog>()
}
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \(myDog.name)")
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs less than 2 years old
let puppies = realm.objects(Dog.self).filter("age < 2")
puppies.count // => 0 because no dogs have been added to the Realm yet
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
// Queries are updated in realtime
puppies.count // => 1
// Query and update from any thread
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
let theDog = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
theDog!.age = 3
}
}
}
For a dictionary app. I'd definitely go with Realm. it uses NSFileProtection and encryption so people won't be able to steal your translation data.
Setup RealmSwift for your projects

Realm swift thread safe variable

I am new to Realm and I am wondering if it is good idea to store primary key of object in View controller and object as stored property.
class VC: ViewController{
public var id:Int!
private var customer:Customer{
get{
return DBO.loadCustomer(for: id)
}
}
}
class DBO{
public static func loadCustomer(for id: Int) -> Customer{
let realm = try! Realm()
return realm.object(ofType: Customer.self, forPrimaryKey: id)!
}
}
I am assuming that this because of this my app will be thread safe, because every time thread will try to access customer it will fetch object from db. But I wonder if this is efficient for single object and array of objects.
As of Realm 2.2 you could use thread-safe references (see here):
Now, you can create thread-safe references for all types that were
previously thread-confined within a Realm, and passing objects between
threads becomes a simple, three-step process:
Initialize a ThreadSafeReference with the thread-confined object.
Pass that ThreadSafeReference to a destination thread or queue.
Resolve this reference on the target Realm by calling
Realm.resolve(_:). Use the returned object as you normally would.
And a code sample, from the source above:
let realm = try! Realm()
let person = Person(name: "Jane") // no primary key required
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "com.example.myApp.bg").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
But, your approach should also work fine.

Re-initializing a Realm object in a different thread

Is running a query the only way of re-initializing a Realm Object for use in a different thread? Is there no way to somehow grab some kind of reference for use with Realm, so that the query doesn't have to be completely from scratch? It would be nice to have some kind of guarantee that the object is for the same record (if it exists and the query for that "ref" is successful), without having to add a primary key just for this one purpose.
This outlines my situation:
func construct(name: String, tokens: [String]) -> Document {
let doc = Document()
let realm = try! Realm()
try! realm.write {
realm.add(doc)
}
DispatchQueue.global(qos: .userInitiated).async {
let realm = try! Realm()
// Some long running task to convert `tokens` into `[Blurb]`
// var blurbs: [Blurb]
// What's the recommended way of re-initing that new `Document`?
// let sameDoc = .....
try! realm.write {
sameDoc.blurbs.append(blurbs)
}
}
return doc
}

How do I fill these arrays in order to then populate a tableView?

I am trying to fetch some data from the backend and then assign it to 3 different arrays. These arrays I then want to use to populate my tableViewCells. The issue is, when I print my arrays outside of the fetch block, they return nil. When I print them in the fetch block, they return the object's variables which I intend it to do so.
I include the self.tableView.reloadData() line in the hope that the arrays get populated and subsequently fill the tableViewCells, but it doesn't seem to be working.
Any suggestions welcomed on how to get those arrays populated correctly so when I print them outside of the fetch request they return the appropriate data.
var capArray = [String]()
var imageDic = [String: [PFFile]]()
var priceArray = [Int]()
override func viewDidAppear(animated: Bool) {
capArray.removeAll(keepCapacity: true)
imageDic.removeAll(keepCapacity: true)
priceArray.removeAll(keepCapacity: true)
let query = PFQuery(className: "SellerObject")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for o in objects {
if o.objectForKey("caption") != nil && o.objectForKey("imageFile") != nil && o.objectForKey("price") != nil {
let cap = o.objectForKey("caption") as? String
self.capArray.append(cap!)
let imdic = o.objectForKey("imageFile") as? [PFFile]
self.imageDic[cap!] = imdic
let price = o.objectForKey("price") as? String
let priceInt = Int(price!)
self.priceArray.append(priceInt!)
}
}
}
}
self.tableView.reloadData()
}
Your issue is self.tableView.reloadData() is executing before the block has finished executing.
The simplest thing to see it working is to move self.tableView.reloadData() inside the block, but that is a short term hack just to see things in action.
But that is not the correct long-term approach. To do this properly your array population code should really be in a model.
Your table view would load initially with no data to use, your model would notify your view controller as data becomes available and the view controller then updates the table.
If the data comes from the device the table view could wait before it loads if the data can be retrieved quickly, if the data is coming over a network it needs to wait.

Resources