Differences Between Filtering Realm with Nspredicate and Block

Differences between filtering Realm with NSPredicate and block

Yes, there is a performance difference between the two approaches.

The NSPredicate-based filtering is performed by Realm's query engine, which filters directly on the data in the Realm file without ever creating instances of Person. It can take advantage of knowledge of the database structure to perform the queries more efficiently (by using indexes, for instance). In contrast, the block based filtering must create instances of Person for each object in your Realm in order to pass them to the block.

There are other semantic differences as well, which primarily stem from the differing result types of the two methods. The NSPredicate-based filtering returns a Results<T> rather than the [T] that the block-based filtering returns.

Results<T> is a live-updating view onto the results of your query. You can hand one to a view controller and its contents will update after other parts of your application perform writes that cause new objects to begin matching the predicate. You can also register for change notifications to learn about when new objects begin matching the predicate, an existing object stops matching it, or when an object that matches was modified in some way.

Filtering Realm objects with Swift

I don't usually use NSPredicate's directly, instead I do an inline predicate closure within the filter paramter.

let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.getType.filter({ $0.text == "special" } != [] ) })
print(filterThis)

Display NSProgressIndicator while filtering Realm

This is a perfect use case for Realm's collection notifications. That'd look something like:

var notificationToken: NotificationToken? = nil
var results: Results<MyClass>? = nil

func performFilterAction() {
filterProgressIndicator.isHidden = false
filterProgressIndicator.startAnimation(nil)

let realm = try! Realm()
results = realm.objects(MyClass.self).filter("location = %@", "US")
notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
self.filterProgressIndicator.isHidden = true
break
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
break
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
break
}
}
}

deinit {
notificationToken?.stop()
}

This lets you display a progress indicator while the initial filtering is performed, and also provides animated updates as changes are made to the data in the collection. If you don't care about the latter you could instead just call reloadData() in the .update case as well.

Invalid value when query on Int in realm

You shouldn't be using String interpolation when creating NSPredicates, since even though it is supported, it is really easy to mess up the predicate format. Simply use %@ for substituting variable values into the predicate.

let allRooms = Users.realm.objects(Rooms.self).filter("placeId == %@ AND floor == %@",placeId, floor)

Some further improvements to your code: don't use nil check, then force unwrapping, use optional binding when working with Optionals.

if let placeName = placeName , let floor = floor {

Also don't add an initial value to Arrays when creating them, instead of var roomNames = [""] and var roomTypes = [""], do

var roomNames = [String]()
var roomTypes = [String]()

Explain this code: uses a string as filter function

It's using:

// MARK: Filtering
/**
Returns a `Results` containing all objects matching the given predicate in the collection.
- parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments.
*/
func filter(_ predicateFormat: String, _ args: Any...) -> Results<Element>

Source: RealmCollectiton

Under the hood, it's using NSPredicate(format:), it "hides" it by simplifying and avoid writing each time NSPredicate(format: ...), and should use KeyPath too. More explanation on what you can do with that here at Predicate Format String Syntax.

not

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

on Sequence

Here a sample code to illustrate/mimic:

class TestClass: NSObject {
@objc var title: String
init(title: String) {
self.title = title
super.init()
}
override var description: String {
"TestClass - title: \(title)"
}
}

let objects: [TestClass] = [TestClass(title: "Title1"), TestClass(title: "Title2")]

// In a "Swifty way
let searchedText = "Title1"
let filtered1 = objects.filter {
$0.title == searchedText
}
print("filtered1: \(filtered1)")

// With a NSPredicate, which is more Objective-C (filtering on NSArray for instance)
// But also there is a translation with Core-Data, and then fetching optimization
let filtered2 = objects.filter {
NSPredicate(format: "title == 'Title1'").evaluate(with: $0)
}
print("filtered2: \(filtered2)")
// Same, but with avoiding hard coding strings
let filtered3 = objects.filter {
NSPredicate(format: "%K == %@", #keyPath(TestClass.title), searchedText).evaluate(with: $0)
}
print("filtered3: \(filtered3)")

extension Sequence {
func filter(_ predicateFormat: String, _ args: Any...) -> [Element] {
let predicate = NSPredicate(format: predicateFormat, argumentArray: args)
return self.filter({ predicate.evaluate(with: $0) })
}
}

// With an extension similar to what does Realm
let filtered4 = objects.filter("title == 'Title1'")
print("filtered4: \(filtered4)")

// With an extension similar to what does Realm, and less hard coded string (cf filtered3 construction)
let filtered5 = objects.filter("%K == %@", #keyPath(TestClass.title), searchedText)
print("filtered5: \(filtered5)")

With output:

$>filtered1: [TestClass - title: Title1]
$>filtered2: [TestClass - title: Title1]
$>filtered3: [TestClass - title: Title1]
$>filtered4: [TestClass - title: Title1]
$>filtered5: [TestClass - title: Title1]

Realm Swift background search

You filter the objects after pulling them all into memory (by converting the Results to an Array). You'll have a vastly better performance, if you let Realm handle the filtering. For that purpose you'd need to be able to make all your queries by predicates which you can combine to one OR-compound predicate.

Furthermore I'd avoid storing the matching field in the object to separate concerns as the values are transient. They are only needed as long those objects are kept in memory.

Beside that I'd recommend to use a primary key for IID and then just retrieve one object by another instead of building a huge predicate with all IDs.

To put it all together, this would be the way I'd tackle that:

func filterUsers(searchText:String, completion: (result: Array<(user: User, field: String)>) -> ()) {   
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var predicates = [
"firstName": NSPredicate(format: "firstName contains[c] %@", searchText)
"lastName": NSPredicate(format: "lastName contains[c] %@", searchText)
]
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: Array(predicates.values))

let bgRealm = try! Realm()

// Filter the whole list of users
let results = bgRealm.objects(User).filter(compoundPredicate)

// Find out which field is matching
let idsAndFields: [(IID: String, field: String)] = results.flatMap {
for (field, predicate) in predicates {
if predicate.evaluateWithObject($0) {
return (IID: $0.IID, field: field)
}
}
return nil
}

dispatch_async(dispatch_get_main_queue()) {
let realm = try! Realm()
let result = idsAndFields.flatMap {
if let user = realm.objectForPrimaryKey($0.IID) {
return (user: user, field: $0.field)
} else {
return nil
}
}
completion(result: result)
})
}
}

Cross-referencing predicate for Realm queries in Swift

To query all (actually there should be able one object because of primary key) objects B where objectB.propertyA == objectA.propertyA use:

let resultObjectsB = realm.objects(B).filter('propertyA = %@', objectA.propertyA)

Another option is to use One-to-One relationship between A and B, learn more at https://realm.io/docs/swift/latest/#to-one-relationships

Filtering NSMutableArray with NSPredicate

I discovered the issue. This line of code was the culprit:

let resultPredicate = NSPredicate(format: "name contains[c] %@", searchText)

Changing it to this fixed the issue:

let resultPredicate = NSPredicate(format: "SELF contains[cd] %@", searchText)


Related Topics



Leave a reply



Submit