Realm - how to find a string in a List - ios

Here's my object:
class Cat: Object {
let toys = List<String>()
}
How can I find a toy in the toys array and delete it?
if let foundToy = cat.toys.filter(???).first {
try! realm.write {
realm.delete(foundToy)
}
}

Realm does not support queries on a List of primities (yet).
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
You will need to define a Realm ToyClass and have a property of String.
See Array of Primitives: Support queries #5361
So create a ToyClass
class ToyClass: Object {
#objc dynamic var toy_name = ""
}
and update your CatClass List
class CatClass: Object {
let toys = List<ToyClass>()
}
There are lots of way to delete but if you know the name of the toy, you can delete it directly from Realm.
IMPORTANT - this will remove the first object that matches the filter criteria completely from Realm which includes the object and the reference to it in the list. Note that it's going to delete whatever object matches the first object so if you have two objects called 'toy 1' it will delete one of them - data stored in realm is 'unsorted' so the result may not be what you want.
if let toyToDelete = realm.objects(ToyClass.self).filter("toy_name == 'toy 1'").first {
try! realm.write {
realm.delete(toyToDelete)
}
}
If you just want to remove the first object that matches the criteria (which may be dangerous) from the list but keep the object in Realm, you can do this
let cat = realm.objects(CatClass.self).first!
if let toyToDelete = cat.toys.filter("toy_name == 'toy 1'").first {
try! realm.write {
cat.toys.realm?.delete(toyToDelete)
}
}
You should really add a primary key to your objects so you can tell realm specifically which object to find/delete.
class ToyClass: Object {
#objc dynamic var toy_id = UUID().uuidString
#objc dynamic var toy_name = ""
override static func primaryKey() -> String? {
return "toy_id"
}
}
EDIT: Some testing code to demonstrate firstIndex potentially not working
Set up the cat and two toys
let cat0 = CatClass()
cat0.cat_name = "cat 0"
let toy0 = ToyClass()
toy0.toy_name = "toy 0"
let toy1 = ToyClass()
toy1.toy_name = "toy 1"
cat0.toys.append(toy0)
cat0.toys.append(toy1)
try! realm.write {
realm.add(cat0)
}
Then retrieve cat0 and attempt to get the index of toy 1
let cat = realm.objects(CatClass.self).filter("cat_name == 'cat 0'").first!
let toyToBeDeleted = cat.toys.filter("toy_name == 'toy 1'").first!
print(toyToBeDeleted) //prints toy 1
let index = cat.toys.firstIndex(of: toyToBeDeleted)
print(index) //prints nil

If you are keeping an array of distinct String (i.e., the toys array doesn't contain duplicates), you can just delete the first String found:
if let toyIndex = cat.toys.firstIndex(of: toyNameToBeDeleted) {
try! realm.write {
cat.toys.remove(at: toyIndex)
 }
}
If you are trying to delete all String objects == to a certain toy name, do this instead:
try! realm.write {
cat.toys = cat.toys.filter { $0 != toyNameToBeDeleted }
}

Related

adding objects in array creates duplicate values in swift

I have an array of custom objects but when I add items to array it creates duplicate of last item add in array.
Below is my code, please suggest where is the mistake, this small thing not able to get it.
var tempArr:[AnimalViewModel] = [AnimalViewModel]()
do {
var objAnimal = Animal()
var result = try managedContext.fetch(fetchRequest)
for ds in result as! [NSManagedObject] {
objAnimal.name = (ds.value(forKey: "name")) as! String
objAnimal.type = (ds.value(forKey: “type”)) as! String
Var objAVM = AnimalViewModel(aniModel: objAnimal)
tempArr.append(objAVM)
}
} catch {
print(" Error ")
}
The array tempArr contains all duplicate element as last inserted element even objAnimal contains different values.
Thanks,
First of all never print a meaningless literal string like "Error" in a catch block. Print always the error instance.
Animal is obviously a class (reference type). You are creating one instance and the properties are updated in the loop. As always the same instance is used the values are overwritten and you get result.count items with the same contents.
Create new instances inside the loop and replace Entity with the real entity name
var tempArr = [AnimalViewModel]()
do {
let result = try managedContext.fetch(fetchRequest) as! [Entity] // let !
for ds in result {
let objAnimal = Animal() // let !
objAnimal.name = ds.name
objAnimal.type = ds.type
let objAVM = AnimalViewModel(aniModel: objAnimal) // let !
tempArr.append(objAVM)
}
} catch {
print(error)
}
And please notice and fix the warnings about never mutated variables

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})
}

Doing RealmSwift with FILO record insertion with 50 records limitation

