How to do Abstract Entities in Realm.io in Swift - ios

I am converting from CoreData to Realm.io I did a little experiment to see how Realm.io handles situations where I need to have subclasses of a class that is an RLMObject.
Model
import Realm
#objc enum RecurrenceEnum : Int {
case Daily = 1
case Weekly = 2
case Monthly = 3
}
class Challenge: RLMObject {
dynamic var title = ""
}
class TotalCountChallenge: Challenge {
dynamic var totalCountGoal: Int = 0
}
class RecurringChallenge: Challenge {
dynamic var recurranceType: RecurrenceEnum = .Daily
dynamic var totalCountGoal: Int = 0
}
When I save either a TotalCountChallenge or A RecurringChallenge it reports no errors but when I go to query for challenges by title I don't get anything back.
Query from my ViewController
// Query using an NSPredicate object
let predicate = NSPredicate(format: "title BEGINSWITH %#", "Booya")
var challenges = Challenge.objectsWithPredicate(predicate)
if challenges == nil || challenges.count == 0 {
let tcChallenge = TotalCountChallenge()
tcChallenge.title = "Booya Total Count Challenge"
tcChallenge.totalCountGoal = 1_000_000
let rChallenge = RecurringChallenge()
rChallenge.title = "Booya Recurring Challenge"
rChallenge.recurranceType = .Weekly
rChallenge.totalCountGoal = 2_000_000
let realm = RLMRealm.defaultRealm()
// You only need to do this once (per thread)
// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(tcChallenge)
realm.addObject(rChallenge)
realm.commitWriteTransaction()
}
challenges = Challenge.objectsWithPredicate(predicate)
if challenges != nil && challenges.count > 0 {
for challenge in challenges {
let c = challenge as! Challenge
println("\(c.title)")
}
} else {
println("No Challenges found")
}
challenges = TotalCountChallenge.objectsWithPredicate(predicate)
if challenges != nil && challenges.count > 0 {
for challenge in challenges {
let c = challenge as! Challenge
println("TotalCountChallenge: \(c.title)")
}
} else {
println("No Total Count Challenges found")
}
challenges = RecurringChallenge.objectsWithPredicate(predicate)
if challenges != nil && challenges.count > 0 {
for challenge in challenges {
let c = challenge as! Challenge
println("RecurringChallenge \(c.title)")
}
} else {
println("No Recurring Challenges found")
}
Output
No Challenges found
TotalCountChallenge: Booya Total Count Challenge
RecurringChallenge Booya Recurring Challenge
When I look at the database using the Browse Tool provided by Realm I see that there is only 1 TotalCountChallenge and 1 RecurringChallenge and there are no Challenges
Is there a way to do this?
Here is a link to the code in github: lewissk/RealmTest

Realm supports subclassing, but not the sort of polymorphism that you're looking for. In Realm, each object type is stored in its own table, regardless of whether or not you've declared it in code as a subclass of another object. The implication of this is that isn't not currently possible to query across different object classes, even if they share a common superclass. There's an issue tracking this feature request at https://github.com/realm/realm-cocoa/issues/1109.

Related

Coredata relationships how make ordered collection or added in particular index in iOS swift

