I'm having some trouble figuring out how to turn ordinary swift filtering code into an NSPredicate query with Realm. I have included a simplified example of my data structure. My goal is to figure out which of the User's contacts from the device are already registered for my app.
class User: Object {
let contacts = List<Contact>()
}
class Contact: Object {
let numbers = List<String>()
}
Each User object contains a list of 'Contacts' which are all the contacts currently stored on the Users device. And each 'Contact' Object has a list of 'numbers' because each contact can have multiple phone numbers.
In regular swift code, figuring out which of the users contacts are already registered for my app looks like this:
func alreadyRegistertedUserContacts(_ contacts: Results<Contact>,
allUsers: Results<User>) -> [Contact] {
return contacts.filter { (contact) -> Bool in
return contact.numbers.contains(where: { (number) -> Bool in
return allUsers.contains(where: { (user) -> Bool in
return user.phoneNumber == number
})
})
}
}
So the question is, for efficiencies sake, how would I change this function to use NSPredicates instead?
Thanks in advance for your help!
Realm does not currently support using properties whose types are lists of primitives in its predicates. Until that limitation is lifted, it's not possible to filter your objects using an NSPredicate alone.
If you were willing to change your model like so:
class StringObject: Object {
#objc dynamic var string = ""
}
class Contact: Object {
let numbers = List<StringObject>()
}
class User: Object {
let contacts = List<Contact>()
}
This should allow you to perform your filtering like so:
return contacts.filter("ANY numbers.value IN %#",
allUsers.flatMap { return $0.phoneNumber })
Related
I would like to make my class in Swift iterable.
My goal is to be able to create a class called Contact that holds properties such as the givenName, familyName, and middleName, like iOS CNContact. I would like to be able to have a class function that compares two instances of the class Contact, and finds which property the two contact objects have that match, so that say if both contacts have the same value for the givenName property, then the class function returns the result.
Here is a sample code:
class Contact {
static func compare(left: Contact, right: Contact) {
for property in left.properties {
if property == right.property {
// match is found
}
}
}
var givenName: String = ""
var familyName: String = ""
var middleName: String = ""
private var properties = [givenName, familyName, middleName]
}
let left = Contact()
let right = Contact()
Contact.compare(left: left, right: right)
I found posts that used mirroring and reflection, but I want to use Sequence and IteratorProtocol. I suspect there is already the ability to do exactly what I want to do. It seems to be a logical need that would arise.
What is the way to do this that has a balance between simplicity and the ability to address common needs to iterate through the instance properties of a class. An enumeration can be declared with given has values. Is there a way to make that work for this purpose? Is there a protocol that a class can use that assigns a hash value or other identifiable value that would allow for a sequential order to iterate through the properties of a class?
I was able to find posts and documentation that allowed me to get as far as the following code in playground that generated the following in debug window:
struct Name: Sequence {
typealias Iterator = NameIterator
typealias Element = Name
typealias Name = String
var name = "<name>"
func makeIterator() -> NameIterator {
return NameIterator()
}
}
struct NameIterator: IteratorProtocol {
typealias Iterator = String
typealias Element = Name
typealias Name = String
mutating func next() -> Name? {
let nextName = Name()
return nextName
}
}
let nameStrings = ["Debbie", "Harper", "Indivan", "Juniella"]
for nameString in nameStrings {
print(nameString)
}
Debbie
Harper
Indivan
Juniella
If you really don't want to use mirror, a straightforward way is to cycle through a list of key paths. This is particularly easy in your case because the properties are all strings:
class Contact {
static let properties = [\Contact.givenName, \Contact.familyName, \Contact.middleName]
static func compare(left: Contact, right: Contact) {
for property in properties {
if left[keyPath: property] == right[keyPath: property] {
print("got a match"); return
}
}
print("no match")
}
var givenName: String = ""
var familyName: String = ""
var middleName: String = ""
}
I think there's some confusion going on here.
The Sequence protocol and friends (IteratorProtocol, Collection, etc.) exist for you to be able to define custom sequences/collections that can leverage the existing collection algorithms (e.g. if you conform to Sequence, your type gets map "for free"). It has absolutely nothing to do with accessing object properties. If you want to do that, the only official reflection API in Swift is Mirror.
It's possible to...
...just Mirror, to create a standard collection (e.g. Array) of properties of an object
...just Sequence/Collection, to create a custom collection object that lists the property values of an object from hard-coded keypaths
...or you can use both, together, to create a custom collection object that uses Mirror to automatically list the properties of an object and their values
When you have a Realm model that looks something like:
class Thing {
var id: String?
var kids: List<String>
}
And have Thing objects like: Thing(id: "thingyOne", kids: List<"Momo", "Jojo">). You can easily query for objects that have id of "thingyOne" using Realm().objects(Thing.self).filter("id == 'thingyOne'"). How do you check which objects have kids named "Jojo" in their kids list?
I'm currently using this method:
let things = Realm().objects(Thing.self)
for thing in things {
if thing.kids.contains("Jojo") {
// Success
}
}
This makes the app extremely slow because I have thousands of Realm objects. How do you do it the correct way?
I have a strange problem:
I have a NSManagedObject called ItemTemplate. It has many child managed objects, e.g. CustomItemTemplate or SpecialItemTemplate.
Now I have a list viewController that is supposed to show these child managed objects. For example only "CustomItemTemplaze" or only "SpecialItemTemplate". I wrote this generic method to fetch all ItemTemplates and filter out the desired child-objects (I haven't found a better way yet).
private func loadTemplates<T: ItemTemplate>(ofType type: T.Type) -> [ModelObject] {
// ModelObject is just a model for my managed objects
var templates = [ModelObject]()
do {
let request: NSFetchRequest<ItemTemplate> = ItemTemplate.fetchRequest()
let result = try mainViewContext.fetch(request)
for item in result {
if item is T { // this is somehow always true
templates.append(item.modelObject) // add the converted item to the array
}
}
} catch let error as NSError {
print("Error: ", error.debugDescription)
}
return templates
}
I call it like this:
enum Category {
case custom
case special
public var templateClass: ItemTemplate.Type {
switch self {
case .custom:
return CustomItemTemplate.self
case .special:
return SpecialItemTemplate.self
}
}
}
loadTemplates(ofType: currentCategory.templateClass)
However, it's not filtering. if item is T seems to be true for every item. It thus returns every ItemTemplate, instead of only certain child objects.
Why is that? I can't explain it.
Thanks for any help!
I would like to ask how to corret my issue. I just simply append some "portals" to a depending country. EACH "portal" which comes more than once, I dont want to append.
I have following class definitions:
class cls_main{
var countries:[cls_country]!
init() {
countries = [cls_country]()
}
// "add Country"
func addCountry(iCountry:cls_country) {
countries.append(iCountry)
}
}
class cls_country{
var countryName:String!
var portals:[cls_portal]!
init() {
portals = [cls_portal]()
}
// "add Portal"
func addPortal(portName:String) {
var tmpPortal = cls_portal()
tmpPortal.portalName = portName
println("-->Input Portal: \(tmpPortal.portalName)")
if portals.count == 0 {
portals.append(tmpPortal)
} else {
for port in portals {
if port.portalName == portName {
println("SAME INPUT, DONT SAVE")
} else {
portals.append(tmpPortal)
}
}
}
}
func arrayCount(){
println("Portals : \(portals.count)")
}
}
class cls_portal{
var portalName:String!
}
And so I will call it:
var MAIN = cls_main()
var country = cls_country()
country.countryName = "USA"
country.addPortal("Dance")
country.addPortal("Dance") // Should not be appended...
country.addPortal("Hike")
country.addPortal("Swim")
country.addPortal("Play")
MAIN.addCountry(country)
country = cls_country()
After adding the values Im looping over the values and print them. The result would be like this:
Loop:
for country in MAIN.countries {
println("COUNTRY: \(country.countryName)")
if country.countryName == "USA" {
for portal in country.portals {
println(" -> PORTAL : \(portal.portalName)")
}
country.arrayCount()
}
}
Result:
-->Input Portal: Dance
-->Input Portal: Dance
SAME INPUT, DONT SAVE
-->Input Portal: Hike
-->Input Portal: Swim
-->Input Portal: Play
COUNTRY: USA
-> PORTAL : Dance
-> PORTAL : Hike
-> PORTAL : Swim
-> PORTAL : Swim
-> PORTAL : Play
-> PORTAL : Play
-> PORTAL : Play
-> PORTAL : Play
Portals : 8
So why every and each portal will be multiplying at the end? Thank you very much.
In your search loop, you are deciding after looking at each element whether tmpPortal is in your portals or not. You need to potentially look at all of the items before you can make that decision. Add a variable found to note that it has been found. You can break out of the loop once you've found the item.
if portals.count == 0 {
portals.append(tmpPortal)
} else {
var found = false
for port in portals {
if port.portalName == portName {
println("SAME INPUT, DONT SAVE")
found = true
// found it, stop searching
break
}
}
if !found {
portals.append(tmpPortal)
}
}
If you are using Swift 1.2 a better solution to this would by using Set so you wont need addPortal method at all. Set offers almost the same functionality as array but it simply does not store same values. Be aware that in order to make it work with set your cls_portal class must adopt hashable and equitable protocols
A set stores distinct values of the same type in a collection with no defined ordering. You can use sets as an alternative to arrays when the order of items is not important, or when you need to ensure that an item only appears once.
You’re looping multiple times over the array, checking it. But for every element that isn’t a match, you are inserting the portal. So when there are 3 other non-matching elements, you insert a portal 3 times.
Try replacing your inner loop (everything from if portals.count == all the way down to the end of the else) with this:
if !contains(portals, { $0.portalName == portName }) {
portals.append(tmpPortal)
}
contains is a function that checks if a collection (like your array of portals) contains an entry that matches a certain criteria. The criteria is determined by a closure, in this case one that checks if the element’s portal name matches the one you’re checking for. Try reading this link if you’re not familiar with closures – they can be very helpful in Swift.
P.S. there are a few other things in your code you might want to reconsider. For example, it’s best to avoid using implicitly-unwrapped optionals (i.e. types with a ! after them) like this. Implicit optionals are in Swift for some very specific purposes, which it doesn’t look like apply in your code. Especially not with arrays - better to just make the array empty on initialization. But also, if it makes no sense to have a portal without a country name, you make it part of the initializer.
So instead of this:
class cls_country{
var countryName:String!
var portals:[cls_portal]!
init() {
portals = [cls_portal]()
}
}
// and in use:
var tmpPortal = cls_portal()
tmpPortal.portalName = portName
you could write this:
class cls_country {
var portals = [cls_country]()
// consider let not var if a country will never
// need to change its name:
let countryName: String
init(countryName: String) {
self.countryName = countryName
//no need to initialize portals in init
}
}
// then later, in use:
var tmpPortal = cls_portal(countryName: portName)
I'm trying to use NSPredicate like so:
let namePredicate = NSPredicate(format: "(username BEGINSWITH[c] $word)")
if prefix == "#" {
if countElements(word) > 0 {
suggestionDatasource.users = (suggestionDatasource.users as NSArray).filteredArrayUsingPredicate(namePredicate!.predicateWithSubstitutionVariables(["word" : word])) as [User]
}
}
However, my User class doesn't subclass NSObject so it's not Key Value Compliant. I get unrecognized selector 'valueForKey' whenever I try this. Is there a way I can make a custom class Key Value Compliant without subclassing NSObject? Or perhaps a way to use NSPredicate without having to use KVC.
Thanks
Aren't you making this awfully unnecessarily hard for yourself? Why not just use what Swift gives you - filter, hasPrefix, and so on? I don't know what a User actually is, but let's pretend it's something like this:
struct User {
let username : String = ""
}
And let's pretend that users is like this:
let users = [User(username:"Matt Neuburg"), User(username:"Dudley Doright")]
Then what you seem to be trying to do is trivial in Swift:
let word = "Matt"
let users2 = users.filter {$0.username.hasPrefix(word)}
You can manually implement value(forKey key: String) -> Any? method:
extension Model {
#objc func value(forKey key: String) -> Any? {
switch key {
case "id":
return id
// Other fields
default:
return nil
}
}
}
Note #objc prefix of the method: it's important, as it's allowing NSPredicate to see that the method is implemented.