I have NotificationList Object which show many notification data with tableView.
import RealmSwift
class NotificationList: Object {
dynamic var title = ""
dynamic var body = ""
dynamic var createdAt = NSDate()
let notifications = List<Notification>()
}
Everytime I do insert record,I run this function using RealmSwift.
func insertNotification(list: NotificationList){
try! realm.write({ () -> Void in
realm.add(list)
})
}
But,what I really need a help with was,I want to check all total count of NotificationList Realm object before it insert records anytime the notifications come in.And after i did check total count,I want to delete if total count exceeds more than 50**(count<=50)** using FILO(First In Last Out) by createdAt sorting.
Any help with how to do that realmSwift query every time that I insert new records?I am new to RealmSwift.I can only do CRUD for now because I am beginner.
You're on the right track. :)
Realm has a very useful feature in the fact that results from queries dynamically update as the underlying data is modified, as such, it's quite trivial to iterate through your existing list of notification lists, and remove the older ones until the count is down to 50 again.
func insertNotification(list: NotificationList){
// Insert the new list object
try! realm.write {
realm.add(list)
}
// Iterate through all list objects, and delete the earliest ones
// until the number of objects is back to 50
let sortedLists = realm.objects(NotificationList).sorted("createdAt")
while sortedLists.count > 50 {
let first = sortedLists.first
try! realm.write {
realm.delete(first)
}
}
}
Let me know how you go!
A more concise alternative:
func insertNotification(list: NotificationList) {
// Insert the new list object
try! realm.write {
realm.add(list)
}
// Trim the number of objects back down to 50, keeping the newest objects.
let sortedLists = realm.objects(NotificationList).sorted("createdAt")
if sortedLists.count > 50 {
try! realm.write {
realm.delete(sortedLists.prefix(sortedLists.count - 50))
}
}
}
You will need to get the count of NotificationList objects, and then need to delete any excess objects if their count is more than or equal to 50, plus one more for the object being inserted. You will need to delete older objects. Here is the implementation that can help you.
func insertNotification(list: NotificationList){
//Check if there are more than 50 notifications lists already we want
//to delete all items in access of 50 plus one more to accommodate
//the new object being inserted.
let sortedItems = realm.objects(NotificationList).sorted("createdAt")
while sortedItems.count >= 50 {
let first = sortedItems.first
try! realm.write {
realm.delete(first)
}
}
//Now add the new object
try! realm.write({ () -> Void in
realm.add(list)
})
}
Same Logic in CoreData
let aryCoreData = CoreDataManager.shared.fetch(DataDB.self)
for i in 0...aryCoreData.count-1 {
if i > 4 {
let first = aryCoreData.first!
CoreDataManager.shared.delete(object: first)
}
}

Swift: array of objects search

I want to search in array of objects in swift
but I didn't know how :(
I tried
filteredArrayUsingPredicate
but still don't work ,It's giving me an error msg
-- Update --
the error message is
swift:42:9: 'Array<search_options>' does not have a member named 'filteredArrayUsingPredicate'
-- Update --
class search_options {
let id:String
let option:String
init(){}
init(id:String ,option:String){
self.id = id
self.option = option
}
}
I only want to search in option variable
And when I tried to used
func searchBarSearchButtonClicked( searchBar: UISearchBar!)
{
let filteredArray = filter(search_options_array) { $0 == "test" }
println(searchBar.text)
}
I got this message
swift:40:58: 'search_options' is not a subtype of 'String'
Find index of specific object:
if let index = find(myArray, objectIAmLookingFor) {
// found! do something
}
Filter array:
let filteredArray = filter(myArray) { $0 == objectIAmLookingFor }
Finally after long search I did't ! ,
I was looking to find a way to do a dynamic search like if array of String contains
"hello","lo","yes"
and I want to get all the strings that contains for example "lo"
I want to get "hello" and "lo"
so the best way I found is regular expression search
so I do a For Loop throw all options in Array and compare every single object variable to the pattern ,and save it in new array on objects
for var i = 0; i < search_options_array.count; i++ {
let myRegex = "searched_text"
if let match = search_options_array[i].option.rangeOfString(myRegex, options: .RegularExpressionSearch){
filtered_options_array.append(search_options(id:search_options_array[i].id,option:search_options_array[i].option) )
}
}
The best part here you can use all benefits of regular expression and have a copy of yours old array so if you need it.
Thanks every one for helping.
Because filter accepts as a predicate a function which maps each element of the given Array to a Bool value (to determine which value should be filtered out), in your case it may be this way;
let a = [
search_options(id: "a", option: "X"),
search_options(id: "b", option: "Y"),
search_options(id: "c", option: "X")
]
let b = filter(a) { (e: search_options) in e.option == "X" }
// ==> [search_options(id: "a", option: "X"), search_options(id: "c", option: "X")]
The correct answer is
func searchBarSearchButtonClicked( searchBar: UISearchBar!)
{
let filteredArray = filter(search_options_array) { $0.option == "test" }
println(searchBar.text)
}
or
func searchBarSearchButtonClicked( searchBar: UISearchBar!)
{
let filteredArray = filter(search_options_array) { $0.id == "test" }
println(searchBar.text)
}
You must retrieve property of searched object by which you perform searching

Resources