I am working with core-data relationships add the student in profile entities.
Profile and Student entities are multiple relationship with each others.
Profile entities for create students, it successfully created.
I want to add or append some information that profile entities through student entities its also added.
(Its Like: Profile entities have a array of dictionary of student entities )
But when in display in UItableview added info of student it display in unordered.
I want to display the added student should be display in last or first.
Coredata is unordered collection of set. how to make it order.
Also selected ordered Arrangement. its shows error students not be ordered.
How can achieve this. Help me
Here my code:
func create(record: ProfileModel) {
let cdProfile = CDProfile(context: PersistentStorage.shared.context)
cdProfile.emailID = record.emailID
cdProfile.gender = record.gender
cdProfile.getDate = record.getDate
cdProfile.id = record.id
if(record.toStudent != nil && record.toStudent?.count != 0){
var studentSet = Set<CDStudent>()
record.toStudent?.forEach({ (student) in
let cdStudent = CDStudent(context: PersistentStorage.shared.context)
cdStudent.activity = student.activity
cdStudent.currentPage = Int16(student.currentPage ?? 0)
cdStudent.getPercentage = student.getPercentage
studentSet.insert(cdStudent)
})
cdProfile.toStudent = studentSet
}
PersistentStorage.shared.saveContext()
}
#IBAction func saveBtnClick(_ sender: Any) {
let studentArr = StudentModel(_activity: "S-\(self.sectionString)", _studentComments: self.infotextView.text, _getPercentage: "-", _result: String(self.audioValueKey.count), _sectionID: self.sectionString, _sessionDate: self.convertedDate, _timeSpend: self.timeSpendStr, _currentPage: self.allPageNumber, _selectedValue: self.audioValueKey)
if let getStudentData = userProfileArr![indexNumber].toStudent?.count{
self.personArrCount = getStudentData
let getArr = userProfileArr![indexNumber].toStudent!
if getArr.count == 0{
}else{
for j in 0..<getArr.count{
self.student.append(getArr[j])
// self.student.insert(getArr[j], at: 0)
}
self.student.append(studentArr)
}
}else{
self.personArrCount = 0
self.student.append(studentArr)
print("student-empty",student)
}
let getProfileData = userProfileArr![indexNumber]
let updatePerson = ProfileModel(_id: selectedUserIndex!.id, _profileComments: getProfileData.profileComments!, _emailID: getProfileData.emailID!, _gender: getProfileData.gender!, _profileImage: getProfileData.profileImage!, _getDate: "NO", _studentDOB: getProfileData.studentDOB!, _studentName: getProfileData.studentName!, _toStudent: student)
print("student",self.student)
if(dataManager.update(record: updatePerson))
{
print("Update added")
}else{
print("Not-- added")
}
}
How can i fix this issue help me... Thanks advance.
first of all Core data only provides sorting on parent table only
if you wanna sort data in a subtable you can do as below
First you need to add a field named student_id(int16) in student table
Then you need to assign value as count + 1
As core data does not provide autoincrement field need to manage manually.
Follow the below code to sort data as last to first
// assume you have fetched profile in stud_profile var
if let student_list = stud_profile.toStudent as? Set<CDStudent> {
let arrStudents = Array(student_list).sorted(by: {$0.student_id > $1.student_id})
}
4.You can use arrStudents as it will return sorted [CDStudent]

Core data taking time to insert records with fetching entity & set as relationship

I have 2 entities: Components & SurveyReadingWeb that have a one-to-many relationship.
I have saved Components data in core data database; but while saving SurveyReadingWeb -- in a for loop -- I have to fetch Components data entity by passing component id and setting the surveyreadings isComponentexists relationship, like in the code below for 8000 records. This process is taking too much time, nearly 7 minutes. How can I reduce the time?
for item in items {
autoIncrementId = autoIncrementId + 1
print("reading items parsed:- \(item.SurveyReadingId), \(autoIncrementId)")
let reading : SurveyReadingWeb? = NSEntityDescription.insertNewObject(forEntityName: Constants.EntityNames.kSURVEYREADINGWEB, into: self.managedContext) as? SurveyReadingWeb
reading?.surveyId1 = Int32(autoIncrementId)
reading?.surveyId = Int32(item.SurveyId) ?? 0
reading?.readingData = item.CH4
reading?.surveyDateTime = item.SurveyReadingDateTime
reading?.latitude = item.Latitude
reading?.longitude = item.Longitude
reading?.serialNumber = item.SerialNumber
reading?.descriptionText = item.Description
reading?.componentName = item.Description
reading?.componentId = Int32(item.ComponentId) ?? 0
reading?.readingTypeId = Int32(item.ReadingTypeID) ?? 0
reading?.readingTypeDetails = item.ReadingTypeDetails
reading?.currentLatitude = item.CurrentLatitude
reading?.currentLongitude = item.CurrentLongitude
reading?.hdop = item.HDOP
reading?.vdop = item.VDOP
reading?.componentDistance = item.ComponentDistance
reading?.facilityId = Int32(item.ProjectId) ?? 0
reading?.posted = 1
reading?.readyToSync = 1
reading?.isComponentExists = DatabaseManager.sharedManager.getCompNameFromID(compID: Int32(item.ComponentId) ?? 0)
}
saveContext()
func getCompNameFromID(compID : Int32) -> Components? {
var component : Components? = nil
let fetchRequest : NSFetchRequest<Components> = Components.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "componentId == %ld", compID)
do {
let searchResults = try managedContext.fetch(fetchRequest)
component = (searchResults.first) ?? nil
} catch {
}
return component ?? nil
}
You can reduce the time dramatically if you add an NSCache instance to cache compID:Components key value pairs.
In getCompNameFromID check if the compID exists in the cache. If yes get the value from the cache (very fast), if not fetch the record and save it for the compID in the cache.
And use the convenient SurveyReadingWeb(context: initializer to get rid of all the ugly question marks, however the performance benefit is rather tiny.

Couchbase lite, Search query taking very long time

When I try to search the couchbase documents of size around 10K, the searching is taking very long time. Below are the code snippet. Can anyone optimize it or suggest me any alternative approach. Thank you.
1) Search function
func search(keyword:String) -> [[String:AnyObject]] {
var results:[[String:AnyObject]]=[]
let searchView = database.viewNamed(AppConstants().SEARCH)
if searchView.mapBlock == nil {
startIndexing()
}
let query = searchView.createQuery()
var docIds = Set<String>()
let result = try query.run()
while let row = result.nextRow() {
let key = "\(row.key)"
let keyArr = keyword.characters.split(" ")
for (index, element) in keyArr.enumerate() {
let keyItem = String(element)
if key.lowercaseString.containsString(keyItem.lowercaseString) {
let value = row.value as! [String:AnyObject]
let id = value["_id"] as? String
if id != nil && !docIds.contains(id!) {
results.append(value)
docIds.insert(id!)
}
}
}
}
}
2) Indexing
func startIndexing() {
let searchView = database.viewNamed(AppConstants().SEARCH)
if searchView.mapBlock == nil {
searchView.setMapBlock({ (doc, emit) in
let docType = doc[AppConstants().DOC_TYPE] as! String
if AppConstants().DOC_TYPE_CONTACT.isEqual(docType) {
self.parseJsonToKeyValues(doc)
for value in self.fields.values {
emit(value, doc)
}
self.fields.removeAll()
}
}, version: "1")
}
}
self.parseJsonToKeyValues(doc) will return me the key value store of my documents to index.
You're emitting the entire document along with every field for your view. This could easily cause your queries to be slow. It also seems unlikely you want to do this, unless you really need to be able to query against every field in your document.
It's considered best practice to set your map function right after opening the database. Waiting until right before you query may or may not slow you down.
See https://developer.couchbase.com/documentation/mobile/current/guides/couchbase-lite/native-api/view/index.html for more, especially the section labeled "Development Considerations".

