Maximum length of String attribute in Core Data entity using NSSortDescriptor

329 Views Asked by At

I want to find the word with maximum characters of an entity attribute. I am trying to create a sort descriptor to compare string lengths. The code compiles, but I get the error unsupported NSSortDescriptor selector: comparatorOfStringLengths when the fetch query is executed. I tried the comparator block version of NSSortDescriptor but it is unsupported.

            query.sortDescriptors = [
                NSSortDescriptor.init(key: "word", ascending: false, selector: #selector(NSString.comparatorOfStringLengths))
            ]

Below is the latest version of the signature of the comparator method. I tried also using a static func with two arguments, and with Any instead of String as well, I get same result. I modeled this signature on the func localizedStandardCompare(_ string: String) -> ComparisonResult standard func.

extension NSString {   
    @objc func comparatorOfStringLengths(_ b: String) -> ComparisonResult {
        if self.length < b.length {
            return .orderedAscending
        }
        if self.length > b.length {
            return .orderedDescending
        }
        return .orderedSame
    }
}

Screenshot of error: enter image description here

EDIT: I adjusted/corrected the signature of selector specifier but this also fails: #selector(NSString.comparatorOfStringLengths(_:)))

1

There are 1 best solutions below

0
AudioBubble On

I could be wrong about that but i think with Core Data (iOS 13) such a NSPredicate and NSSortDescriptor as you would like it is still not possible.

An option might be to add an additional attribute wordlen of number type that holds the length of word. And fetch the entity with the highest value for wordlen.

For further explanation i quickly switch to my Person standards example with the entity Person with a string attribute name (corresponds to word in OP) and an integer attribute namelen.

With the new function validateNamelen that will be placed where the Person code was generated:

extension Person {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Person> {
        return NSFetchRequest<Person>(entityName: "Person")
    }

    @NSManaged public var name: String?
    @NSManaged public var namelen: NSNumber?

    @objc func validateNamelen(_ ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
        ioValue.pointee = NSNumber.init(integerLiteral: name!.count)
    }

}

that will be triggered by Core Data and helps to synchronize the value of namelen with newly created or updated Person entities.

Then adding an index NameLenIndex on namelen makes the fetch faster:

enter image description here

Swift 5 Code for evaluation:

    // Create Persons
    var personObj: Person = Person(context: mainContext)
    personObj.name = "Person1 A"
    personObj = Person(context: mainContext)
    personObj.name = "Person2 BB"
    personObj = Person(context: mainContext)
    personObj.name = "Person3 CCC"
    personObj = Person(context: mainContext)
    personObj.name = "Person4 A"
    personObj = Person(context: mainContext)
    personObj.name = "Person5 BB"
    personObj = Person(context: mainContext)
    personObj.name = "Person6 CCC"
    saveContext()

    // Get person
    let request: NSFetchRequest<Person> = Person.fetchRequest()

    // Which persons?
    let predicateMax = NSPredicate(format: "namelen==max(namelen)")
    let predicateMin = NSPredicate(format: "namelen==min(namelen)")

    // Persons with longest names
    request.predicate = predicateMin;

    print("Persons with shortes Names:")
    do {
        let results = try mainContext.fetch(request)
        for result in results {
            print(result.name ?? "No name found")
        }
    } catch {
        print("Error: \(error)")
    }

    request.predicate = predicateMax;

    print("Persons with longest Names:")
    do {
        let results = try mainContext.fetch(request)
        for result in results {
            print(result.name ?? "No name found")
        }
    } catch {
        print("Error: \(error)")
    }

Given an empty database at the beginning the output is like this:

Persons with shortes Names:
Person1 A
Person4 A
Persons with longest Names:
Person6 CCC
Person3 CCC