Faster way to check if entry exists in Core Data

In my app when data is synced i can get 20k entries (from given timestamp) from the server that should be synced to the local device. For every entry i try to fetch it (if it exist already) and if doesn't i create new. The problem is that the whole operation is too slow - for 20k on iphone 5 is 10+ mins. Another solution that i though is to delete all entries from the given timestamp and create new entries for all returned entries and there will be no need to perform fetch for every single entry ? If someone have any advice will be nice. Here is sample code for the current state:
var logEntryToUpdate:LogEntry!
if let savedEntry = CoreDataRequestHelper.getLogEntryByID(inputID: inputID, fetchAsync: true) {
logEntryToUpdate = savedEntry
} else {
logEntryToUpdate = LogEntry(entity: logEntryEntity!, insertInto: CoreDataStack.sharedInstance.saveManagedObjectContext)
}
logEntryToUpdate.populateWithSyncedData(data: row, startCol: 1)
Here is the actual request method:
class func getLogEntryByID(inputID:Int64, fetchAsync:Bool) ->LogEntry? {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
if let fetchResults = try mocToFetch.fetch(logEntryRequest) as? [LogEntry] {
if ( fetchResults.count > 0 ) {
return fetchResults[0]
}
return nil
}
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return nil
}
Another thing that i tried is to check the count for specific request but again is too slow.
class func doesLogEntryExist(inputID:Int64, fetchAsync:Bool) ->Bool {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
//logEntryRequest.resultType = .countResultType
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
let count = try mocToFetch.count(for: logEntryRequest)
if ( count > 0 ) {
return true
}
return false
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return false
}
Whether fetching the instance or getting the count, you're still doing one fetch request per incoming record. That's going to be slow, and your code will be spending almost all of its time performing fetches.
One improvement is to batch up the records to reduce the number of fetches. Get multiple record IDs into an array, and then fetch all of them at once with a predicate like
NSPredicate(format: "inputId IN %#", inputIdArray)
Then go through the results of the fetch to see which IDs were found. Accumulate 50 or 100 IDs in the array, and you'll reduce the number of fetches by 50x or 100x.
Deleting all the entries for the timestamp and then re-inserting them might be good, but it's hard to predict. You'll have to insert all 20,000. Is that faster or slower than reducing the number of fetches? It's impossible to say for sure.
Based on Paulw11's comment, I came up with the following method to evaluate Structs being imported into Core Data.
In my example, I have a class where I store search terms. Within the search class, create a predicate which describes the values of the stuff within my array of structs.
func importToCoreData(dataToEvaluateArray: [YourDataStruct]) {
// This is what Paul described in his comment
let newDataToEvaluate = Set(dataToEvaluateArray.map{$0.id})
let recordsInCoreData = getIdSetForCurrentPredicate()
let newRecords = newDataToEvaluate.subtracting(recordsInCoreData)
// create an empty array
var itemsToImportArray: [YourDataStruct] = []
// and dump records with ids contained in newRecords into it
dataToEvaluateArray.forEach{ record in
if newRecords.contains(record.id) {
itemsToImportArray.append(record)
}
}
// THEN, import if you need to
itemsToImportArray.forEach { struct in
// set up your entity, properties, etc.
}
// Once it's imported, save
// You can save each time you import a record, but it'll go faster if you do it once.
do {
try self.managedObjectContext.save()
} catch let error {
self.delegate?.errorAlert(error.localizedDescription, sender: self)
}
self.delegate?.updateFetchedResultsController()
}
To instantiate recordsInCoreData, I created this method, which returns a Set of unique identifiers that exist in the managedObjectContext:
func getIdSetForCurrentPredicate() -> Set<String> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "YourEntity")
// searchQuery is a class I created with a computed property for a creating a predicate. You'll need to write your own predicate
fetchRequest.predicate = searchQuery.predicate
var existingIds: [YourEntity] = []
do {
existingIds = try managedObjectContext.fetch(fetchRequest) as! [YourEntity]
} catch let error {
delegate?.errorAlert(error.localizedDescription, sender: self)
}
return Set<String>(existingIds.map{$0.id})
}

Retrieving data from Firebase takes long on Swift

I'm currently working on a project which let users to rent extra spaces. You can think clone of airbnb. In this iOS app, I'm using Firebase for Back-end as a Service.
I want to retrieve the flats that satisfy the filtering options like, bed count, bedroom count, Smoking etc. Logically, I'm grouping all flats by time slots first, then fetching the flats satisfies filtered city, then i'm fetching the flats which satisfies the filters. Then I'm iterating all the flats one by one to fetch their images.
My Json tree is like:
filter_flats/FlatCity/FlatID
time_slots/timestamp/flatID:true
flat_images/FlatID
Here is what I'm trying:
import Foundation
import Firebase
import FirebaseStorage
protocol QuerymasterDelegate :class
{
func getFilteredFlats(filter:FilterModel, completion: #escaping ([FilteredFlat]) -> ())
}
class Querymaster:QuerymasterDelegate
{
var flatEndpoint = FIRFlat()
var returningFlats = [FilteredFlat]()
/////////////////////////////////////////////////////////////
//MARK: This method pulls all flats with required timeslot.//
/////////////////////////////////////////////////////////////
internal func getFilteredFlats(filter: FilterModel, completion: #escaping ([FilteredFlat]) -> ()) {
var zamanAraliginaUygunFlatlar = [String]()
FIRREF.instance().getRef().child("time_slots").queryOrderedByKey().queryStarting(atValue: filter.fromDate?.toTimeStamp()).queryEnding(atValue: filter.toDate?.toTimeStamp()).observeSingleEvent(of: .value, with: { (ss) in
for ts in ss.children.allObjects
{
let timeslotFlatObj = ts as! FIRDataSnapshot
let timeslotForFlat = timeslotFlatObj.value as! [String:Bool]
for x in timeslotForFlat
{
if (x.value == true && !zamanAraliginaUygunFlatlar.contains(x.key))
{
zamanAraliginaUygunFlatlar.append(x.key)
}
}
}
/////////////////////////////////////////////////////////
//MARK: This method pulls all flats with filtered city.//
/////////////////////////////////////////////////////////
FIRREF.instance().getRef().child("filter_flats/" + filter.city!).observe(.value, with: { (ss) in
var sehirdekiFlatler = [String:Flat]()
for i in ss.children.allObjects
{
let flt = Flat()
let flatObject = i as! FIRDataSnapshot
let mainDict = flatObject.value as! [String:Any]
flt.userID = (mainDict["userId"] as? String)!
flt.id = flatObject.key
flt.city = filter.city
flt.title = mainDict["title"] as? String
flt.bathroomCount = mainDict["bathroomCount"] as? Int
flt.bedCount = mainDict["bedCount"] as? Int
flt.bedroomCount = mainDict["bedroomCount"] as? Int
flt.internet = mainDict["internet"] as? Bool
flt.elevator = mainDict["elevator"] as? Bool
flt.heating = mainDict["heating"] as? Bool
flt.gateKeeper = mainDict["gateKeeper"] as?Bool
flt.parking = mainDict["parking"] as? Bool
flt.pool = mainDict["pool"] as? Bool
flt.smoking = mainDict["smoking"] as? Bool
flt.tv = mainDict["tv"] as? Bool
flt.flatCapacity = mainDict["capacity"] as? Int
flt.cooling = mainDict["cooling"] as? Bool
flt.price = mainDict["price"] as? Double
flt.washingMachine = mainDict["washingMachine"] as? Bool
sehirdekiFlatler[flt.id] = flt
if(!(zamanAraliginaUygunFlatlar).contains(flt.id))
{
if(filter.bathroomCount == nil || filter.bathroomCount! <= flt.bathroomCount!)
{
if(filter.bedCount == nil || filter.bedCount! <= flt.bedCount!)
{
if(filter.bedroomCount == nil || filter.bedroomCount! <= flt.bedroomCount!)
{
if(filter.internet == false || filter.internet! == flt.internet!)
{
if(filter.elevator == false || filter.elevator! == flt.elevator!)
{
if(filter.heating == false || filter.heating! == flt.heating!)
{
if(filter.gateKeeper == false || filter.gateKeeper! == flt.gateKeeper!)
{
if(filter.parking == false || filter.parking! == flt.parking!)
{
if(filter.pool == false || filter.pool! == flt.pool!)
{
if(filter.smoking! == false || filter.smoking! == flt.smoking!)
{
if(filter.tv! == false || filter.tv! == flt.tv!)
{
if(filter.capacity == nil || filter.capacity! <= flt.flatCapacity!)
{
if(filter.cooling == false || filter.cooling! == flt.cooling!)
{
if(filter.priceFrom == nil || filter.priceFrom! <= flt.price!)
{
if(filter.priceTo == nil || filter.priceTo! >= flt.price!)
{
if(filter.washingMachine == false || filter.washingMachine! == flt.washingMachine!)
{
let filteredFlat = FilteredFlat()
filteredFlat.flatCity = flt.city
filteredFlat.flatID = flt.id
filteredFlat.flatPrice = flt.price
filteredFlat.flatTitle = flt.title
filteredFlat.userID = flt.userID
self.returningFlats.append(filteredFlat)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
////////////////////////////////////////////////////////////////
//MARK: This method pulls thumbnail image for returning Flats.//
////////////////////////////////////////////////////////////////
if self.returningFlats.count > 0
{
for a in self.returningFlats
{
FIRREF.instance().getRef().child("flat_images/" + a.flatID!).observeSingleEvent(of: .value, with: { (ss) in
let dict = ss.children.allObjects[0] as! FIRDataSnapshot
let obj = dict.value as! [String:String]
let flatImageDownloaded = FlatImageDownloaded(imageID: dict.key, imageDownloadURL: obj["downloadURL"]!)
a.flatThumbnailImage = flatImageDownloaded
completion(self.returningFlats)
})
}
}
})
})
}
}
The marked code This method pulls thumbnail image for returning Flats. is usually fetching data slower because of jumping among nodes of flats.
I didn't realized that what i'm doing wrong. Can you suggest more efficient method?
Thanks in advance.
This sounds like you are working with a significant amount of data, and we don't know your current UI but that may be the place to start to help reduce the bandwidth and speed up performance.
For example, in the UI there are three buttons that activate as the user selects the prior one.
Select City <- highlighted and ready to be tapped
Select Bedrooms <-Dimmed and not available yet
Select Internet <-Dimmed and not available yet
When the user taps Select City, they are presented a list of Cities available. this is easy and a fast Firebase query.
They tap Select Bedrooms and select 2. No real query needed here yet since it's a number between 1 and 5 (for example).
They tap Select Internet and this is a Yes/No or maybe None/Dial up/Broadband.
Now here's the key - the Structure.
City0
name: "some city"
Flats
flat_0
location: "City0"
bedrooms: "2"
internet: "Broadband"
filter: "City0_2_Broadband"
flat_1
location: "City0"
bedrooms: "4"
internet: "Dial up"
filter "City0_4_Dial up"
with this, you can search for any city, or any number of bedrooms or any internet as separate queries.
The real power is having the ability to perform an SQL-like 'and' query when you want to get very specific data; which flats are available and 4 bedrooms and dial up and in city0
query for: "City0_4_Dial up"
very fast and flexible and you can build the criteria for the query within the UI which limits the amount of data flowing into the app from Firebase, which makes it very fast.
If this is too far off base, let me know and I will edit and provide another option.
Firebase uses NoSQL database, so the typical querying would be very inefficient.
As far as I understand, your app requires a lot of query for the data, so Firebase itself does not the perfect solution for your app I guess..
Anyway, in the code, you can just request images in Dispatch async block, and that would make the process faster.
Also, fyi, storing images in FirebaseStorage and store the url in the FirebaseDatabase will help you reduce a lot of money per data.

Resources